diff --git a/.changeset/afraid-poets-thank.md b/.changeset/afraid-poets-thank.md new file mode 100644 index 0000000000..b234e2e2c1 --- /dev/null +++ b/.changeset/afraid-poets-thank.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Fix card scrolling behaviour diff --git a/.changeset/angry-sites-fold.md b/.changeset/angry-sites-fold.md deleted file mode 100644 index 8124ddaf47..0000000000 --- a/.changeset/angry-sites-fold.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Added the configuration flag `auth.omitIdentityTokenOwnershipClaim` that causes issued user tokens to no longer contain the `ent` claim that represents the ownership references of the user. - -The benefit of this new flag is that issued user tokens will be much smaller in -size, but they will no longer be self-contained. This means that any consumers -of the token that require access to the ownership claims now need to call the -`/api/auth/v1/userinfo` endpoint instead. Within the Backstage ecosystem this is -done automatically, as clients will still receive the full set of claims during -authentication, while plugin backends will need to use the `UserInfoService` -which already calls the user info endpoint if necessary. - -When enabling this flag, it is important that any custom sign-in resolvers directly return the result of the sign-in method. For example, the following would not work: - -```ts -const { token } = await ctx.issueToken({ - claims: { sub: entityRef, ent: [entityRef] }, -}); -return { token }; // WARNING: This will not work with the flag enabled -``` - -Instead, the sign-in resolver should directly return the result: - -```ts -return ctx.issueToken({ - claims: { sub: entityRef, ent: [entityRef] }, -}); -``` diff --git a/.changeset/beige-kiwis-flow.md b/.changeset/beige-kiwis-flow.md deleted file mode 100644 index 500095b44b..0000000000 --- a/.changeset/beige-kiwis-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes-node': minor ---- - -**BREAKING**: The `PinnipedHelper` class now expects a regular `LoggerService` instance from the new backend system, instead of a Winston logger. diff --git a/.changeset/blue-apples-pump.md b/.changeset/blue-apples-pump.md new file mode 100644 index 0000000000..c7bbdbbafc --- /dev/null +++ b/.changeset/blue-apples-pump.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +Added OwnershipCard, ComponentsGrid and UserProfileCard components to the `overridableComponents`. diff --git a/.changeset/brave-donuts-sink.md b/.changeset/brave-donuts-sink.md deleted file mode 100644 index 47827345a5..0000000000 --- a/.changeset/brave-donuts-sink.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/test-utils': patch ---- - -Added support for interpolating JSX elements with the `MockTranslationApi`. diff --git a/.changeset/brave-eggs-mate.md b/.changeset/brave-eggs-mate.md deleted file mode 100644 index 73eb8e363a..0000000000 --- a/.changeset/brave-eggs-mate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/theme': patch ---- - -Show arrow when MuiTableSortLabel receives focus diff --git a/.changeset/brave-pumas-attack.md b/.changeset/brave-pumas-attack.md new file mode 100644 index 0000000000..b08e3e5ff3 --- /dev/null +++ b/.changeset/brave-pumas-attack.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-openapi-utils': patch +--- + +Update `express-openapi-validator` to 5.5.8 to fix security vulnerability in transitive dependency `multer` diff --git a/.changeset/brave-toes-switch.md b/.changeset/brave-toes-switch.md deleted file mode 100644 index 27b39cccd3..0000000000 --- a/.changeset/brave-toes-switch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-msgraph': minor ---- - -Add new `userGroupMember.path`, `user.path` and, `group.path` option to each query type to allow more complex msgraph queries diff --git a/.changeset/breezy-fans-exist.md b/.changeset/breezy-fans-exist.md new file mode 100644 index 0000000000..bf70b58cea --- /dev/null +++ b/.changeset/breezy-fans-exist.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Added missing space for alert display component diff --git a/.changeset/breezy-hotels-deny.md b/.changeset/breezy-hotels-deny.md deleted file mode 100644 index 13c8b47a7f..0000000000 --- a/.changeset/breezy-hotels-deny.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': patch ---- - -Added missing `organizations` property to `azure` section in `config.d.ts` file diff --git a/.changeset/bright-elephants-sparkle.md b/.changeset/bright-elephants-sparkle.md new file mode 100644 index 0000000000..05dc6150c6 --- /dev/null +++ b/.changeset/bright-elephants-sparkle.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Created a flag for scaffolding apps using the new frontend system. diff --git a/.changeset/bright-moles-sort.md b/.changeset/bright-moles-sort.md deleted file mode 100644 index 9796729d1a..0000000000 --- a/.changeset/bright-moles-sort.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-dynamic-feature-service': minor ---- - -**BREAKING** Removed support for the legacy backend, please migrate to the new backend system diff --git a/.changeset/bumpy-showers-design.md b/.changeset/bumpy-showers-design.md deleted file mode 100644 index 2db5006944..0000000000 --- a/.changeset/bumpy-showers-design.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': patch ---- - -If the commit action is not `create` log a more appropriate error message to the end user advising that the files they're trying to modify might not exist diff --git a/.changeset/busy-badgers-hang.md b/.changeset/busy-badgers-hang.md deleted file mode 100644 index fc4b0e907e..0000000000 --- a/.changeset/busy-badgers-hang.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Removed `lerna-debug.log*` pattern from `.gitignore` as Lerna was removed from the package in version `@backstage/create-app@0.5.19`. diff --git a/.changeset/calm-geese-serve.md b/.changeset/calm-geese-serve.md new file mode 100644 index 0000000000..ff52f59b56 --- /dev/null +++ b/.changeset/calm-geese-serve.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +Remove usage of the deprecated `loggerToWinstonLogger` from `@backstage/backend-common`. diff --git a/.changeset/calm-toys-occur.md b/.changeset/calm-toys-occur.md deleted file mode 100644 index ea03aaf4f8..0000000000 --- a/.changeset/calm-toys-occur.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -'@backstage/plugin-bitbucket-cloud-common': minor ---- - -Update Bitbucket Cloud schema and models. - -The latest schema was fetched from Bitbucket Cloud and stored locally. -Based on the updated schema, the models got regenerated. - -**BREAKING:** - -Due to the schema changes, the model update includes one breaking change: - -- `Account.username` was removed. - -Additionally, there were a couple of compatible changes including the addition of -`BaseCommit.committer` and others. diff --git a/.changeset/chatty-coats-sin.md b/.changeset/chatty-coats-sin.md new file mode 100644 index 0000000000..97f74bee80 --- /dev/null +++ b/.changeset/chatty-coats-sin.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': patch +--- + +Deprecated `createPublicSignInApp`, which has been replaced by the new `appModulePublicSignIn` from `@backstage/plugin-app/alpha` instead. diff --git a/.changeset/chatty-dodos-design.md b/.changeset/chatty-dodos-design.md new file mode 100644 index 0000000000..d998f09250 --- /dev/null +++ b/.changeset/chatty-dodos-design.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +'@backstage/backend-defaults': patch +--- + +Fixed WinstonLogger throwing when redactions were null or undefined diff --git a/.changeset/chatty-schools-post.md b/.changeset/chatty-schools-post.md new file mode 100644 index 0000000000..0500a8a838 --- /dev/null +++ b/.changeset/chatty-schools-post.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: Remove deprecated `source` property from the `AppNodeSpec` type, use `AppNodeSpec.plugin` instead. diff --git a/.changeset/chatty-showers-cheat.md b/.changeset/chatty-showers-cheat.md deleted file mode 100644 index 24c2720fb6..0000000000 --- a/.changeset/chatty-showers-cheat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': minor ---- - -**BREAKING ALPHA**: You can no longer import the catalog plugin from the `/alpha` export; please use the regular root default export instead. diff --git a/.changeset/chilly-trams-cheer.md b/.changeset/chilly-trams-cheer.md deleted file mode 100644 index 0e14423ef8..0000000000 --- a/.changeset/chilly-trams-cheer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes-react': patch ---- - -Add headlamp formatter diff --git a/.changeset/chubby-cougars-run.md b/.changeset/chubby-cougars-run.md deleted file mode 100644 index deb954ef6b..0000000000 --- a/.changeset/chubby-cougars-run.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -This patch addresses an issue identified in Backstage when configured with a MySQL database. If an entity of type location -(e..all.yaml) has more than 70 referenced entities, clicking "Refresh" does not update the referenced entities as expected. This occurs because the TEXT type in MySQL has a limit of 65,535 bytes, which is insufficient to store all the referenced entities, causing the refresh operation to fail. diff --git a/.changeset/chubby-needles-vanish.md b/.changeset/chubby-needles-vanish.md deleted file mode 100644 index 2cd664dcdd..0000000000 --- a/.changeset/chubby-needles-vanish.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/canon': patch ---- - -Fix Canon missing dependencies diff --git a/.changeset/clean-chairs-sit-2.md b/.changeset/clean-chairs-sit-2.md new file mode 100644 index 0000000000..6fb7d6c844 --- /dev/null +++ b/.changeset/clean-chairs-sit-2.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-scaffolder-react': minor +--- + +**DEPRECATION**: The following types have been deprecated from this package and moved into `@backstage/plugin-scaffolder-common` and should be imported from there instead. + +`Action`, `ListActionsResponse`, `LogEvent`, `ScaffolderApi`, `ScaffolderDryRunOptions`, `ScaffolderDryRunResponse`, `ScaffolderGetIntegrationsListOptions`, `ScaffolderGetIntegrationsListResponse`, +`ScaffolderOutputLink`, `ScaffolderOutputText`, `ScaffolderScaffoldOptions`, `ScaffolderScaffoldResponse`, `ScaffolderStreamLogsOptions`, `ScaffolderTask`, `ScaffolderTaskOutput`, `ScaffolderTaskStatus`, +`ScaffolderUsageExample`, `TemplateFilter`, `TemplateGlobalFunction`, `TemplateGlobalValue`, `TemplateParameterSchema`. diff --git a/.changeset/clean-chairs-sit.md b/.changeset/clean-chairs-sit.md new file mode 100644 index 0000000000..9e76e56887 --- /dev/null +++ b/.changeset/clean-chairs-sit.md @@ -0,0 +1,10 @@ +--- +'@backstage/backend-openapi-utils': minor +'@backstage/plugin-scaffolder-backend': minor +'@backstage/plugin-scaffolder-common': minor +'@backstage/plugin-scaffolder-react': minor +'@backstage/plugin-scaffolder-node': minor +'@backstage/plugin-scaffolder': minor +--- + +Move Scaffolder API to OpenAPI diff --git a/.changeset/clever-plants-warn.md b/.changeset/clever-plants-warn.md new file mode 100644 index 0000000000..7018aa31ee --- /dev/null +++ b/.changeset/clever-plants-warn.md @@ -0,0 +1,24 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-app-api': patch +--- + +Add support for a new `aliasFor` option for `createRouteRef`. This allows for the creation of a new route ref that acts as an alias for an existing route ref that is installed in the app. This is particularly useful when creating modules that override existing plugin pages, without referring to the existing plugin. For example: + +```tsx +export default createFrontendModule({ + pluginId: 'catalog', + extensions: [ + PageBlueprint.make({ + params: { + defaultPath: '/catalog', + routeRef: createRouteRef({ aliasFor: 'catalog.catalogIndex' }), + loader: () => + import('./CustomCatalogIndexPage').then(m => ( + + )), + }, + }), + ], +}); +``` diff --git a/.changeset/cold-heads-arrive.md b/.changeset/cold-heads-arrive.md new file mode 100644 index 0000000000..1e1f5ff6e2 --- /dev/null +++ b/.changeset/cold-heads-arrive.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: The `defaultPath` param of `PageBlueprint` has been renamed to `path`. This change does not affect the compatibility of extensions created with older versions of this blueprint. diff --git a/.changeset/cold-humans-check.md b/.changeset/cold-humans-check.md deleted file mode 100644 index 5bcb6caa4b..0000000000 --- a/.changeset/cold-humans-check.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-user-settings': patch ---- - -plugin-user-settingsgs support i18n diff --git a/.changeset/cold-lemons-design.md b/.changeset/cold-lemons-design.md new file mode 100644 index 0000000000..9bdadac9c7 --- /dev/null +++ b/.changeset/cold-lemons-design.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app': patch +--- + +Log a warning when `SwappableComponent` extensions are installed outside of using the `app` plugin diff --git a/.changeset/common-goats-raise.md b/.changeset/common-goats-raise.md deleted file mode 100644 index c2d797b719..0000000000 --- a/.changeset/common-goats-raise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-github': patch ---- - -Use action context logger in Octokit client diff --git a/.changeset/common-heads-build.md b/.changeset/common-heads-build.md new file mode 100644 index 0000000000..2874ea7594 --- /dev/null +++ b/.changeset/common-heads-build.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Fixes some styles on the Select component in BUI. diff --git a/.changeset/component-refs-app.md b/.changeset/component-refs-app.md new file mode 100644 index 0000000000..52bfa85c58 --- /dev/null +++ b/.changeset/component-refs-app.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-app': patch +--- + +Default implementations of core components are now provided by this package. + +A backwards compatible `componentsApi` implementation is also provided from this package which uses the `SwappableComponentsApi` as the implementation. This backwards compatible wrapper will be removed in the future. diff --git a/.changeset/component-refs-breaking-app.md b/.changeset/component-refs-breaking-app.md new file mode 100644 index 0000000000..9ba6307f60 --- /dev/null +++ b/.changeset/component-refs-breaking-app.md @@ -0,0 +1,15 @@ +--- +'@backstage/plugin-app': minor +--- + +**BREAKING**: The `componentsApi` implementation has been removed from the plugin and replaced with the new `SwappableComponentsApi` instead. + +If you were overriding the `componentsApi` implementation, you can now use the new `SwappableComponentsApi` instead. + +```ts +// old +appPlugin.getExtension('api:app/components').override(...) + +// new +appPlugin.getExtension('api:app/swappable-components').override(...) +``` diff --git a/.changeset/component-refs-breaking-frontend-plugin-api.md b/.changeset/component-refs-breaking-frontend-plugin-api.md new file mode 100644 index 0000000000..47c40865d3 --- /dev/null +++ b/.changeset/component-refs-breaking-frontend-plugin-api.md @@ -0,0 +1,96 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: The component system has been overhauled to use `SwappableComponent` instead of `ComponentRef`. Several APIs have been removed and replaced: + +- Removed: `createComponentRef`, `createComponentExtension`, `ComponentRef`, `ComponentsApi`, `componentsApiRef`, `useComponentRef`, `coreComponentRefs` +- Added: `createSwappableComponent`, `SwappableComponentBlueprint`, `SwappableComponentRef`, `SwappableComponentsApi`, `swappableComponentsApiRef` + +**BREAKING**: The default `componentRefs` and exported `Core*Props` have been removed and have replacement `SwappableComponents` and revised type names instead. + +- The `errorBoundaryFallback` component and `CoreErrorBoundaryFallbackProps` type have been replaced with `ErrorDisplay` swappable component and `CoreErrorDisplayProps` respectively. +- The `progress` component and `CoreProgressProps` type have been replaced with `Progress` swappable component and `ProgressProps` respectively. +- The `notFoundErrorPage` component and `CoreNotFoundErrorPageProps` type have been replaced with `NotFoundErrorPage` swappable component and `NotFoundErrorPageProps` respectively. + +**Migration for creating swappable components:** + +```tsx +// OLD: Using createComponentRef and createComponentExtension +import { + createComponentRef, + createComponentExtension, +} from '@backstage/frontend-plugin-api'; + +const myComponentRef = createComponentRef<{ title: string }>({ + id: 'my-plugin.my-component', +}); + +const myComponentExtension = createComponentExtension({ + ref: myComponentRef, + loader: { + lazy: () => import('./MyComponent').then(m => m.MyComponent), + }, +}); + +// NEW: Using createSwappableComponent and SwappableComponentBlueprint +import { + createSwappableComponent, + SwappableComponentBlueprint, +} from '@backstage/frontend-plugin-api'; + +const MySwappableComponent = createSwappableComponent({ + id: 'my-plugin.my-component', + loader: () => import('./MyComponent').then(m => m.MyComponent), +}); + +const myComponentExtension = SwappableComponentBlueprint.make({ + name: 'my-component', + params: { + component: MySwappableComponent, + loader: () => import('./MyComponent').then(m => m.MyComponent), + }, +}); +``` + +**Migration for using components:** + +```tsx +// OLD: Using ComponentsApi and useComponentRef +import { + useComponentRef, + componentsApiRef, + useApi, + coreComponentRefs, +} from '@backstage/frontend-plugin-api'; + +const MyComponent = useComponentRef(myComponentRef); +const ProgressComponent = useComponentRef(coreComponentRefs.progress); + + +// NEW: Direct component usage +import { Progress } from '@backstage/frontend-plugin-api'; + +// Use directly as React Component + + +``` + +**Migration for core component references:** + +```tsx +// OLD: Core component refs +import { coreComponentRefs } from '@backstage/frontend-plugin-api'; + +coreComponentRefs.progress +coreComponentRefs.notFoundErrorPage +coreComponentRefs.errorBoundaryFallback + +// NEW: Direct swappable component imports +import { Progress, NotFoundErrorPage, ErrorDisplay } from '@backstage/frontend-plugin-api'; + +// Use directly as React components + + + +``` diff --git a/.changeset/cool-bikes-push.md b/.changeset/cool-bikes-push.md deleted file mode 100644 index 00825c1ad0..0000000000 --- a/.changeset/cool-bikes-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-app-api': patch ---- - -Updated `I18nextTranslationApi` to support interpolation of JSX elements. diff --git a/.changeset/cool-cities-grab.md b/.changeset/cool-cities-grab.md deleted file mode 100644 index 372ca9887f..0000000000 --- a/.changeset/cool-cities-grab.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -`LogViewer` now supports a `textWrap` prop that wraps log lines to the next line for overflowing content instead of using horizontal scroll diff --git a/.changeset/cool-colts-float.md b/.changeset/cool-colts-float.md deleted file mode 100644 index c001f81ade..0000000000 --- a/.changeset/cool-colts-float.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/repo-tools': patch ---- - -Support passing additional properties to OpenAPI server generator diff --git a/.changeset/cool-knives-design.md b/.changeset/cool-knives-design.md deleted file mode 100644 index 63197edd16..0000000000 --- a/.changeset/cool-knives-design.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -The `id` option of `createFrontendPlugin` has been renamed to `pluginId` in order to better align with similar APIs in the frontend and backend systems. - -The old `id` option is deprecated and will be removed in a future release. diff --git a/.changeset/crazy-chefs-sin.md b/.changeset/crazy-chefs-sin.md deleted file mode 100644 index 8d33add929..0000000000 --- a/.changeset/crazy-chefs-sin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend-module-slack': patch ---- - -Fix slack notification processor to handle a notification with an empty description diff --git a/.changeset/crazy-pants-exist.md b/.changeset/crazy-pants-exist.md new file mode 100644 index 0000000000..5eca381523 --- /dev/null +++ b/.changeset/crazy-pants-exist.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': minor +--- + +Added plugin and module templates for the new frontend system. These templates are not included by default, but can be included by adding `@backstage/cli/templates/new-frontend-plugin` and `@backstage/cli/templates/new-frontend-plugin-module` as [custom templates](https://backstage.io/docs/tooling/cli/templates#installing-custom-templates). diff --git a/.changeset/create-app-1745325336.md b/.changeset/create-app-1753196727.md similarity index 100% rename from .changeset/create-app-1745325336.md rename to .changeset/create-app-1753196727.md diff --git a/.changeset/create-app-1745936753.md b/.changeset/create-app-1754401469.md similarity index 100% rename from .changeset/create-app-1745936753.md rename to .changeset/create-app-1754401469.md diff --git a/.changeset/cruel-bars-buy.md b/.changeset/cruel-bars-buy.md new file mode 100644 index 0000000000..d172551428 --- /dev/null +++ b/.changeset/cruel-bars-buy.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': patch +--- + +Updated import of the `FrontendFeature` type. diff --git a/.changeset/cruel-lights-sip.md b/.changeset/cruel-lights-sip.md deleted file mode 100644 index b38ab052a9..0000000000 --- a/.changeset/cruel-lights-sip.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend-module-github-provider': patch ---- - -Added missing types package diff --git a/.changeset/fruity-bags-flow.md b/.changeset/cruel-zoos-argue.md similarity index 73% rename from .changeset/fruity-bags-flow.md rename to .changeset/cruel-zoos-argue.md index bc6034a489..eb59d2d85b 100644 --- a/.changeset/fruity-bags-flow.md +++ b/.changeset/cruel-zoos-argue.md @@ -1,23 +1,21 @@ --- '@backstage/plugin-catalog-unprocessed-entities': patch -'@backstage/frontend-test-utils': patch +'@backstage/frontend-defaults': patch '@backstage/core-compat-api': 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-search-react': 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 --- -Internal update to use the new `pluginId` option of `createFrontendPlugin`. +Internal update to align with new blueprint parameter naming in the new frontend system. diff --git a/.changeset/cyan-pots-appear.md b/.changeset/cyan-pots-appear.md deleted file mode 100644 index f82f4548c3..0000000000 --- a/.changeset/cyan-pots-appear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-unprocessed': minor ---- - -**BREAKING** Removed support for the legacy backend and removed references to `@backstage/backend-common`, please [migrate to the new backend system](https://backstage.io/docs/backend-system/building-plugins-and-modules/migrating) diff --git a/.changeset/deep-apples-attack.md b/.changeset/deep-apples-attack.md new file mode 100644 index 0000000000..abf34ad863 --- /dev/null +++ b/.changeset/deep-apples-attack.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Show additional information about the cause in error messages from GitLab diff --git a/.changeset/deep-mangos-dig.md b/.changeset/deep-mangos-dig.md new file mode 100644 index 0000000000..237f625e27 --- /dev/null +++ b/.changeset/deep-mangos-dig.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Attempt to circumvent event listener memory leak in compression middleware diff --git a/.changeset/deep-ties-move.md b/.changeset/deep-ties-move.md deleted file mode 100644 index 57f6c7772c..0000000000 --- a/.changeset/deep-ties-move.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-ldap': patch ---- - -Improves error reporting for missing metadata.name in LDAP catalog provider. diff --git a/.changeset/dirty-grapes-vanish.md b/.changeset/dirty-grapes-vanish.md deleted file mode 100644 index b78660d21e..0000000000 --- a/.changeset/dirty-grapes-vanish.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/backend-defaults': minor ---- - -**BREAKING**: The `DefaultSchedulerService` constructor options now requires `RootLifecycleService`, `HttpRouterService`, and `PluginMetadataService` fields. - -The scheduler will register a REST API for listing and triggering tasks. Please see [the scheduler documentation](https://backstage.io/docs/backend-system/core-services/scheduler) for more details about this API. diff --git a/.changeset/dry-carpets-hope.md b/.changeset/dry-carpets-hope.md deleted file mode 100644 index 569e089a66..0000000000 --- a/.changeset/dry-carpets-hope.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-backstage-openapi': patch ---- - -Do not swallow errors; instead allow them to bubble up to the task scheduler for better tracking and logging. diff --git a/.changeset/dull-doodles-trade.md b/.changeset/dull-doodles-trade.md deleted file mode 100644 index eb9399fcc7..0000000000 --- a/.changeset/dull-doodles-trade.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes-backend': patch ---- - -Changed logging of cluster details to debug to minimise log clutter. diff --git a/.changeset/early-colts-accept.md b/.changeset/early-colts-accept.md deleted file mode 100644 index ff583144f0..0000000000 --- a/.changeset/early-colts-accept.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/backend-dynamic-feature-service': patch ---- - -Fixed various typos. -`FrontendRemoteResolver`'s misspelled `getAdditionaRemoteInfo` has been deprecated. Use the correct spelling `getAdditionalRemoteInfo` instead. diff --git a/.changeset/early-dryers-teach.md b/.changeset/early-dryers-teach.md deleted file mode 100644 index d747fbc730..0000000000 --- a/.changeset/early-dryers-teach.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-node': minor ---- - -Added `parseEntityYaml` from `@backstage/plugin-catalog-backend`, to make it more easily usable by custom plugins and modules diff --git a/.changeset/eight-sloths-walk.md b/.changeset/eight-sloths-walk.md new file mode 100644 index 0000000000..666069ce8b --- /dev/null +++ b/.changeset/eight-sloths-walk.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: Removed the deprecated `createFrontendPlugin` variant where the plugin ID is passed via an `id` option. To update existing code, switch to using the `pluginId` option instead. diff --git a/.changeset/eight-toys-feel.md b/.changeset/eight-toys-feel.md deleted file mode 100644 index c0f5a12c40..0000000000 --- a/.changeset/eight-toys-feel.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -Added a configuration to permit backend plugin module failures on startup: - -```yaml -backend: - ... - startup: - plugins: - plugin-x: - modules: - module-y: - onPluginModuleBootFailure: continue -``` - -This configuration permits `plugin-x` with `module-y` to fail on startup. Omitting the -`onPluginModuleBootFailure` configuration matches the previous behavior, wherein any -individual plugin module failure is forwarded to the plugin and aborts backend startup. - -The default can also be changed, so that continuing on failure is the default -unless otherwise specified: - -```yaml -backend: - startup: - default: - onPluginModuleBootFailure: continue - plugins: - catalog: - modules: - github: - onPluginModuleBootFailure: abort -``` diff --git a/.changeset/eleven-ghosts-strive.md b/.changeset/eleven-ghosts-strive.md deleted file mode 100644 index ac48c19524..0000000000 --- a/.changeset/eleven-ghosts-strive.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Full support in EntityPicker (and derivatives) for default EntityPresentationApi diff --git a/.changeset/eleven-tigers-drop.md b/.changeset/eleven-tigers-drop.md new file mode 100644 index 0000000000..ea2702cfb5 --- /dev/null +++ b/.changeset/eleven-tigers-drop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-home': patch +--- + +Add the missing Visits API to the alpha plugin, fixing a crash due to the API not being installed. diff --git a/.changeset/empty-years-grow.md b/.changeset/empty-years-grow.md new file mode 100644 index 0000000000..17a5ef0e36 --- /dev/null +++ b/.changeset/empty-years-grow.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': minor +--- + +We are moving our DataTable component to React Aria. We removed our DataTable to only use Table as a single and opinionated option for tables. This new structure is made possible by using React Aria under the hood. diff --git a/.changeset/every-schools-find.md b/.changeset/every-schools-find.md new file mode 100644 index 0000000000..b4cb15bf52 --- /dev/null +++ b/.changeset/every-schools-find.md @@ -0,0 +1,20 @@ +--- +'@backstage/plugin-catalog-unprocessed-entities': patch +'@backstage/frontend-app-api': patch +'@backstage/core-compat-api': patch +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-notifications': 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 +--- + +Internal update to use the new variant of `ApiBlueprint`. diff --git a/.changeset/evil-forks-hang.md b/.changeset/evil-forks-hang.md new file mode 100644 index 0000000000..f261c48c2b --- /dev/null +++ b/.changeset/evil-forks-hang.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': minor +--- + +Added the `analyzeLocation` method to `catalogApiMock` diff --git a/.changeset/evil-phones-unite.md b/.changeset/evil-phones-unite.md new file mode 100644 index 0000000000..9a3e4ff151 --- /dev/null +++ b/.changeset/evil-phones-unite.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Support `filter` parameter on the `EntityHeaderBlueprint` diff --git a/.changeset/famous-cities-stand.md b/.changeset/famous-cities-stand.md deleted file mode 100644 index 86ba727997..0000000000 --- a/.changeset/famous-cities-stand.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-auth-node': patch ---- - -Added the `identity` property to `BackstageSignInResult`. - -The `prepareBackstageIdentityResponse` function will now also forward the `identity` to the response if present in the provided sign-in result. diff --git a/.changeset/fancy-ducks-help.md b/.changeset/fancy-ducks-help.md new file mode 100644 index 0000000000..06cfc23309 --- /dev/null +++ b/.changeset/fancy-ducks-help.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: Removed the `routable` property from `ExtensionBoundary`. This property was never needed in practice and is instead inferred from whether or not the extension outputs a route reference. It can be safely removed. diff --git a/.changeset/fancy-frogs-like.md b/.changeset/fancy-frogs-like.md deleted file mode 100644 index ed3a9fbf81..0000000000 --- a/.changeset/fancy-frogs-like.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Internal code cleanup diff --git a/.changeset/fast-jars-push.md b/.changeset/fast-jars-push.md new file mode 100644 index 0000000000..2073e008eb --- /dev/null +++ b/.changeset/fast-jars-push.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: The `ResolveInputValueOverrides` type is no longer exported. diff --git a/.changeset/fifty-ads-dance.md b/.changeset/fifty-ads-dance.md new file mode 100644 index 0000000000..79801caafd --- /dev/null +++ b/.changeset/fifty-ads-dance.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Fixes a couple of small bugs in BUI including setting H1 and H2 correctly on the Header and HeaderPage. diff --git a/.changeset/five-ducks-hide.md b/.changeset/five-ducks-hide.md new file mode 100644 index 0000000000..6ce1fec8f7 --- /dev/null +++ b/.changeset/five-ducks-hide.md @@ -0,0 +1,31 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: The `NavLogoBlueprint` has been removed and replaced by `NavContentBlueprint`, which instead replaces the entire navbar. The default navbar has also been switched to a more minimal implementation. + +To use `NavContentBlueprint` to install new logos, you can use it as follows: + +```tsx +NavContentBlueprint.make({ + params: { + component: ({ items }) => { + return compatWrapper( + + + + {/* Other sidebar content */} + + + {items.map((item, index) => ( + + ))} + + + {/* Other sidebar content */} + , + ); + }, + }, +}); +``` diff --git a/.changeset/floppy-days-tell.md b/.changeset/floppy-days-tell.md deleted file mode 100644 index 2eb184f6fe..0000000000 --- a/.changeset/floppy-days-tell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/canon': patch ---- - -For improved a11y, clicking a Select component label now focuses the Select trigger element, and the TextField component's label is now styled to indicate it's interactive. diff --git a/.changeset/floppy-groups-hug.md b/.changeset/floppy-groups-hug.md new file mode 100644 index 0000000000..6e47ecaaf0 --- /dev/null +++ b/.changeset/floppy-groups-hug.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: Removed the ability to define a default extension `name` in blueprints. This option had no practical purpose as blueprints already use the `kind` to identity the source of the extension. diff --git a/.changeset/fluffy-otters-cry.md b/.changeset/fluffy-otters-cry.md new file mode 100644 index 0000000000..fd24a0bb47 --- /dev/null +++ b/.changeset/fluffy-otters-cry.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': minor +--- + +Use an app plugin for built-in extension app node specs. diff --git a/.changeset/four-peaches-talk.md b/.changeset/four-peaches-talk.md deleted file mode 100644 index 4b0d2e8a47..0000000000 --- a/.changeset/four-peaches-talk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Internal refactor of opaque type handling. diff --git a/.changeset/four-spiders-jump.md b/.changeset/four-spiders-jump.md new file mode 100644 index 0000000000..d5a92b8745 --- /dev/null +++ b/.changeset/four-spiders-jump.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app': patch +--- + +Added a new module for implementing public sign-in apps, exported as `appModulePublicSignIn` via the `/alpha` sub-path export. This replaces the `createPublicSignInApp` export from `@backstage/frontend-defaults`, which is now deprecated. diff --git a/.changeset/free-months-share.md b/.changeset/free-months-share.md new file mode 100644 index 0000000000..42e47a973d --- /dev/null +++ b/.changeset/free-months-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': minor +--- + +**BREAKING**: Removed the deprecated `FrontendFeature` type, import it from `@backstage/frontend-plugin-api` instead. diff --git a/.changeset/fruity-rockets-rhyme.md b/.changeset/fruity-rockets-rhyme.md new file mode 100644 index 0000000000..13e957c5f1 --- /dev/null +++ b/.changeset/fruity-rockets-rhyme.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': minor +--- + +**BREAKING**: Removed the deprecated `CreateAppFeatureLoader` and support for it in other APIs. Switch existing usage to use the newer `createFrontendFeatureLoader` from `@backstage/frontend-plugin-api` instead. diff --git a/.changeset/full-streets-take.md b/.changeset/full-streets-take.md new file mode 100644 index 0000000000..f04cd92c57 --- /dev/null +++ b/.changeset/full-streets-take.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app': minor +--- + +Updated the `app/nav` extension to use the new `NavContentBlueprint`, and removed support for extensions created with the now removed `NavLogoBlueprint`. diff --git a/.changeset/funny-brooms-trade.md b/.changeset/funny-brooms-trade.md new file mode 100644 index 0000000000..2a0b94bb13 --- /dev/null +++ b/.changeset/funny-brooms-trade.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: The separate `RouteResolutionApiResolveOptions` type has been removed. diff --git a/.changeset/funny-dancers-start.md b/.changeset/funny-dancers-start.md new file mode 100644 index 0000000000..267585f8b2 --- /dev/null +++ b/.changeset/funny-dancers-start.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Update styling of Tooltip element diff --git a/.changeset/fuzzy-ducks-jump.md b/.changeset/fuzzy-ducks-jump.md new file mode 100644 index 0000000000..1a4064dc91 --- /dev/null +++ b/.changeset/fuzzy-ducks-jump.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-compat-api': patch +--- + +The `compatWrapper` has been switched to use the new `SwappableComponentsApi` instead of the old `ComponentsApi` in its bridging to the old frontend system. diff --git a/.changeset/fuzzy-ducks-speak.md b/.changeset/fuzzy-ducks-speak.md new file mode 100644 index 0000000000..30e23c7ca5 --- /dev/null +++ b/.changeset/fuzzy-ducks-speak.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +The `description` property in `publish:gitlab:merge-request` has been made optional again to comply with the GitLab API. diff --git a/.changeset/gentle-cars-arrive.md b/.changeset/gentle-cars-arrive.md new file mode 100644 index 0000000000..d6fd765302 --- /dev/null +++ b/.changeset/gentle-cars-arrive.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-kubernetes-backend': minor +--- + +**BREAKING CHANGE**: Removed support for the legacy backend system. This means that the deprecated `createRouter` and `KubernetesBuilder` and related types have been removed. Please refer to the [relevant documentation](https://backstage.io/docs/features/kubernetes/installation/#adding-kubernetes-backend-plugin) to configure the Kubernetes plugin. + +**BREAKING CHANGE**: The deprecated types `AuthenticationStrategy`, `AuthMetadata`, `ClusterDetails`, `CustomResource`, `CustomResourcesByEntity`, `FetchResponseWrapper`, `KubernetesBuilder`, `KubernetesBuilderReturn`, `KubernetesClustersSupplier`, `KubernetesCredential`, `KubernetesEnvironment`, `KubernetesFetcher`, `KubernetesObjectsProvider`, `KubernetesObjectTypes`, `KubernetesServiceLocator`,`ObjectFetchParams`, `ObjectToFetch`,`RouterOptions` and `ServiceLocatorRequestContext` should all now be imported from `@backstage/plugin-kubernetes-node`. diff --git a/.changeset/good-islands-drive.md b/.changeset/good-islands-drive.md deleted file mode 100644 index 5b909dc84f..0000000000 --- a/.changeset/good-islands-drive.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Added `info` object to the context of the alpha CLI. diff --git a/.changeset/great-hounds-fix.md b/.changeset/great-hounds-fix.md new file mode 100644 index 0000000000..a7cc488ecb --- /dev/null +++ b/.changeset/great-hounds-fix.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Fixed fs:readdir action example diff --git a/.changeset/great-snakes-dress.md b/.changeset/great-snakes-dress.md new file mode 100644 index 0000000000..404bdab03c --- /dev/null +++ b/.changeset/great-snakes-dress.md @@ -0,0 +1,5 @@ +--- +'@backstage/theme': patch +--- + +We are introducing two new data attributes on the `body` to support Backstage UI (BUI) new theming system. diff --git a/.changeset/green-lies-invite copy.md b/.changeset/green-lies-invite copy.md new file mode 100644 index 0000000000..469ed2fbac --- /dev/null +++ b/.changeset/green-lies-invite copy.md @@ -0,0 +1,39 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: In an attempt to align some of the API's around providing components to `Blueprints`, we've renamed the parameters for both the `RouterBlueprint` and `AppRootWrapperBlueprint` from `Component` to `component`. + +```tsx +// old +RouterBlueprint.make({ + params: { + Component: ({ children }) =>
{children}
, + }, +}); + +// new +RouterBlueprint.make({ + params: { + component: ({ children }) =>
{children}
, + }, +}); +``` + +```tsx +// old +AppRootWrapperBlueprint.make({ + params: { + Component: ({ children }) =>
{children}
, + }, +}); + +// new +AppRootWrapperBlueprint.make({ + params: { + component: ({ children }) =>
{children}
, + }, +}); +``` + +As part of this change, the type for `component` has also changed from `ComponentType>` to `(props: { children: ReactNode }) => JSX.Element | null` which is not breaking, just a little more reflective of the actual expected component. diff --git a/.changeset/green-lies-invite.md b/.changeset/green-lies-invite.md new file mode 100644 index 0000000000..92ccc2324d --- /dev/null +++ b/.changeset/green-lies-invite.md @@ -0,0 +1,7 @@ +--- +'@backstage/frontend-test-utils': patch +'@backstage/core-compat-api': patch +'@backstage/plugin-app': patch +--- + +Updated the usage of the `RouterBlueprint` and `AppRootWrapperBlueprint` to use the lowercase `component` parameter diff --git a/.changeset/green-trainers-float.md b/.changeset/green-trainers-float.md deleted file mode 100644 index 537fe01736..0000000000 --- a/.changeset/green-trainers-float.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Fix EntityPicker field to render description as markdown, matching other form components in the system. diff --git a/.changeset/heavy-baths-rule.md b/.changeset/heavy-baths-rule.md deleted file mode 100644 index 09f26e8b57..0000000000 --- a/.changeset/heavy-baths-rule.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-github': minor ---- - -**BREAKING** The `GithubLocationAnalyzer` now requires the `AuthService` and the `CatalogService` when being constructed and the `TokenManger` has been removed. diff --git a/.changeset/heavy-onions-swim.md b/.changeset/heavy-onions-swim.md deleted file mode 100644 index 42ba056c1e..0000000000 --- a/.changeset/heavy-onions-swim.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': minor ---- - -Added `workspace:template` and `workspace:template:file` actions to complement respective `fetch:*` actions diff --git a/.changeset/honest-moons-rest.md b/.changeset/honest-moons-rest.md new file mode 100644 index 0000000000..d7540ea3e0 --- /dev/null +++ b/.changeset/honest-moons-rest.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +**Breaking change** Move breadcrumb to fit in the `HeaderPage` instead of the `Header` in Backstage UI. diff --git a/.changeset/honest-seas-repeat.md b/.changeset/honest-seas-repeat.md new file mode 100644 index 0000000000..99f1ceaeb2 --- /dev/null +++ b/.changeset/honest-seas-repeat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app': minor +--- + +**BREAKING**: The `app-root-element` extension now only accepts `JSX.Element` in its `element` param, meaning overrides need to be updated. diff --git a/.changeset/honest-snakes-draw.md b/.changeset/honest-snakes-draw.md new file mode 100644 index 0000000000..f7a5ffe830 --- /dev/null +++ b/.changeset/honest-snakes-draw.md @@ -0,0 +1,23 @@ +--- +'@backstage/cli': minor +--- + +**BREAKING**: The new app build based on [Rspack](https://rspack.dev/) is now the default, and the `EXPERIMENTAL_RSPACK` flag has been removed. To revert to the old behavior, set the `LEGACY_WEBPACK_BUILD` environment flag and install the following optional dependencies: + +```json +{ + "dependencies": { + "@module-federation/enhanced": "^0.9.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", + "esbuild-loader": "^4.0.0", + "eslint-webpack-plugin": "^4.2.0", + "fork-ts-checker-webpack-plugin": "^9.0.0", + "mini-css-extract-plugin": "^2.4.2", + "terser-webpack-plugin": "^5.1.3", + "webpack": "^5.96.0", + "webpack-dev-server": "^5.0.0" + } +} +``` + +If you do encounter a blocking issue that forces you to use the old WebPack build, please [open an issue](https://github.com/backstage/backstage/issues) explaining the problem. The WebPack build will be removed in a future release. diff --git a/.changeset/hot-clowns-behave.md b/.changeset/hot-clowns-behave.md new file mode 100644 index 0000000000..3c7e9dec12 --- /dev/null +++ b/.changeset/hot-clowns-behave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Adding `type` as an override to the `convertLegacyEntityCardExtension` diff --git a/.changeset/huge-heads-occur.md b/.changeset/huge-heads-occur.md new file mode 100644 index 0000000000..1972037cf0 --- /dev/null +++ b/.changeset/huge-heads-occur.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-azure': patch +--- + +`host` should be optional in `config` schema diff --git a/.changeset/huge-olives-do.md b/.changeset/huge-olives-do.md deleted file mode 100644 index 61eb37559f..0000000000 --- a/.changeset/huge-olives-do.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-events-backend-module-gitlab': patch ---- - -Adds support for `object_kind` field with priority over `event_name` on Gitlab webhook event types diff --git a/.changeset/huge-paws-design.md b/.changeset/huge-paws-design.md new file mode 100644 index 0000000000..8d90435480 --- /dev/null +++ b/.changeset/huge-paws-design.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +We are motion away from `motion` to use `gsap` instead to make Backstage UI backward compatible with React 17. diff --git a/.changeset/icy-mugs-glow.md b/.changeset/icy-mugs-glow.md deleted file mode 100644 index 553daff24a..0000000000 --- a/.changeset/icy-mugs-glow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-bitbucket-cloud-common': patch ---- - -Add support for `repo:updated` events as `Events.RepoUpdatedEvent`. diff --git a/.changeset/itchy-doodles-boil.md b/.changeset/itchy-doodles-boil.md new file mode 100644 index 0000000000..727a0ed01b --- /dev/null +++ b/.changeset/itchy-doodles-boil.md @@ -0,0 +1,12 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: The `AnyRoutes` and `AnyExternalRoutes` types have been removed and their usage has been inlined instead. + +Existing usage can be replaced according to their previous definitions: + +```ts +type AnyRoutes = { [name in string]: RouteRef | SubRouteRef }; +type AnyExternalRoutes = { [name in string]: ExternalRouteRef }; +``` diff --git a/.changeset/khaki-grapes-sink.md b/.changeset/khaki-grapes-sink.md deleted file mode 100644 index d96521e029..0000000000 --- a/.changeset/khaki-grapes-sink.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/canon': patch ---- - -Added new icon and onClear props to the TextField to make it easier to accessorize inputs. diff --git a/.changeset/large-experts-sort.md b/.changeset/large-experts-sort.md deleted file mode 100644 index 6d58836db7..0000000000 --- a/.changeset/large-experts-sort.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-events-backend-module-github': minor ---- - -**BREAKING**: Removed the `createGithubSignatureValidator` export. - -Added support webhook validation based on `integrations.github.[].apps.[].webhookSecret`. diff --git a/.changeset/large-lemons-clap.md b/.changeset/large-lemons-clap.md deleted file mode 100644 index cf1a3eed5d..0000000000 --- a/.changeset/large-lemons-clap.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/backend-plugin-api': patch -'@backstage/backend-defaults': patch -'@backstage/integration': patch ---- - -update documentation diff --git a/.changeset/late-squids-feel.md b/.changeset/late-squids-feel.md new file mode 100644 index 0000000000..f527152c8b --- /dev/null +++ b/.changeset/late-squids-feel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-proxy-backend': patch +--- + +correct rewrite rule to avoid extra subpath in proxy path diff --git a/.changeset/lazy-tires-show.md b/.changeset/lazy-tires-show.md deleted file mode 100644 index 3ccf5f155e..0000000000 --- a/.changeset/lazy-tires-show.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-permission-backend': minor -'@backstage/plugin-permission-common': minor -'@backstage/plugin-permission-node': minor ---- - -Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests. diff --git a/.changeset/lemon-ways-lay.md b/.changeset/lemon-ways-lay.md new file mode 100644 index 0000000000..725e57c141 --- /dev/null +++ b/.changeset/lemon-ways-lay.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Renaming the `getNodesByRoutePath` parameter from `sourcePath` to `routePath` diff --git a/.changeset/little-bugs-care.md b/.changeset/little-bugs-care.md new file mode 100644 index 0000000000..930cbd0e21 --- /dev/null +++ b/.changeset/little-bugs-care.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-client': minor +--- + +Added the analyze-location endpoint to the CatalogClient diff --git a/.changeset/long-grapes-glow.md b/.changeset/long-grapes-glow.md new file mode 100644 index 0000000000..e0e805ed87 --- /dev/null +++ b/.changeset/long-grapes-glow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-node': minor +--- + +Added the analyze-location endpoint to the CatalogService diff --git a/.changeset/lovely-cats-take.md b/.changeset/lovely-cats-take.md deleted file mode 100644 index ab1ee1c249..0000000000 --- a/.changeset/lovely-cats-take.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch ---- - -Support Bitbucket Cloud's `repo:updated` events at `BitbucketCloudEntityProvider`. - -To make use of the new event type, you have to configure your webhook or add a new ones -that delivers this event type to Backstage similar to `repo:push` before. - -Only `repo:updated` events that modify a repository's URL (e.g., due to a name change) -will cause changes (removing the "old", adding the "new" repository). diff --git a/.changeset/lovely-fans-write.md b/.changeset/lovely-fans-write.md new file mode 100644 index 0000000000..71b02f2bde --- /dev/null +++ b/.changeset/lovely-fans-write.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +differentiate between entirely and partially composite schemas in schema rendering diff --git a/.changeset/major-comics-stay.md b/.changeset/major-comics-stay.md new file mode 100644 index 0000000000..b8000bd0c5 --- /dev/null +++ b/.changeset/major-comics-stay.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-compat-api': minor +--- + +**BREAKING**: The `defaultPath` override of `convertLegacyPageExtension` has been renamed to `path`, in order to align with the same update that was made to the `PageBlueprint`. diff --git a/.changeset/mean-parents-build.md b/.changeset/mean-parents-build.md deleted file mode 100644 index d3bba29019..0000000000 --- a/.changeset/mean-parents-build.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/canon': patch ---- - -Pin version of @base-ui-components/react. diff --git a/.changeset/mighty-carrots-decide.md b/.changeset/mighty-carrots-decide.md deleted file mode 100644 index 68f210da48..0000000000 --- a/.changeset/mighty-carrots-decide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-react': minor ---- - -Allow search filters to provide labels and values separately, and not only values diff --git a/.changeset/mighty-cycles-stay.md b/.changeset/mighty-cycles-stay.md new file mode 100644 index 0000000000..cb1bb3646e --- /dev/null +++ b/.changeset/mighty-cycles-stay.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-scaffolder-react': patch +--- + +Export `FormField` type from `/alpha` in `-react` package, and internal refactor. diff --git a/.changeset/mira-looking-ostrich.md b/.changeset/mira-looking-ostrich.md new file mode 100644 index 0000000000..fb85b10d0d --- /dev/null +++ b/.changeset/mira-looking-ostrich.md @@ -0,0 +1,18 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Plugins should now use the new `AnalyticsImplementationBlueprint` to define and provide concrete analytics implementations. For example: + +```ts +import { AnalyticsImplementationBlueprint } from '@backstage/frontend-plugin-api'; + +const AcmeAnalytics = AnalyticsImplementationBlueprint.make({ + name: 'acme-analytics', + params: define => + define({ + deps: { config: configApiRef }, + factory: ({ config }) => AcmeAnalyticsImpl.fromConfig(config), + }), +}); +``` diff --git a/.changeset/moody-clowns-hear.md b/.changeset/moody-clowns-hear.md new file mode 100644 index 0000000000..d2f45e2d91 --- /dev/null +++ b/.changeset/moody-clowns-hear.md @@ -0,0 +1,13 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Make the processing hash calculation not care about the order of the processors. + +This change does not affect the behavior of the catalog, but it will make the processing +hash calculation more robust against changes in the order of processors. This should lead to +more stable processing hashes, which in turn should lead to fewer unnecessary reprocessing +of entities. + +After deploying this fix, you may see a period of increased processing and stitching, but +this should stabilize over time as the processing hashes become more consistent. diff --git a/.changeset/neat-glasses-occur.md b/.changeset/neat-glasses-occur.md deleted file mode 100644 index 5fea5b1886..0000000000 --- a/.changeset/neat-glasses-occur.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-import': patch ---- - -Add i18n support for `catalog-import` plugin. diff --git a/.changeset/neat-glasses-occured.md b/.changeset/neat-glasses-occured.md deleted file mode 100644 index 289e03e243..0000000000 --- a/.changeset/neat-glasses-occured.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-import': minor ---- - -**BREAKING**: `generateStepper` and `defaultGenerateStepper` now require a translation argument to be passed through for supporting translations. diff --git a/.changeset/neat-tigers-clap.md b/.changeset/neat-tigers-clap.md new file mode 100644 index 0000000000..93d114bb29 --- /dev/null +++ b/.changeset/neat-tigers-clap.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Fixes CSS to adjust based on whether or not the global Backstage sidebar is on the page. diff --git a/.changeset/nej-inte-ostrich.md b/.changeset/nej-inte-ostrich.md new file mode 100644 index 0000000000..434708e2e4 --- /dev/null +++ b/.changeset/nej-inte-ostrich.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app': patch +--- + +The default implementation of the Analytics API now collects and instantiates analytics implementations exposed via `AnalyticsImplementationBlueprint` extensions. If no such extensions are discovered, the API continues to do nothing with analytics events fired within Backstage. If multiple such extensions are discovered, every discovered implementation automatically receives analytics events. diff --git a/.changeset/new-hands-scream.md b/.changeset/new-hands-scream.md deleted file mode 100644 index 03291648db..0000000000 --- a/.changeset/new-hands-scream.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-auth-backend': minor ---- - -**BREAKING**: Removed support for the old backend system, and removed all deprecated exports. - -If you were using one of the deprecated imports from this package, you will have to follow the instructions in their respective deprecation notices before upgrading. Most of the general utilities are available from `@backstage/plugin-auth-node`, and the specific auth providers are available from dedicated packages such as for example `@backstage/plugin-auth-backend-module-github-provider`. See [the auth docs](https://backstage.io/docs/auth/) for specific instructions. diff --git a/.changeset/nice-actors-cheer.md b/.changeset/nice-actors-cheer.md new file mode 100644 index 0000000000..918148ffb8 --- /dev/null +++ b/.changeset/nice-actors-cheer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Adding a more sensible default order to the default filters diff --git a/.changeset/nice-buttons-return.md b/.changeset/nice-buttons-return.md new file mode 100644 index 0000000000..a47a3aa1a7 --- /dev/null +++ b/.changeset/nice-buttons-return.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': minor +--- + +Adds `additionalAllowedURIProtocols` to sanitizer config diff --git a/.changeset/nice-crabs-clean.md b/.changeset/nice-crabs-clean.md new file mode 100644 index 0000000000..45690e98aa --- /dev/null +++ b/.changeset/nice-crabs-clean.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Support multiple headers in new frontend system, and don't render a header until the entity has finished loading diff --git a/.changeset/nice-vans-vanish.md b/.changeset/nice-vans-vanish.md deleted file mode 100644 index c52fc42488..0000000000 --- a/.changeset/nice-vans-vanish.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/canon': minor ---- - -**Breaking Change** Icons on Button and IconButton now need to be imported and placed like this: - - -`} -/> - - - - Usage - Theming - - - -`} - /> - - - We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with - your brand. For additional flexibility, you can use the provided class names for each element listed below. - `} - /> - - - - -## API reference - - - -## Examples - -### Variants - -Here's a view when buttons have different variants. - -} - code={buttonVariants} -/> - -### Sizes - -Here's a view when buttons have different sizes. - -} - code={` - - -`} -/> - -### With Icons - -Here's a view when buttons have icons. - -} - code={` - - - -`} -/> - -### Full width - -Here's a view when buttons are full width. - -} - code={` - -`} -/> - -### Disabled - -Here's a view when buttons are disabled. - -} - code={``} -/> - -### Responsive - -Here's a view when buttons are responsive. - - - Responsive Button - `} -/> diff --git a/canon-docs/src/app/(docs)/components/button/props.ts b/canon-docs/src/app/(docs)/components/button/props.ts deleted file mode 100644 index 813dcaa6f0..0000000000 --- a/canon-docs/src/app/(docs)/components/button/props.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { classNamePropDefs, stylePropDefs } from '../../../../utils/propDefs'; -import type { PropDef } from '../../../../utils/propDefs'; - -export const buttonPropDefs: Record = { - variant: { - type: 'enum', - values: ['primary', 'secondary'], - default: 'primary', - responsive: true, - }, - size: { - type: 'enum', - values: ['small', 'medium'], - default: 'medium', - responsive: true, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/canon-docs/src/app/(docs)/components/checkbox/page.mdx b/canon-docs/src/app/(docs)/components/checkbox/page.mdx deleted file mode 100644 index 413636dc9a..0000000000 --- a/canon-docs/src/app/(docs)/components/checkbox/page.mdx +++ /dev/null @@ -1,66 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { CheckboxSnippet } from '@/snippets/stories-snippets'; -import { Snippet } from '@/components/Snippet'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { BaseUI } from '@/components/HeadlessBanners/BaseUI'; -import { checkboxPropDefs } from './props'; - -# Checkbox - -A checkbox component that can be used to trigger actions. - -} - code={``} -/> - - - - Usage - Theming - - - -`} - /> - - - We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with - your brand. For additional flexibility, you can use the provided class names for each element listed below. - `} - /> - - - - -## API reference - - - - - -## Examples - -### All variants - -Here's a view when checkboxes have different variants. - -} - code={` - - - - - `} -/> diff --git a/canon-docs/src/app/(docs)/components/collapsible/page.mdx b/canon-docs/src/app/(docs)/components/collapsible/page.mdx deleted file mode 100644 index e013dff89c..0000000000 --- a/canon-docs/src/app/(docs)/components/collapsible/page.mdx +++ /dev/null @@ -1,113 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { CollapsibleSnippet } from '@/snippets/stories-snippets'; -import { - collapsibleRootPropDefs, - collapsibleTriggerPropDefs, - collapsiblePanelPropDefs, -} from './props'; - -# Collapsible - -An avatar component with a fallback for initials. - -} - code={` - ( - - )} /> - - - It's the edge of the world and all of Western civilization - The sun may rise in the East, at least it settled in a final location - It's understood that Hollywood sells Californication - - -`} -/> - - - - Usage - Theming - - - - ( - - )} /> - Your content -`} - /> - - - We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with - your brand. For additional flexibility, you can use the provided class names for each element listed below. - - `canon-CollapsibleRoot` - - `canon-CollapsibleTrigger` - - `canon-CollapsiblePanel` - - - -## API reference - -### Collapsible.Root - -Groups all parts of the collapsible. Renders a `
` element. - - - -### Collapsible.Trigger - -The trigger by default render a simple unstyled button. Because menus can be rendered in different ways, we recommend -using the `render` prop to render a custom trigger. - - - )} /> - - - It's the edge of the world and all of Western civilization - The sun may rise in the East, at least it settled in a final location - It's understood that Hollywood sells Californication - - -`} -/> diff --git a/canon-docs/src/app/(docs)/components/collapsible/props.ts b/canon-docs/src/app/(docs)/components/collapsible/props.ts deleted file mode 100644 index 02c62dc89e..0000000000 --- a/canon-docs/src/app/(docs)/components/collapsible/props.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - renderPropDefs, -} from '@/utils/propDefs'; -import type { PropDef } from '@/utils/propDefs'; - -export const collapsibleRootPropDefs: Record = { - defaultOpen: { - type: 'boolean', - default: 'false', - }, - open: { - type: 'boolean', - }, - onOpenChange: { - type: 'enum', - values: ['(open) => void'], - }, - ...renderPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const collapsibleTriggerPropDefs: Record = { - ...renderPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const collapsiblePanelPropDefs: Record = { - hiddenUntilFound: { - type: 'boolean', - default: 'false', - }, - keepMounted: { - type: 'boolean', - default: 'false', - }, - ...renderPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/canon-docs/src/app/(docs)/components/container/page.mdx b/canon-docs/src/app/(docs)/components/container/page.mdx deleted file mode 100644 index 40f0321624..0000000000 --- a/canon-docs/src/app/(docs)/components/container/page.mdx +++ /dev/null @@ -1,62 +0,0 @@ -import { CodeBlock } from '@/components/CodeBlock'; -import { PropsTable } from '@/components/PropsTable'; -import { Tabs } from '@/components/Tabs'; -import { Snippet } from '@/components/Snippet'; -import { ContainerSnippet } from '@/snippets/stories-snippets'; -import { containerPropDefs } from './props'; - -# Container - -The container component let you use our default max-width and center the -content on the page. - -} - code={` - -`} -/> - - - - Usage - - - Hello World! -`} /> - - - -## API reference - - - -## Examples - -### Simple - -A simple example of how to use the Container component. - - - Hello World - Hello World - Hello World -`} -/> - -### Responsive padding & margin - -The Container component also supports responsive values, making it easy to -create responsive designs. - - - Hello World - Hello World - Hello World -`} -/> diff --git a/canon-docs/src/app/(docs)/components/container/props.ts b/canon-docs/src/app/(docs)/components/container/props.ts deleted file mode 100644 index 4a3410ec12..0000000000 --- a/canon-docs/src/app/(docs)/components/container/props.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - gapPropDefs, -} from '../../../../utils/propDefs'; -import type { PropDef } from '../../../../utils/propDefs'; - -export const containerPropDefs: Record = { - ...gapPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/canon-docs/src/app/(docs)/components/flex/page.mdx b/canon-docs/src/app/(docs)/components/flex/page.mdx deleted file mode 100644 index 8150f3e3c5..0000000000 --- a/canon-docs/src/app/(docs)/components/flex/page.mdx +++ /dev/null @@ -1,83 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { - flexFAQ1, - flexSimple, - flexResponsive, - flexAlign, -} from '@/snippets/code-snippets'; -import { Snippet } from '@/components/Snippet'; -import { FlexSnippet } from '@/snippets/stories-snippets'; -import { flexPropDefs } from './props'; -import { spacingPropDefs } from '../../../../utils/propDefs'; - -# Flex - -This is the flex container component. It will help to define the number of -columns that will be used in the grid. You can also define the gap between the -columns. All values are responsive. - -} - code={` - - - -`} -/> - - - - Usage - - - -`} - /> - - - -## API reference - - - -The grid component also accepts all the spacing props from the Box component. - - - -## Common questions - -### Can I stack horizontally? - -The Flex component only allows for stacking elements vertically. If you want -to create a column layout, please use the Grid component. - - - -## Examples - -### Simple - -A simple example of how to use the Flex component. - - - -### Responsive - -The Flex component also supports responsive values, making it easy to create -responsive designs. - - - -### Align - -The Flex component also supports responsive alignment, making it easy to -create responsive designs. - - diff --git a/canon-docs/src/app/(docs)/components/flex/props.ts b/canon-docs/src/app/(docs)/components/flex/props.ts deleted file mode 100644 index 2268a6909d..0000000000 --- a/canon-docs/src/app/(docs)/components/flex/props.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { - PropDef, - childrenPropDefs, - classNamePropDefs, - stylePropDefs, -} from '../../../../utils/propDefs'; - -export const flexPropDefs: Record = { - align: { - type: 'enum', - values: ['start', 'center', 'end', 'baseline', 'stretch'], - responsive: true, - }, - direction: { - type: 'enum', - values: ['row', 'column', 'row-reverse', 'column-reverse'], - responsive: true, - }, - justify: { - type: 'enum', - values: ['start', 'center', 'end', 'between'], - responsive: true, - }, - ...childrenPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/canon-docs/src/app/(docs)/components/grid/props.ts b/canon-docs/src/app/(docs)/components/grid/props.ts deleted file mode 100644 index a9e8717fa1..0000000000 --- a/canon-docs/src/app/(docs)/components/grid/props.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { - PropDef, - childrenPropDefs, - classNamePropDefs, - stylePropDefs, -} from '../../../../utils/propDefs'; - -const columnsValues = [ - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - '10', - '11', - '12', -]; - -export const gridPropDefs: Record = { - columns: { - type: 'enum | string', - values: [...columnsValues, 'auto'], - responsive: true, - default: 'auto', - }, - ...childrenPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const gridItemPropDefs: Record = { - colSpan: { - type: 'enum | string', - values: [...columnsValues, 'full'], - responsive: true, - }, - rowSpan: { - type: 'enum | string', - values: [...columnsValues, 'full'], - responsive: true, - }, - start: { - type: 'enum | string', - values: [...columnsValues, 'auto'], - responsive: true, - }, - end: { - type: 'enum | string', - values: [...columnsValues, 'auto'], - responsive: true, - }, - ...childrenPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/canon-docs/src/app/(docs)/components/heading/page.mdx b/canon-docs/src/app/(docs)/components/heading/page.mdx deleted file mode 100644 index 10fd60bef1..0000000000 --- a/canon-docs/src/app/(docs)/components/heading/page.mdx +++ /dev/null @@ -1,74 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { HeadingSnippet } from '@/snippets/stories-snippets'; -import { Snippet } from '@/components/Snippet'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { headingPropDefs } from './props'; - -# Heading - -Headings are used to structure the content of your page. - -} - code={`Hello World!`} -/> - - - - Usage - Theming - - - -`} - /> - - - We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with - your brand. For additional flexibility, you can use the provided class names for each element listed below. - `} - /> - - - - -## API reference - - - -## Examples - -### All variants - -The `Heading` component has a `variant` prop that can be used to change the -appearance of the heading. - -} - code={` - Display - Title 1 - Title 2 - Title 3 - Title 4 -`} -/> - -### Responsive - -You can also use the `variant` prop to change the appearance of the text based -on the screen size. - - - Responsive heading - `} -/> diff --git a/canon-docs/src/app/(docs)/components/heading/props.ts b/canon-docs/src/app/(docs)/components/heading/props.ts deleted file mode 100644 index a3dd3288ff..0000000000 --- a/canon-docs/src/app/(docs)/components/heading/props.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { classNamePropDefs, stylePropDefs } from '../../../../utils/propDefs'; -import type { PropDef } from '../../../../utils/propDefs'; - -export const headingPropDefs: Record = { - variant: { - type: 'enum', - values: ['display', 'title1', 'title2', 'title3', 'title4', 'title5'], - responsive: true, - }, - children: { - type: 'enum', - values: ['ReactNode'], - responsive: false, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/canon-docs/src/app/(docs)/components/icon-button/page.mdx b/canon-docs/src/app/(docs)/components/icon-button/page.mdx deleted file mode 100644 index 60530ec9ea..0000000000 --- a/canon-docs/src/app/(docs)/components/icon-button/page.mdx +++ /dev/null @@ -1,97 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { IconButtonSnippet } from '@/snippets/stories-snippets'; -import { iconButtonVariants } from '@/snippets/code-snippets'; -import { iconButtonPropDefs } from './props'; - -# Icon Button - -A button component with a single icon that can be used to trigger actions. - -} - code={` - - - `} -/> - - - - Usage - Theming - - - -`} - /> - - - We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with - your brand. For additional flexibility, you can use the provided class names for each element listed below. - `} - /> - - - - -## API reference - - - -## Examples - -### Variants - -Here's a view when buttons have different variants. - -} - code={iconButtonVariants} -/> - -### Sizes - -Here's a view when buttons have different sizes. - -} - code={` - - -`} -/> - -### Disabled - -Here's a view when buttons are disabled. - -} - code={``} -/> - -### Responsive - -Here's a view when buttons are responsive. - -`} -/> diff --git a/canon-docs/src/app/(docs)/components/icon-button/props.ts b/canon-docs/src/app/(docs)/components/icon-button/props.ts deleted file mode 100644 index bcb1580247..0000000000 --- a/canon-docs/src/app/(docs)/components/icon-button/props.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { classNamePropDefs, stylePropDefs } from '../../../../utils/propDefs'; -import type { PropDef } from '../../../../utils/propDefs'; - -export const iconButtonPropDefs: Record = { - variant: { - type: 'enum', - values: ['primary', 'secondary'], - default: 'primary', - responsive: true, - }, - size: { - type: 'enum', - values: ['small', 'medium'], - default: 'medium', - responsive: true, - }, - icon: { type: 'enum', values: 'icon', responsive: false }, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/canon-docs/src/app/(docs)/components/icon/page.mdx b/canon-docs/src/app/(docs)/components/icon/page.mdx deleted file mode 100644 index 66be460d13..0000000000 --- a/canon-docs/src/app/(docs)/components/icon/page.mdx +++ /dev/null @@ -1,44 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { IconSnippet } from '@/snippets/stories-snippets'; -import { Snippet } from '@/components/Snippet'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { iconPropDefs } from './props'; - -# Icon - -Icons are used to represent an action or a state. - -} - code={``} -/> - - - - Usage - Theming - - - -`} - /> - - - We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with - your brand. For additional flexibility, you can use the provided class names for each element listed below. - `} - /> - - - - -## API reference - - diff --git a/canon-docs/src/app/(docs)/components/link/page.mdx b/canon-docs/src/app/(docs)/components/link/page.mdx deleted file mode 100644 index 64a3261590..0000000000 --- a/canon-docs/src/app/(docs)/components/link/page.mdx +++ /dev/null @@ -1,84 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { - MenuSnippet, - ButtonSnippet, - LinkSnippet, -} from '@/snippets/stories-snippets'; -import { linkPropDefs } from './props'; - -# Link - -A link component that renders a `` element. - -} - code={`Sign up for Backstage`} -/> - - - - Usage - Theming - - - Sign up for Backstage`} - /> - - - We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with - your brand. For additional flexibility, you can use the provided class names for each element listed below. - - `canon-Link` - - `canon-Link[data-variant='body']` - - `canon-Link[data-variant='subtitle']` - - `canon-Link[data-variant='caption']` - - `canon-Link[data-variant='label']` - - `canon-Link[data-weight='regular']` - - `canon-Link[data-weight='bold']` - - - -## API reference - - - -## Examples - -### Variants - -Here's a view when links have different variants. - -} - code={` - - - - - `} -/> - -### Weights - -Here's a view when links have different weights. - -} - code={` - - - `} -/> diff --git a/canon-docs/src/app/(docs)/components/link/props.ts b/canon-docs/src/app/(docs)/components/link/props.ts deleted file mode 100644 index 83507c5594..0000000000 --- a/canon-docs/src/app/(docs)/components/link/props.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs'; -import type { PropDef } from '@/utils/propDefs'; - -export const linkPropDefs: Record = { - to: { - type: 'string', - }, - variant: { - type: 'enum', - values: ['subtitle', 'body', 'caption', 'label'], - default: 'body', - responsive: true, - }, - weight: { - type: 'enum', - values: ['regular', 'bold'], - default: 'regular', - responsive: true, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/canon-docs/src/app/(docs)/components/menu/page.mdx b/canon-docs/src/app/(docs)/components/menu/page.mdx deleted file mode 100644 index 98c4949438..0000000000 --- a/canon-docs/src/app/(docs)/components/menu/page.mdx +++ /dev/null @@ -1,157 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { MenuSnippet } from '@/snippets/stories-snippets'; -import { BaseUI } from '@/components/HeadlessBanners/BaseUI'; -import { - menuRootPropDefs, - menuTriggerPropDefs, - menuPositionerPropDefs, - menuItemPropDefs, -} from './props'; - -# Menu - -A list of actions in a dropdown, enhanced with keyboard navigation. - -} - code={` - ( - - )} - /> - - - - Settings - Invite new members - Download app - Log out - - - - `} -/> - - - - Usage - Theming - - - - - - - - - - - - `} - /> - - - We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with - your brand. For additional flexibility, you can use the provided class names for each element listed below. - - `canon-MenuTrigger` - - `canon-MenuBackdrop` - - `canon-MenuPositioner` - - `canon-MenuPopup` - - `canon-MenuArrow` - - `canon-MenuItem` - - `canon-MenuGroup` - - `canon-MenuGroupLabel` - - `canon-MenuRadioGroup` - - `canon-MenuRadioItem` - - `canon-MenuRadioItemIndicator` - - `canon-MenuCheckboxItem` - - `canon-MenuCheckboxItemIndicator` - - `canon-MenuSubmenuTrigger` - - `canon-MenuSeparator` - - - -## API reference - - - -### Menu.Root - -Groups all parts of the menu. Doesn't render its own HTML element. - - - -### Menu.Trigger - -The trigger by default render a simple unstyled button. Because menus can be rendered in different ways, we recommend -using the `render` prop to render a custom trigger. - - - )} - /> - - - - Settings - Invite new members - Download app - Log out - - - - `} -/> diff --git a/canon-docs/src/app/(docs)/components/select/page.mdx b/canon-docs/src/app/(docs)/components/select/page.mdx deleted file mode 100644 index 4399f62235..0000000000 --- a/canon-docs/src/app/(docs)/components/select/page.mdx +++ /dev/null @@ -1,128 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { SelectSnippet } from '@/snippets/stories-snippets'; -import { selectPropDefs } from './props'; - -# Select - -A common form component for choosing a predefined value in a dropdown menu. - -} - code={``} -/> - -### Sizes - -Here's a view when the selects have different sizes. - -} - code={` - - `} -/> - -### Disabled - -Here's a view when the select is disabled. - -} - code={` + + + + {themes.map(({ name, value }) => ( + + {name} + + ))} + + + + ); +}; diff --git a/canon-docs/src/components/Toolbar/theme.module.css b/docs-ui/src/components/Toolbar/theme.module.css similarity index 71% rename from canon-docs/src/components/Toolbar/theme.module.css rename to docs-ui/src/components/Toolbar/theme.module.css index 37ccb25249..c5490a72d7 100644 --- a/canon-docs/src/components/Toolbar/theme.module.css +++ b/docs-ui/src/components/Toolbar/theme.module.css @@ -1,22 +1,17 @@ .tabs { border-radius: 0.375rem; width: 100%; - background-color: var(--bg); - transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; } .tabsTheme { width: 100px; border-radius: 0.375rem; - background-color: var(--bg); - transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; } .list { display: flex; position: relative; z-index: 0; - padding-inline: 0.25rem; gap: 0.25rem; } @@ -31,9 +26,10 @@ appearance: none; color: var(--secondary); user-select: none; - height: 2rem; flex: 1; cursor: pointer; + width: 3rem; + height: 3rem; &[data-selected] { color: var(--primary); @@ -57,7 +53,7 @@ position: absolute; inset: 0.25rem 0; border-radius: 0.25rem; - outline: 2px solid var(--panel); + outline: 2px solid var(--action); outline-offset: -1px; } } @@ -74,10 +70,10 @@ top: 50%; translate: var(--active-tab-left) -50%; width: var(--active-tab-width); - height: 1.5rem; - border-radius: 0.25rem; - background-color: var(--panel); - transition-property: translate, width, background-color; + height: 3rem; + border-radius: 3rem; + background-color: var(--action); + transition-property: translate, width; transition-duration: 200ms; transition-timing-function: ease-in-out; } diff --git a/canon-docs/src/components/Toolbar/theme.tsx b/docs-ui/src/components/Toolbar/theme.tsx similarity index 94% rename from canon-docs/src/components/Toolbar/theme.tsx rename to docs-ui/src/components/Toolbar/theme.tsx index 94224048ea..ef3d842056 100644 --- a/canon-docs/src/components/Toolbar/theme.tsx +++ b/docs-ui/src/components/Toolbar/theme.tsx @@ -1,7 +1,7 @@ 'use client'; import { Tabs } from '@base-ui-components/react/tabs'; -import { Icon } from '@backstage/canon'; +import { Icon } from '@backstage/ui'; import { usePlayground } from '@/utils/playground-context'; import styles from './theme.module.css'; diff --git a/docs-ui/src/content/components/avatar.mdx b/docs-ui/src/content/components/avatar.mdx new file mode 100644 index 0000000000..be1fb070d1 --- /dev/null +++ b/docs-ui/src/content/components/avatar.mdx @@ -0,0 +1,63 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { AvatarSnippet } from '@/snippets/stories-snippets'; +import { + avatarPropDefs, + snippetUsage, + snippetSizes, + snippetFallback, +} from './avatar.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={``} +/> + +## Usage + + + +## API reference + + + +## Examples + +### Sizes + +Avatar sizes can be set using the `size` prop. + +} + code={snippetSizes} +/> + +### Fallback + +If the image is not available, the avatar will show the initials of the name. + +} + code={snippetFallback} +/> + + + + diff --git a/docs-ui/src/content/components/avatar.props.ts b/docs-ui/src/content/components/avatar.props.ts new file mode 100644 index 0000000000..8cc53eeff3 --- /dev/null +++ b/docs-ui/src/content/components/avatar.props.ts @@ -0,0 +1,46 @@ +import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs'; +import type { PropDef } from '@/utils/propDefs'; + +export const avatarPropDefs: Record = { + src: { + type: 'string', + }, + name: { + type: 'string', + }, + size: { + type: 'enum', + values: ['small', 'medium', 'large'], + default: 'medium', + responsive: true, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const snippetUsage = `import { Avatar } from '@backstage/ui'; + +`; + +export const snippetSizes = ` + + + +`; + +export const snippetFallback = ``; diff --git a/canon-docs/src/app/(docs)/components/box/page.mdx b/docs-ui/src/content/components/box.mdx similarity index 53% rename from canon-docs/src/app/(docs)/components/box/page.mdx rename to docs-ui/src/content/components/box.mdx index 25ce3b6cd8..84fca48349 100644 --- a/canon-docs/src/app/(docs)/components/box/page.mdx +++ b/docs-ui/src/content/components/box.mdx @@ -1,34 +1,35 @@ import { CodeBlock } from '@/components/CodeBlock'; import { PropsTable } from '@/components/PropsTable'; -import { Tabs } from '@/components/Tabs'; import { Snippet } from '@/components/Snippet'; import { BoxSnippet } from '@/snippets/stories-snippets'; -import { boxPropDefs } from './props'; -import { spacingPropDefs } from '../../../../utils/propDefs'; +import { + boxPropDefs, + snippetUsage, + boxPreviewSnippet, + boxSimpleSnippet, + boxResponsiveSnippet, +} from './box.props'; +import { spacingPropDefs } from '@/utils/propDefs'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Box - -Box is the lowest-level component in Canon. It provides a consistent API for styling and layout. + } - code={` - -`} + code={boxPreviewSnippet} align="center" /> - - - Usage - - - `} /> - - + ## API reference @@ -51,16 +52,14 @@ avoid collapsing margins but both are available. A simple example of how to use the Box component. -Hello World`} /> + ### Responsive Here's a view when buttons are responsive. - - Hello World - `} -/> + + + + + diff --git a/docs-ui/src/content/components/box.props.ts b/docs-ui/src/content/components/box.props.ts new file mode 100644 index 0000000000..0ee45c80f4 --- /dev/null +++ b/docs-ui/src/content/components/box.props.ts @@ -0,0 +1,40 @@ +import { + classNamePropDefs, + displayPropDefs, + heightPropDefs, + positionPropDefs, + stylePropDefs, + widthPropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const boxPropDefs: Record = { + as: { + type: 'enum', + values: ['div', 'span'], + default: 'div', + responsive: true, + }, + ...widthPropDefs, + ...heightPropDefs, + ...positionPropDefs, + ...displayPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const snippetUsage = `import { Box } from '@backstage/ui'; + +`; + +export const boxPreviewSnippet = ` + +`; + +export const boxSimpleSnippet = `Hello World`; + +export const boxResponsiveSnippet = ` + Hello World +`; diff --git a/docs-ui/src/content/components/button-icon.mdx b/docs-ui/src/content/components/button-icon.mdx new file mode 100644 index 0000000000..e1f27e0601 --- /dev/null +++ b/docs-ui/src/content/components/button-icon.mdx @@ -0,0 +1,85 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ButtonIconSnippet } from '@/snippets/stories-snippets'; +import { + buttonIconPropDefs, + buttonIconUsageSnippet, + buttonIconDefaultSnippet, + buttonIconVariantsSnippet, + buttonIconSizesSnippet, + buttonIconDisabledSnippet, + buttonIconResponsiveSnippet, + buttonIconAsLinkSnippet, +} from './button-icon.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={buttonIconDefaultSnippet} +/> + +## Usage + + + +## API reference + + + +## Examples + +### Variants + +Here's a view when buttons have different variants. + +} + code={buttonIconVariantsSnippet} +/> + +### Sizes + +Here's a view when buttons have different sizes. + +} + code={buttonIconSizesSnippet} +/> + +### Disabled + +Here's a view when buttons are disabled. + +} + code={buttonIconDisabledSnippet} +/> + +### Responsive + +Here's a view when buttons are responsive. + + + + + + diff --git a/docs-ui/src/content/components/button-icon.props.ts b/docs-ui/src/content/components/button-icon.props.ts new file mode 100644 index 0000000000..2ba7372a9c --- /dev/null +++ b/docs-ui/src/content/components/button-icon.props.ts @@ -0,0 +1,59 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const buttonIconPropDefs: Record = { + variant: { + type: 'enum', + values: ['primary', 'secondary'], + default: 'primary', + responsive: true, + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'medium', + responsive: true, + }, + icon: { type: 'enum', values: ['ReactNode'], responsive: false }, + isDisabled: { type: 'boolean', default: 'false', responsive: false }, + type: { + type: 'enum', + values: ['button', 'submit', 'reset'], + default: 'button', + responsive: false, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const buttonIconUsageSnippet = `import { ButtonIcon } from '@backstage/ui'; + +`; + +export const buttonIconDefaultSnippet = ` + } variant="primary" /> + } variant="secondary" /> +`; + +export const buttonIconVariantsSnippet = ` + } variant="primary" /> + } variant="secondary" /> +`; + +export const buttonIconSizesSnippet = ` + } size="small" /> + } size="medium" /> +`; + +export const buttonIconDisabledSnippet = `} isDisabled />`; + +export const buttonIconResponsiveSnippet = `} variant={{ initial: 'primary', lg: 'secondary' }} />`; + +export const buttonIconAsLinkSnippet = `import { ButtonLink } from '@backstage/ui'; + + + Button +`; diff --git a/docs-ui/src/content/components/button-link.mdx b/docs-ui/src/content/components/button-link.mdx new file mode 100644 index 0000000000..5ce538838d --- /dev/null +++ b/docs-ui/src/content/components/button-link.mdx @@ -0,0 +1,96 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ButtonLinkSnippet } from '@/snippets/stories-snippets'; +import { + buttonLinkPropDefs, + buttonLinkSnippetUsage, + buttonLinkVariantsSnippet, + buttonLinkSizesSnippet, + buttonLinkIconsSnippet, + buttonLinkDisabledSnippet, + buttonLinkResponsiveSnippet, +} from './button-link.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={buttonLinkVariantsSnippet} +/> + +## Usage + + + +## API reference + + + +## Examples + +### Variants + +Here's a view when buttons have different variants. + +} + code={buttonLinkVariantsSnippet} +/> + +### Sizes + +Here's a view when buttons have different sizes. + +} + code={buttonLinkSizesSnippet} +/> + +### With Icons + +Here's a view when buttons have icons. + +} + code={buttonLinkIconsSnippet} +/> + +### Disabled + +Here's a view when buttons are disabled. + +} + code={buttonLinkDisabledSnippet} +/> + +### Responsive + +Here's a view when buttons are responsive. + + + + + + diff --git a/docs-ui/src/content/components/button-link.props.ts b/docs-ui/src/content/components/button-link.props.ts new file mode 100644 index 0000000000..758d6fd811 --- /dev/null +++ b/docs-ui/src/content/components/button-link.props.ts @@ -0,0 +1,73 @@ +import { classNamePropDefs, stylePropDefs } from '../../utils/propDefs'; +import type { PropDef } from '../../utils/propDefs'; + +export const buttonLinkPropDefs: Record = { + variant: { + type: 'enum', + values: ['primary', 'secondary'], + default: 'primary', + responsive: true, + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'medium', + responsive: true, + }, + iconStart: { type: 'enum', values: ['ReactNode'], responsive: false }, + iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false }, + isDisabled: { type: 'boolean', default: 'false', responsive: false }, + href: { type: 'string', responsive: false }, + hrefLang: { type: 'string', responsive: false }, + target: { + type: 'enum', + values: ['HTMLAttributeAnchorTarget'], + default: '_self', + responsive: false, + }, + rel: { type: 'string', responsive: false }, + children: { type: 'enum', values: ['ReactNode'], responsive: false }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const buttonLinkSnippetUsage = `import { ButtonLink } from '@backstage/ui'; + +`; + +export const buttonLinkVariantsSnippet = ` + } variant="primary"> + Button + + } variant="secondary"> + Button + +`; + +export const buttonLinkSizesSnippet = ` + Small + Medium +`; + +export const buttonLinkIconsSnippet = ` + }>Button + }>Button + } + iconEnd={}> + Button + +`; + +export const buttonLinkDisabledSnippet = ` + + Primary + + + Secondary + +`; + +export const buttonLinkResponsiveSnippet = ` + Responsive Button +`; diff --git a/docs-ui/src/content/components/button.mdx b/docs-ui/src/content/components/button.mdx new file mode 100644 index 0000000000..a25115012b --- /dev/null +++ b/docs-ui/src/content/components/button.mdx @@ -0,0 +1,109 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ButtonSnippet, ButtonLinkSnippet } from '@/snippets/stories-snippets'; +import { + buttonPropDefs, + buttonSnippetUsage, + buttonVariantsSnippet, + buttonSizesSnippet, + buttonIconsSnippet, + buttonDisabledSnippet, + buttonResponsiveSnippet, + buttonAsLinkSnippet, +} from './button.props'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; + + + +} + code={buttonVariantsSnippet} +/> + +## Usage + + + +## API reference + + + +## Examples + +### Variants + +Here's a view when buttons have different variants. + +} + code={buttonVariantsSnippet} +/> + +### Sizes + +Here's a view when buttons have different sizes. + +} + code={buttonSizesSnippet} +/> + +### With Icons + +Here's a view when buttons have icons. + +} + code={buttonIconsSnippet} +/> + +### Disabled + +Here's a view when buttons are disabled. + +} + code={buttonDisabledSnippet} +/> + +### Responsive + +Here's a view when buttons are responsive. + + + +### As Link + +If you want to use a button as a link, please use the `ButtonLink` component. + +} + code={buttonAsLinkSnippet} +/> + + + + diff --git a/docs-ui/src/content/components/button.props.ts b/docs-ui/src/content/components/button.props.ts new file mode 100644 index 0000000000..4647c20956 --- /dev/null +++ b/docs-ui/src/content/components/button.props.ts @@ -0,0 +1,72 @@ +import { classNamePropDefs, stylePropDefs } from '../../utils/propDefs'; +import type { PropDef } from '../../utils/propDefs'; + +export const buttonPropDefs: Record = { + variant: { + type: 'enum', + values: ['primary', 'secondary'], + default: 'primary', + responsive: true, + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'medium', + responsive: true, + }, + iconStart: { type: 'enum', values: ['ReactNode'], responsive: false }, + iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false }, + isDisabled: { type: 'boolean', default: 'false', responsive: false }, + children: { type: 'enum', values: ['ReactNode'], responsive: false }, + type: { + type: 'enum', + values: ['button', 'submit', 'reset'], + default: 'button', + responsive: false, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const buttonSnippetUsage = `import { Button } from '@backstage/ui'; + + + +`; + +export const buttonSizesSnippet = ` + + +`; + +export const buttonIconsSnippet = ` + + + +`; + +export const buttonDisabledSnippet = ` + + +`; + +export const buttonResponsiveSnippet = ``; + +export const buttonAsLinkSnippet = `import { ButtonLink } from '@backstage/ui'; + + + Button +`; diff --git a/docs-ui/src/content/components/card.mdx b/docs-ui/src/content/components/card.mdx new file mode 100644 index 0000000000..06c3a5bc2e --- /dev/null +++ b/docs-ui/src/content/components/card.mdx @@ -0,0 +1,89 @@ +import { PropsTable } from '@/components/PropsTable'; +import { CardSnippet } from '@/snippets/stories-snippets'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { + cardPropDefs, + cardUsageSnippet, + cardDefaultSnippet, + cardHeaderPropDefs, + cardBodyPropDefs, + cardFooterPropDefs, + cardLongBodySnippet, + cardListRowSnippet, +} from './card.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={cardDefaultSnippet} +/> + +## Usage + + + +## API reference + +### Card + +A card component that can be used to display content in a box. + + + +### CardHeader + +To display a header in a card, use the `CardHeader` component. This will be fixed at the top of the card. + + + +### CardBody + +To display content in a card, use the `CardBody` component. This will automatically fill the card. + + + +### CardFooter + +To display a footer in a card, use the `CardFooter` component. This will be fixed at the bottom of the card. + + + +## Examples + +### With long body + +Here's a view when card has a long body. + +} + code={cardLongBodySnippet} + open +/> + +### With list + +Here's a view when card has a list. + +} + code={cardListRowSnippet} + open +/> + + + + diff --git a/docs-ui/src/content/components/card.props.ts b/docs-ui/src/content/components/card.props.ts new file mode 100644 index 0000000000..70c71bdf56 --- /dev/null +++ b/docs-ui/src/content/components/card.props.ts @@ -0,0 +1,83 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const cardPropDefs: Record = { + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const cardHeaderPropDefs: Record = { + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const cardBodyPropDefs: Record = { + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const cardFooterPropDefs: Record = { + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const cardUsageSnippet = `import { card } from '@backstage/ui'; + + + Header + Body + Footer +`; + +export const cardDefaultSnippet = ` + Header + Body + Footer +`; + +export const cardLongBodySnippet = ` + + Header + + + + This is the first paragraph of a long body text that demonstrates how + the Card component handles extensive content. The card should adjust + accordingly to display all the text properly while maintaining its + structure. + + + Here's a second paragraph that adds more content to our card body. + Having multiple paragraphs helps to visualize how spacing works within + the card component. + + + This third paragraph continues to add more text to ensure we have a + proper demonstration of a card with significant content. This makes it + easier to test scrolling behavior and overall layout when content + exceeds the initial view. + + + + Footer + +`; + +export const cardListRowSnippet = ` + + Header + + + Hello world + Hello world + Hello world + Hello world + ... + + + Footer + +`; diff --git a/docs-ui/src/content/components/checkbox.mdx b/docs-ui/src/content/components/checkbox.mdx new file mode 100644 index 0000000000..5770396ebb --- /dev/null +++ b/docs-ui/src/content/components/checkbox.mdx @@ -0,0 +1,51 @@ +import { PropsTable } from '@/components/PropsTable'; +import { CheckboxSnippet } from '@/snippets/stories-snippets'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { + checkboxPropDefs, + checkboxUsageSnippet, + checkboxDefaultSnippet, + checkboxVariantsSnippet, +} from './checkbox.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={checkboxDefaultSnippet} +/> + +## Usage + + + +## API reference + + + +## Examples + +### All variants + +Here's a view when checkboxes have different variants. + +} + code={checkboxVariantsSnippet} +/> + + + + diff --git a/canon-docs/src/app/(docs)/components/checkbox/props.ts b/docs-ui/src/content/components/checkbox.props.ts similarity index 62% rename from canon-docs/src/app/(docs)/components/checkbox/props.ts rename to docs-ui/src/content/components/checkbox.props.ts index 8abbac1ec9..c7ee69280e 100644 --- a/canon-docs/src/app/(docs)/components/checkbox/props.ts +++ b/docs-ui/src/content/components/checkbox.props.ts @@ -1,5 +1,8 @@ -import { classNamePropDefs, stylePropDefs } from '../../../../utils/propDefs'; -import type { PropDef } from '../../../../utils/propDefs'; +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; export const checkboxPropDefs: Record = { label: { @@ -42,3 +45,16 @@ export const checkboxPropDefs: Record = { ...classNamePropDefs, ...stylePropDefs, }; + +export const checkboxUsageSnippet = `import { Checkbox } from '@backstage/ui'; + +`; + +export const checkboxDefaultSnippet = ``; + +export const checkboxVariantsSnippet = ` + + + + +`; diff --git a/docs-ui/src/content/components/collapsible.mdx b/docs-ui/src/content/components/collapsible.mdx new file mode 100644 index 0000000000..948da323fe --- /dev/null +++ b/docs-ui/src/content/components/collapsible.mdx @@ -0,0 +1,72 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { CollapsibleSnippet } from '@/snippets/stories-snippets'; +import { + collapsibleRootPropDefs, + collapsibleTriggerPropDefs, + collapsiblePanelPropDefs, + collapsibleUsageSnippet, + collapsibleDefaultSnippet, + collapsibleTriggerSnippet, + collapsibleOpenSnippet, +} from './collapsible.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={collapsibleDefaultSnippet} +/> + +## Usage + + + +## API reference + +### Collapsible.Root + +Groups all parts of the collapsible. Renders a `
` element. + + + +### Collapsible.Trigger + +The trigger by default render a simple unstyled button. Because menus can be rendered in different ways, we recommend +using the `render` prop to render a custom trigger. + + + + + +### Collapsible.Panel + +A panel with the collapsible contents. Renders a `
` element. + + + +## Examples + +Open the panel by default by setting the `defaultOpen` prop to `true`. + +} + code={collapsibleOpenSnippet} +/> + + + + diff --git a/docs-ui/src/content/components/collapsible.props.ts b/docs-ui/src/content/components/collapsible.props.ts new file mode 100644 index 0000000000..55e80f6689 --- /dev/null +++ b/docs-ui/src/content/components/collapsible.props.ts @@ -0,0 +1,86 @@ +import { + classNamePropDefs, + stylePropDefs, + renderPropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const collapsibleRootPropDefs: Record = { + defaultOpen: { + type: 'boolean', + default: 'false', + }, + open: { + type: 'boolean', + }, + onOpenChange: { + type: 'enum', + values: ['(open) => void'], + }, + ...renderPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const collapsibleTriggerPropDefs: Record = { + ...renderPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const collapsiblePanelPropDefs: Record = { + hiddenUntilFound: { + type: 'boolean', + default: 'false', + }, + keepMounted: { + type: 'boolean', + default: 'false', + }, + ...renderPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const collapsibleUsageSnippet = `import { Collapsible } from '@backstage/ui'; + + + ( + + )} /> + Your content +`; + +export const collapsibleDefaultSnippet = ` + ( + + )} /> + + + It's the edge of the world and all of Western civilization + The sun may rise in the East, at least it settled in a final location + It's understood that Hollywood sells Californication + + +`; + +export const collapsibleTriggerSnippet = ` + )} /> + + + It's the edge of the world and all of Western civilization + The sun may rise in the East, at least it settled in a final location + It's understood that Hollywood sells Californication + + +`; diff --git a/docs-ui/src/content/components/container.mdx b/docs-ui/src/content/components/container.mdx new file mode 100644 index 0000000000..05615cb1c2 --- /dev/null +++ b/docs-ui/src/content/components/container.mdx @@ -0,0 +1,53 @@ +import { CodeBlock } from '@/components/CodeBlock'; +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { ContainerSnippet } from '@/snippets/stories-snippets'; +import { + containerPropDefs, + containerUsageSnippet, + containerDefaultSnippet, + containerSimpleSnippet, + containerResponsiveSnippet, +} from './container.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={containerDefaultSnippet} +/> + +## Usage + + + +## API reference + + + +## Examples + +### Simple + +A simple example of how to use the Container component. + + + +### Responsive padding & margin + +The Container component also supports responsive values, making it easy to +create responsive designs. + + + + + + diff --git a/docs-ui/src/content/components/container.props.ts b/docs-ui/src/content/components/container.props.ts new file mode 100644 index 0000000000..a4d23a8495 --- /dev/null +++ b/docs-ui/src/content/components/container.props.ts @@ -0,0 +1,32 @@ +import { + classNamePropDefs, + stylePropDefs, + gapPropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const containerPropDefs: Record = { + ...gapPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const containerUsageSnippet = `import { Container } from "@backstage/ui"; + +Hello World!`; + +export const containerDefaultSnippet = ` + +`; + +export const containerSimpleSnippet = ` + Hello World + Hello World + Hello World +`; + +export const containerResponsiveSnippet = ` + Hello World + Hello World + Hello World +`; diff --git a/docs-ui/src/content/components/flex.mdx b/docs-ui/src/content/components/flex.mdx new file mode 100644 index 0000000000..89cd2e380a --- /dev/null +++ b/docs-ui/src/content/components/flex.mdx @@ -0,0 +1,68 @@ +import { PropsTable } from '@/components/PropsTable'; +import { CodeBlock } from '@/components/CodeBlock'; +import { Snippet } from '@/components/Snippet'; +import { FlexSnippet } from '@/snippets/stories-snippets'; +import { + flexPropDefs, + flexUsageSnippet, + flexDefaultSnippet, + flexFAQ1Snippet, + flexSimpleSnippet, + flexResponsiveSnippet, + flexAlignSnippet, +} from './flex.props'; +import { spacingPropDefs } from '@/utils/propDefs'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={flexDefaultSnippet} +/> + +## Usage + + + +## API reference + + + +The grid component also accepts all the spacing props from the Box component. + + + +## Examples + +### Simple + +A simple example of how to use the Flex component. + + + +### Responsive + +The Flex component also supports responsive values, making it easy to create +responsive designs. + + + +### Align + +The Flex component also supports responsive alignment, making it easy to +create responsive designs. + + + + + + diff --git a/docs-ui/src/content/components/flex.props.ts b/docs-ui/src/content/components/flex.props.ts new file mode 100644 index 0000000000..cc4c2d5a75 --- /dev/null +++ b/docs-ui/src/content/components/flex.props.ts @@ -0,0 +1,55 @@ +import { + childrenPropDefs, + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const flexPropDefs: Record = { + align: { + type: 'enum', + values: ['start', 'center', 'end', 'baseline', 'stretch'], + responsive: true, + }, + direction: { + type: 'enum', + values: ['row', 'column', 'row-reverse', 'column-reverse'], + responsive: true, + }, + justify: { + type: 'enum', + values: ['start', 'center', 'end', 'between'], + responsive: true, + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const flexUsageSnippet = `import { Flex } from '@backstage/ui'; + +`; + +export const flexDefaultSnippet = ` + + + +`; + +export const flexSimpleSnippet = ` + Hello World + Hello World + Hello World +`; + +export const flexResponsiveSnippet = ` + Hello World + Hello World + Hello World +`; + +export const flexAlignSnippet = ` + Hello World + Hello World + Hello World +`; diff --git a/canon-docs/src/app/(docs)/components/grid/page.mdx b/docs-ui/src/content/components/grid.mdx similarity index 50% rename from canon-docs/src/app/(docs)/components/grid/page.mdx rename to docs-ui/src/content/components/grid.mdx index c8604076eb..0ffdba6a66 100644 --- a/canon-docs/src/app/(docs)/components/grid/page.mdx +++ b/docs-ui/src/content/components/grid.mdx @@ -1,39 +1,42 @@ import { CodeBlock } from '@/components/CodeBlock'; import { PropsTable } from '@/components/PropsTable'; -import { Tabs } from '@/components/Tabs'; -import { grid } from '@/snippets/code-snippets'; import { Snippet } from '@/components/Snippet'; import { GridSnippet } from '@/snippets/stories-snippets'; -import { spacingPropDefs } from '../../../../utils/propDefs'; -import { gridPropDefs, gridItemPropDefs } from './props'; +import { spacingPropDefs } from '@/utils/propDefs'; +import { + gridPropDefs, + gridItemPropDefs, + gridUsageSnippet, + gridDefaultSnippet, + gridSimpleSnippet, + gridComplexSnippet, + gridMixingRowsSnippet, + gridResponsiveSnippet, + gridStartEndSnippet, +} from './grid.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; -# Grid - -A layout component that helps to create simple column-based layouts as well as -more complex ones. + } - code={` - - - -`} + code={gridDefaultSnippet} /> - - - Usage - - - - - +## Usage + + ## API reference -### Grid +### Grid.Root This is the grid container component. It will help to define the number of columns that will be used in the grid. You can also define the gap between the @@ -60,31 +63,14 @@ elements directly if you prefer. A simple grid with 3 columns and a gap of md. - - Hello World - Hello World - Hello World - -`} -/> + ### Complex grid You can also use the grid item to create more complex layouts. In this example the first column will span 1 column and the second column will span 2 columns. - - - Hello World - - - Hello World - - -`} -/> + ### Mixing rows and columns @@ -92,47 +78,21 @@ The grid item component also supports the `rowSpan` prop, which allows you to span multiple rows within the grid layout. In this example, the first item will span 2 rows to achieve a dynamic and flexible grid structure. - - - Hello World - - - Hello World - - - Hello World - - -`} -/> + ### Responsive grid The grid component also supports responsive values, making it easy to create responsive designs. - - - Hello World - - - Hello World - - -`} -/> + ### Start and End The start and end props can be used to position the item in the grid. - - - Hello World - - -`} -/> + + + + + diff --git a/docs-ui/src/content/components/grid.props.ts b/docs-ui/src/content/components/grid.props.ts new file mode 100644 index 0000000000..34d89785cc --- /dev/null +++ b/docs-ui/src/content/components/grid.props.ts @@ -0,0 +1,111 @@ +import { + childrenPropDefs, + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +const columnsValues = [ + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '10', + '11', + '12', +]; + +export const gridPropDefs: Record = { + columns: { + type: 'enum | string', + values: [...columnsValues, 'auto'], + responsive: true, + default: 'auto', + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const gridItemPropDefs: Record = { + colSpan: { + type: 'enum | string', + values: [...columnsValues, 'full'], + responsive: true, + }, + rowSpan: { + type: 'enum | string', + values: [...columnsValues, 'full'], + responsive: true, + }, + start: { + type: 'enum | string', + values: [...columnsValues, 'auto'], + responsive: true, + }, + end: { + type: 'enum | string', + values: [...columnsValues, 'auto'], + responsive: true, + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const gridUsageSnippet = `import { Grid } from '@backstage/ui'; + +`; + +export const gridDefaultSnippet = ` + + + +`; + +export const gridSimpleSnippet = ` + Hello World + Hello World + Hello World +`; + +export const gridComplexSnippet = ` + + Hello World + + + Hello World + +`; + +export const gridMixingRowsSnippet = ` + + Hello World + + + Hello World + + + Hello World + +`; + +export const gridResponsiveSnippet = ` + + Hello World + + + Hello World + +`; + +export const gridStartEndSnippet = ` + + Hello World + +`; diff --git a/docs-ui/src/content/components/header-page.mdx b/docs-ui/src/content/components/header-page.mdx new file mode 100644 index 0000000000..d3f93058ee --- /dev/null +++ b/docs-ui/src/content/components/header-page.mdx @@ -0,0 +1,87 @@ +import { PropsTable } from '@/components/PropsTable'; +import { CodeBlock } from '@/components/CodeBlock'; +import { Snippet } from '@/components/Snippet'; +import { HeaderPageSnippet } from '@/snippets/stories-snippets'; +import { + propDefs, + usage, + simple, + defaultSnippet, + withTabs, + withBreadcrumbs, + withCustomActions, + withMenuItems, +} from './header-page.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={defaultSnippet} +/> + +## Usage + + + +## API reference + + + +## Examples + +### With Breadcrumbs + +You can add breadcrumbs to the header page to help users navigate to the previous page. The `breadcrumbs` +prop is an array of objects with a `label` and `href` property. By default we truncate the breadcrumb label to 240px. + +} + code={withBreadcrumbs} +/> + +### With Tabs + +You can add tabs to the header page to help users navigate to the different sections of the page. The `tabs` +prop is an array of objects with a `label` and `href` property. + +} + code={withTabs} +/> + +### With Custom Actions + +You can add custom actions to the header page to help users navigate to the different sections of the page. The `customActions` +prop is a React node. + +} + code={withCustomActions} +/> + +### With Menu Items + +You can add menu items to the header page to help users navigate to the different sections of the page. The `menuItems` +prop is an array of objects with a `label`, `value` and `onClick` property. + +} + code={withMenuItems} +/> + + + + diff --git a/docs-ui/src/content/components/header-page.props.ts b/docs-ui/src/content/components/header-page.props.ts new file mode 100644 index 0000000000..fca9497b58 --- /dev/null +++ b/docs-ui/src/content/components/header-page.props.ts @@ -0,0 +1,141 @@ +import { + childrenPropDefs, + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const propDefs: Record = { + title: { + type: 'string', + default: 'Your plugin', + }, + customActions: { + type: 'enum', + values: ['ReactNode'], + }, + menuItems: { + type: 'complex', + complexType: { + name: 'MenuItem[]', + properties: { + label: { + type: 'string', + required: true, + description: 'Display text for the menu item', + }, + value: { + type: 'string', + required: true, + description: 'Unique value for the menu item', + }, + onClick: { + type: '() => void', + required: false, + description: 'Callback function when menu item is clicked', + }, + }, + }, + }, + tabs: { + type: 'complex', + complexType: { + name: 'HeaderTab[]', + properties: { + id: { + type: 'string', + required: true, + description: 'Unique identifier for the tab', + }, + label: { + type: 'string', + required: true, + description: 'Display text for the tab', + }, + href: { + type: 'string', + required: false, + description: 'URL to navigate to when tab is clicked', + }, + matchStrategy: { + type: "'exact' | 'prefix'", + required: false, + description: 'How to match the current route to highlight the tab', + }, + }, + }, + }, + breadcrumbs: { + type: 'complex', + complexType: { + name: 'Breadcrumb[]', + properties: { + label: { + type: 'string', + required: true, + description: 'Display text for the breadcrumb', + }, + href: { + type: 'string', + required: true, + description: 'URL for the breadcrumb link', + }, + }, + }, + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const usage = `import { HeaderPage } from '@backstage/ui'; + +`; + +export const defaultSnippet = `Custom action} +/>`; + +export const withBreadcrumbs = ``; + +export const withTabs = ``; + +export const withCustomActions = `Custom action} +/>`; + +export const withMenuItems = ` {} }, + { label: 'Invite new members', value: 'invite-new-members', onClick: () => {} }, + ]} +/>`; diff --git a/docs-ui/src/content/components/header.mdx b/docs-ui/src/content/components/header.mdx new file mode 100644 index 0000000000..af4e8010c5 --- /dev/null +++ b/docs-ui/src/content/components/header.mdx @@ -0,0 +1,85 @@ +import { PropsTable } from '@/components/PropsTable'; +import { CodeBlock } from '@/components/CodeBlock'; +import { Snippet } from '@/components/Snippet'; +import { HeaderSnippet } from '@/snippets/stories-snippets'; +import { + propDefs, + usage, + simple, + defaultSnippet, + withTabs, + withBreadcrumbs, + withHeaderPage, +} from './header.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={defaultSnippet} +/> + +## Usage + + + +## API reference + + + +## Examples + +### Simple header + +A simple example of how to use the Header component. + +} + code={simple} + open +/> + +### Header with tabs + +A simple example of how to use the Header component with tabs. All links are using React Router +under the hood and will be active when you are on the corresponding page. + +} +code={withTabs} +open +/> + +### Header with breadcrumbs + +Breacrumbs should appear when you scroll down (and not directly visible as it is in the demo below). + +} + code={withBreadcrumbs} + open +/> + +### Header with HeaderPage + +You can use the `Header` component inside the [HeaderPage](/components/header-page) component to compose your multi-level navigation. + +} + code={withHeaderPage} + open +/> + + + + diff --git a/docs-ui/src/content/components/header.props.ts b/docs-ui/src/content/components/header.props.ts new file mode 100644 index 0000000000..af9a745fc5 --- /dev/null +++ b/docs-ui/src/content/components/header.props.ts @@ -0,0 +1,200 @@ +import { + childrenPropDefs, + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const propDefs: Record = { + icon: { + type: 'enum', + values: ['ReactNode'], + }, + title: { + type: 'string', + default: 'Your plugin', + }, + titleLink: { + type: 'string', + default: '/', + }, + customActions: { + type: 'enum', + values: ['ReactNode'], + }, + menuItems: { + type: 'complex', + complexType: { + name: 'MenuItem[]', + properties: { + label: { + type: 'string', + required: true, + description: 'Display text for the menu item', + }, + value: { + type: 'string', + required: true, + description: 'Unique value for the menu item', + }, + onClick: { + type: '() => void', + required: false, + description: 'Callback function when menu item is clicked', + }, + }, + }, + }, + tabs: { + type: 'complex', + complexType: { + name: 'HeaderTab[]', + properties: { + id: { + type: 'string', + required: true, + description: 'Unique identifier for the tab', + }, + label: { + type: 'string', + required: true, + description: 'Display text for the tab', + }, + href: { + type: 'string', + required: false, + description: 'URL to navigate to when tab is clicked', + }, + matchStrategy: { + type: "'exact' | 'prefix'", + required: false, + description: 'How to match the current route to highlight the tab', + }, + }, + }, + }, + onTabSelectionChange: { + type: 'enum', + values: ['(key: string) => void'], + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const usage = `import { Header } from '@backstage/ui'; + +
`; + +export const defaultSnippet = `
+ } /> + } /> + } /> + + } +/>`; + +export const simple = `
+ } /> + } /> + } /> + + } +/>`; + +export const withTabs = ` +
+ } /> + } /> + } /> + + } + tabs={[ + { id: 'overview', label: 'Overview' }, + { id: 'checks', label: 'Checks' }, + { id: 'tracks', label: 'Tracks' }, + { id: 'campaigns', label: 'Campaigns' }, + { id: 'integrations', label: 'Integrations' }, + ]} +/> +`; + +export const withBreadcrumbs = `
`; + +export const withHeaderPage = `
+Custom action} +/>`; diff --git a/docs-ui/src/content/components/icon.mdx b/docs-ui/src/content/components/icon.mdx new file mode 100644 index 0000000000..91860a7e5e --- /dev/null +++ b/docs-ui/src/content/components/icon.mdx @@ -0,0 +1,36 @@ +import { PropsTable } from '@/components/PropsTable'; +import { IconSnippet } from '@/snippets/stories-snippets'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { + iconPropDefs, + iconUsageSnippet, + iconDefaultSnippet, +} from './icon.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={iconDefaultSnippet} +/> + +## Usage + + + +## API reference + + + + + + diff --git a/canon-docs/src/app/(docs)/components/icon/props.ts b/docs-ui/src/content/components/icon.props.ts similarity index 50% rename from canon-docs/src/app/(docs)/components/icon/props.ts rename to docs-ui/src/content/components/icon.props.ts index 2ddbafd94e..7bec0980f6 100644 --- a/canon-docs/src/app/(docs)/components/icon/props.ts +++ b/docs-ui/src/content/components/icon.props.ts @@ -1,5 +1,8 @@ -import { classNamePropDefs, stylePropDefs } from '../../../../utils/propDefs'; -import type { PropDef } from '../../../../utils/propDefs'; +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; export const iconPropDefs: Record = { name: { @@ -14,3 +17,9 @@ export const iconPropDefs: Record = { ...classNamePropDefs, ...stylePropDefs, }; + +export const iconUsageSnippet = `import { Icon } from '@backstage/ui'; + +`; + +export const iconDefaultSnippet = ``; diff --git a/docs-ui/src/content/components/link.mdx b/docs-ui/src/content/components/link.mdx new file mode 100644 index 0000000000..e0c1be353f --- /dev/null +++ b/docs-ui/src/content/components/link.mdx @@ -0,0 +1,102 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { + MenuSnippet, + ButtonSnippet, + LinkSnippet, +} from '@/snippets/stories-snippets'; +import { + linkPropDefs, + linkUsageSnippet, + linkDefaultSnippet, + linkVariantsSnippet, + linkWeightsSnippet, + linkColorsSnippet, + linkRouterSnippet, + linkTruncateSnippet, +} from './link.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={linkDefaultSnippet} +/> + +## Usage + + + +## API reference + + + +## Router Integration + +The `Link` component handles both internal and external navigation. It automatically detects whether the provided URL is internal (relative path) or external (absolute URL with protocol) and renders the appropriate element: + +- **Internal routes**: Uses `react-router-dom`'s `Link` component for client-side navigation +- **External URLs**: Renders a standard `` element for traditional navigation + + + +## Examples + +### Variants + +Here's a view when links have different variants. + +} + code={linkVariantsSnippet} +/> + +### Weights + +Here's a view when links have different weights. + +} + code={linkWeightsSnippet} +/> + +### Colors + +Here's a view when links have different colors. + +} + code={linkColorsSnippet} +/> + +### Truncate + +The `Link` component has a `truncate` prop that can be used to truncate the text. + +} + code={linkTruncateSnippet} +/> + + + + diff --git a/docs-ui/src/content/components/link.props.ts b/docs-ui/src/content/components/link.props.ts new file mode 100644 index 0000000000..748e64cc15 --- /dev/null +++ b/docs-ui/src/content/components/link.props.ts @@ -0,0 +1,81 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const linkPropDefs: Record = { + href: { + type: 'string', + }, + variant: { + type: 'enum', + values: [ + 'title-large', + 'title-medium', + 'title-small', + 'title-x-small', + 'body-large', + 'body-medium', + 'body-small', + 'body-x-small', + ], + default: 'body-medium', + responsive: true, + }, + weight: { + type: 'enum', + values: ['regular', 'bold'], + default: 'regular', + responsive: true, + }, + color: { + type: 'enum', + values: ['primary', 'secondary', 'danger', 'warning', 'success'], + default: 'primary', + responsive: true, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const linkUsageSnippet = `import { Link } from '@backstage/ui'; + +Sign up for Backstage`; + +export const linkDefaultSnippet = `Sign up for Backstage`; + +export const linkVariantsSnippet = ` + ... + ... + ... + ... + ... + ... + ... + ... +`; + +export const linkWeightsSnippet = ` + + +`; + +export const linkColorsSnippet = ` + I am primary + I am secondary + I am danger + I am warning + I am success +`; + +export const linkRouterSnippet = `import { Link } from '@backstage/ui'; + +// Internal route +Home + +// External URL +Backstage +`; + +export const linkTruncateSnippet = `...`; diff --git a/docs-ui/src/content/components/menu.mdx b/docs-ui/src/content/components/menu.mdx new file mode 100644 index 0000000000..c1b67b5034 --- /dev/null +++ b/docs-ui/src/content/components/menu.mdx @@ -0,0 +1,94 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { MenuSnippet } from '@/snippets/stories-snippets'; +import { + menuRootPropDefs, + menuTriggerPropDefs, + menuPositionerPropDefs, + menuItemPropDefs, + menuUsageSnippet, + menuDefaultSnippet, + menuTriggerSnippet, + menuHoverSnippet, + menuSubmenuSnippet, +} from './menu.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={menuDefaultSnippet} +/> + +## Usage + + + +## API reference + +### Menu.Root + +Groups all parts of the menu. Doesn't render its own HTML element. + + + +### Menu.Trigger + +The trigger by default render a simple unstyled button. Because menus can be rendered in different ways, we recommend +using the `render` prop to render a custom trigger. + + + + + +### Menu.Positioner + +Positions the menu popup against the trigger. Renders a `
` element. + + + +### Menu.Item + +An individual interactive item in the menu. Renders a `
` element. + + + +## Examples + +### Open on hover + +To create a menu that opens on hover, add the `openOnHover` prop to the Root part. +You can additionally configure how quickly the menu opens on hover using the `delay` prop. + +} + code={menuHoverSnippet} +/> + +### Nested navigation + +You can nest menus to create a more complex navigation structure. + +} + code={menuSubmenuSnippet} +/> + + + + diff --git a/canon-docs/src/app/(docs)/components/menu/props.ts b/docs-ui/src/content/components/menu.props.ts similarity index 51% rename from canon-docs/src/app/(docs)/components/menu/props.ts rename to docs-ui/src/content/components/menu.props.ts index 5cf41fa5f9..2d60987ecf 100644 --- a/canon-docs/src/app/(docs)/components/menu/props.ts +++ b/docs-ui/src/content/components/menu.props.ts @@ -2,8 +2,8 @@ import { classNamePropDefs, stylePropDefs, renderPropDefs, + type PropDef, } from '@/utils/propDefs'; -import type { PropDef } from '@/utils/propDefs'; export const menuRootPropDefs: Record = { defaultOpen: { @@ -140,3 +140,103 @@ export const menuItemPropDefs: Record = { ...classNamePropDefs, ...stylePropDefs, }; + +export const menuUsageSnippet = `import { Menu } from '@backstage/ui'; + + + + + + + + + + +`; + +export const menuDefaultSnippet = ` + ( + + )} + /> + + + + Settings + Invite new members + Download app + Log out + + + +`; + +export const menuTriggerSnippet = ` + )} + /> + + + + Settings + Invite new members + Download app + Log out + + + +`; + +export const menuSubmenuSnippet = ` ( + + )} +/> + + + + Settings + Invite new members + Download app + Log out + + Submenu + + + + Submenu Item 1 + Submenu Item 2 + Submenu Item 3 + + + + + + +`; diff --git a/docs-ui/src/content/components/radio-group.mdx b/docs-ui/src/content/components/radio-group.mdx new file mode 100644 index 0000000000..ab3d28ad81 --- /dev/null +++ b/docs-ui/src/content/components/radio-group.mdx @@ -0,0 +1,104 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { RadioGroupSnippet } from '@/snippets/stories-snippets'; +import { + radioGroupPropDefs, + radioGroupUsageSnippet, + radioGroupDefaultSnippet, + radioGroupDescriptionSnippet, + radioGroupHorizontalSnippet, + radioGroupDisabledSnippet, + radioGroupDisabledSingleSnippet, + radioGroupValidationSnippet, + radioGroupReadOnlySnippet, +} from './radio-group.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { CodeBlock } from '@/components/CodeBlock'; + + + +} + code={radioGroupDefaultSnippet} +/> + +## Usage + + + +## API reference + + + +## Examples + +### Horizontal + +Here's a simple TextField with a description. + +} + code={radioGroupHorizontalSnippet} +/> + +### Disabled + +You can disable the entire radio group by adding the `isDisabled` prop to the `RadioGroup` component. + +} + code={radioGroupDisabledSnippet} +/> + +### Disabled Single radio + +You can disable a single radio by adding the `isDisabled` prop to the `Radio` component. + +} + code={radioGroupDisabledSingleSnippet} +/> + +### Validation + +Here's an example of a radio group with errors. + +} + code={radioGroupValidationSnippet} +/> + +### Read only + +You can make the radio group read only by adding the `isReadOnly` prop to the `RadioGroup` component. + +} + code={radioGroupReadOnlySnippet} +/> + + + + diff --git a/docs-ui/src/content/components/radio-group.props.ts b/docs-ui/src/content/components/radio-group.props.ts new file mode 100644 index 0000000000..3ed08f1053 --- /dev/null +++ b/docs-ui/src/content/components/radio-group.props.ts @@ -0,0 +1,64 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const radioGroupPropDefs: Record = { + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + responsive: true, + }, + label: { + type: 'string', + }, + icon: { + type: 'enum', + values: ['ReactNode'], + }, + description: { + type: 'string', + }, + name: { + type: 'string', + required: true, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const radioGroupUsageSnippet = `import { RadioGroup } from '@backstage/ui'; + +`; + +export const radioGroupDefaultSnippet = ``; + +export const radioGroupDescriptionSnippet = ``; + +export const radioGroupHorizontalSnippet = ``; + +export const radioGroupDisabledSnippet = ` + Bulbasaur + Charmander + Squirtle +`; + +export const radioGroupDisabledSingleSnippet = ` + Bulbasaur + Charmander + Squirtle +`; + +export const radioGroupValidationSnippet = ` (value === \'charmander\' ? \'Nice try!\' : null)> + Bulbasaur + Charmander + Squirtle +`; + +export const radioGroupReadOnlySnippet = ` + Bulbasaur + Charmander + Squirtle +`; diff --git a/docs-ui/src/content/components/search-field.mdx b/docs-ui/src/content/components/search-field.mdx new file mode 100644 index 0000000000..2a2d93f262 --- /dev/null +++ b/docs-ui/src/content/components/search-field.mdx @@ -0,0 +1,77 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { SearchFieldSnippet } from '@/snippets/stories-snippets'; +import { + searchFieldPropDefs, + searchFieldUsageSnippet, + searchFieldDefaultSnippet, + searchFieldSizesSnippet, + searchFieldDescriptionSnippet, + searchFieldCollapsibleSnippet, +} from './search-field.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { CodeBlock } from '@/components/CodeBlock'; + + + +} + code={searchFieldDefaultSnippet} +/> + +## Usage + + + +## API reference + + + +## Examples + +### Sizes + +We support two different sizes: `small`, `medium`. + +} + code={searchFieldSizesSnippet} +/> + +### With description + +Here's a simple SearchField with a description. + +} + code={searchFieldDescriptionSnippet} +/> + +### Collapsible + +You can make the SearchField collapsible by setting the `startCollapsed` prop to `true`. + +} + code={searchFieldCollapsibleSnippet} +/> + + + + diff --git a/docs-ui/src/content/components/search-field.props.ts b/docs-ui/src/content/components/search-field.props.ts new file mode 100644 index 0000000000..b11ae3931c --- /dev/null +++ b/docs-ui/src/content/components/search-field.props.ts @@ -0,0 +1,49 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const searchFieldPropDefs: Record = { + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + responsive: true, + }, + label: { + type: 'string', + }, + icon: { + type: 'enum', + values: ['ReactNode'], + }, + description: { + type: 'string', + }, + name: { + type: 'string', + required: true, + }, + startCollapsed: { + type: 'boolean', + default: 'false', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const searchFieldUsageSnippet = `import { SearchField } from '@backstage/ui'; + +`; + +export const searchFieldDefaultSnippet = ``; + +export const searchFieldSizesSnippet = ` + } /> + } /> +`; + +export const searchFieldDescriptionSnippet = ``; + +export const searchFieldCollapsibleSnippet = ``; diff --git a/docs-ui/src/content/components/select.mdx b/docs-ui/src/content/components/select.mdx new file mode 100644 index 0000000000..09a852e871 --- /dev/null +++ b/docs-ui/src/content/components/select.mdx @@ -0,0 +1,98 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { SelectSnippet } from '@/snippets/stories-snippets'; +import { + selectPropDefs, + selectUsageSnippet, + selectDefaultSnippet, + selectDescriptionSnippet, + selectSizesSnippet, + selectDisabledSnippet, + selectResponsiveSnippet, + selectIconSnippet, +} from './select.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={selectDefaultSnippet} +/> + +## Usage + + + +## API reference + + + +## Examples + +### With Label and description + +Select component with label and description. + +} + code={selectDescriptionSnippet} +/> + +### Sizes + +Here's a view when the selects have different sizes. + +} + code={selectSizesSnippet} +/> + +### With Icon + +Here's a view when the select has an icon. + +} +code={selectIconSnippet} +/> + +### Disabled + +Here's a view when the select is disabled. + +} + code={selectDisabledSnippet} +/> + +### Responsive + +Here's a view when the select is responsive. + + + + + + diff --git a/docs-ui/src/content/components/select.props.ts b/docs-ui/src/content/components/select.props.ts new file mode 100644 index 0000000000..a93f448bd8 --- /dev/null +++ b/docs-ui/src/content/components/select.props.ts @@ -0,0 +1,153 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const selectPropDefs: Record = { + label: { + type: 'string', + responsive: false, + }, + description: { + type: 'string', + responsive: false, + }, + name: { + type: 'string', + responsive: false, + required: true, + }, + options: { + type: 'enum', + values: ['Array<{ value: string, label: string }>'], + required: true, + }, + placeholder: { + type: 'string', + default: 'Select an item', + responsive: false, + }, + icon: { + type: 'enum', + values: ['ReactNode'], + responsive: false, + }, + value: { + type: 'string', + responsive: false, + }, + defaultValue: { + type: 'string', + responsive: false, + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'medium', + responsive: true, + }, + isOpen: { + type: 'boolean', + responsive: false, + }, + defaultOpen: { + type: 'boolean', + responsive: false, + }, + disabledKeys: { + type: 'enum', + values: ['string[]'], + responsive: false, + }, + isDisabled: { + type: 'boolean', + responsive: false, + }, + isRequired: { + type: 'boolean', + responsive: false, + }, + isInvalid: { + type: 'boolean', + responsive: false, + }, + selectedKey: { + type: 'string', + responsive: false, + }, + defaultSelectedKey: { + type: 'string', + responsive: false, + }, + onOpenChange: { + type: 'enum', + values: ['(isOpen: boolean) => void'], + responsive: false, + }, + onSelectionChange: { + type: 'enum', + values: ['(key: Key | null) => void'], + responsive: false, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const selectUsageSnippet = `import { Select } from '@backstage/ui'; + +`; + +export const selectDescriptionSnippet = `} + options={[ ... ]} +/>`; + +export const selectSizesSnippet = ` + +`; + +export const selectDisabledSnippet = ``; diff --git a/docs-ui/src/content/components/skeleton.mdx b/docs-ui/src/content/components/skeleton.mdx new file mode 100644 index 0000000000..f0d3fbc39a --- /dev/null +++ b/docs-ui/src/content/components/skeleton.mdx @@ -0,0 +1,64 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { SkeletonSnippet } from '@/snippets/stories-snippets'; +import { + skeletonPropDefs, + skeletonUsageSnippet, + skeletonDefaultSnippet, + skeletonDemo1Snippet, + skeletonDemo2Snippet, +} from './skeleton.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={skeletonDefaultSnippet} +/> + +## Usage + + + +## API reference + + + +## Examples + +### Demo 1 + +You can use a mix of different sizes to create a more complex skeleton. + +} + code={skeletonDemo1Snippet} + open +/> + +### Demo 2 + +You can use a mix of different sizes to create a more complex skeleton. + +} + code={skeletonDemo2Snippet} + open +/> + + + + diff --git a/docs-ui/src/content/components/skeleton.props.ts b/docs-ui/src/content/components/skeleton.props.ts new file mode 100644 index 0000000000..ab79268d15 --- /dev/null +++ b/docs-ui/src/content/components/skeleton.props.ts @@ -0,0 +1,58 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const skeletonPropDefs: Record = { + width: { + type: 'number', + default: '80', + responsive: false, + }, + height: { + type: 'number', + default: '24', + responsive: false, + }, + rounded: { + type: 'boolean', + default: 'false', + responsive: false, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const skeletonUsageSnippet = `import { Flex, Skeleton } from '@backstage/ui'; + + + + + +`; + +export const skeletonDefaultSnippet = ` + + + +`; + +export const skeletonDemo1Snippet = ` + + + + + + + + + + +`; + +export const skeletonDemo2Snippet = ` + + + +`; diff --git a/docs-ui/src/content/components/switch.mdx b/docs-ui/src/content/components/switch.mdx new file mode 100644 index 0000000000..acf588e37d --- /dev/null +++ b/docs-ui/src/content/components/switch.mdx @@ -0,0 +1,46 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { SwitchSnippet } from '@/snippets/stories-snippets'; +import { switchPropDefs, snippetUsage } from './switch.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={``} +/> + +## Usage + + + +## API reference + + + +## Examples + +### Disabled + +A switch can be disabled using the `isDisabled` prop. + +} + code={``} +/> + + + + diff --git a/docs-ui/src/content/components/switch.props.ts b/docs-ui/src/content/components/switch.props.ts new file mode 100644 index 0000000000..5576436102 --- /dev/null +++ b/docs-ui/src/content/components/switch.props.ts @@ -0,0 +1,71 @@ +import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs'; +import type { PropDef } from '@/utils/propDefs'; + +export const switchPropDefs: Record = { + autoFocus: { + type: 'boolean', + }, + defaultSelected: { + type: 'boolean', + }, + ...classNamePropDefs, + isDisabled: { + type: 'boolean', + }, + isReadOnly: { + type: 'boolean', + }, + isSelected: { + type: 'boolean', + }, + label: { + type: 'string', + }, + name: { + type: 'string', + }, + onChange: { + type: 'enum', + values: ['(isSelected: boolean) => void'], + }, + onFocus: { + type: 'enum', + values: ['(e: FocusEvent) => void'], + }, + onBlur: { + type: 'enum', + values: ['(e: FocusEvent) => void'], + }, + onFocusChange: { + type: 'enum', + values: ['(isFocused: boolean) => void'], + }, + onKeyDown: { + type: 'enum', + values: ['(e: KeyboardEvent) => void'], + }, + onKeyUp: { + type: 'enum', + values: ['(e: KeyboardEvent) => void'], + }, + onHoverStart: { + type: 'enum', + values: ['(e: HoverEvent) => void'], + }, + onHoverEnd: { + type: 'enum', + values: ['(e: HoverEvent) => void'], + }, + onHoverChange: { + type: 'enum', + values: ['(isHovered: boolean) => void'], + }, + ...stylePropDefs, + value: { + type: 'string', + }, +}; + +export const snippetUsage = `import { Switch } from '@backstage/ui'; + +`; diff --git a/docs-ui/src/content/components/table.mdx b/docs-ui/src/content/components/table.mdx new file mode 100644 index 0000000000..4ea5fd1677 --- /dev/null +++ b/docs-ui/src/content/components/table.mdx @@ -0,0 +1,133 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { TableSnippet } from '@/snippets/stories-snippets'; +import { + tablePropDefs, + tableHeaderPropDefs, + tableBodyPropDefs, + tablePaginationPropDefs, + tableUsageSnippet, + tableBasicSnippet, + tableRowClickSnippet, + tableHybridSnippet, + tableCellInteractionsSnippet, + tablePaginationSnippet, + tableSelectionSnippet, + tableSortingSnippet, + columnPropDefs, + rowPropDefs, + cellPropDefs, +} from './table.props'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; + + + +} + code={tableBasicSnippet} +/> + +## Usage + + + +## API reference + +### Table + +The main table component that renders data in a structured grid format. + + + +### TableHeader + +The header section of the table that contains the column definitions. + + + +### Column + +A column definition that describes how a column should be rendered. + + + +### TableBody + +The body section of the table that contains the rows. + + + +### Row + +A row definition that describes how a row should be rendered. + + + +### Cell + +A cell definition that describes how a cell should be rendered. + + + +### TablePagination + +A pagination component designed to work with the Table component. + + + +## Examples + +### Basic Table + +Coming soon. + +### Row selection + +Coming soon. + +### Row Clicks + +Coming soon. + +### Pagination + +Coming soon. + +### Sorting + +Coming soon. + +### Asynchronous loading + +Coming soon. + +### Empty state + +Coming soon. + +### Column resizing + +This feature is not available yet — let us know if you'd like us to explore it! + +### Column reordering + +This feature is not available yet — let us know if you'd like us to explore it! + +### Column pinning + +This feature is not available yet — let us know if you'd like us to explore it! + +### Column visibility + +This feature is not available yet — let us know if you'd like us to explore it! + + + + diff --git a/docs-ui/src/content/components/table.props.ts b/docs-ui/src/content/components/table.props.ts new file mode 100644 index 0000000000..87be472609 --- /dev/null +++ b/docs-ui/src/content/components/table.props.ts @@ -0,0 +1,329 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '../../utils/propDefs'; + +export const tablePropDefs: Record = { + selectionBehavior: { + type: 'enum', + values: ['toggle', 'replace'], + default: 'toggle', + description: 'How multiple selection should behave in the collection.', + }, + disabledBehavior: { + type: 'enum', + values: ['selection', 'all'], + default: 'selection', + description: + 'Whether disabledKeys applies to all interactions, or only selection.', + }, + disabledKeys: { + type: 'enum', + values: ['Iterable'], + description: 'A list of row keys to disable.', + }, + selectionMode: { + type: 'enum', + values: ['single', 'multiple'], + description: 'The type of selection that is allowed in the collection.', + }, + selectedKeys: { + type: 'enum', + values: ['all', 'Iterable'], + description: 'The currently selected keys in the collection (controlled).', + }, + defaultSelectedKeys: { + type: 'enum', + values: ['all', 'Iterable'], + description: 'The initial selected keys in the collection (uncontrolled).', + }, + onRowAction: { + type: 'enum', + values: ['(key: Key) => void'], + description: + 'Handler that is called when a user performs an action on the row.', + }, + onSelectionChange: { + type: 'enum', + values: ['(keys: Selection) => void'], + description: 'Handler that is called when the selection changes.', + }, + onSortChange: { + type: 'enum', + values: ['(descriptor: SortDescriptor) => any'], + description: + 'Handler that is called when the sorted column or direction changes.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const tableHeaderPropDefs: Record = { + onHoverStart: { + type: 'enum', + values: ['(e: HoverEvent) => void'], + description: 'Handler that is called when a hover interaction starts.', + }, + onHoverEnd: { + type: 'enum', + values: ['(e: HoverEvent) => void'], + description: 'Handler that is called when a hover interaction ends.', + }, + onHoverChange: { + type: 'enum', + values: ['(isHovering: boolean) => void'], + description: 'Handler that is called when the hover state changes.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const columnPropDefs: Record = { + id: { + type: 'enum', + values: ['Key'], + description: 'The unique id of the column.', + }, + allowsSorting: { + type: 'boolean', + description: 'Whether the column allows sorting.', + }, + isRowHeader: { + type: 'boolean', + description: + 'Whether a column is a row header and should be announced by assistive technology during row navigation.', + }, + textValue: { + type: 'string', + description: + "A string representation of the column's contents, used for accessibility announcements.", + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const tableBodyPropDefs: Record = { + renderEmptyState: { + type: 'enum', + values: ['(props) => ReactNode'], + description: + 'Provides content to display when there are no rows in the table.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const rowPropDefs: Record = { + textValue: { + type: 'string', + description: + "A string representation of the row's contents, used for accessibility announcements.", + }, + isDisabled: { + type: 'boolean', + description: 'Whether the row is disabled.', + }, + id: { + type: 'enum', + values: ['Key'], + description: 'The unique id of the row.', + }, + href: { + type: 'string', + description: 'The URL to navigate to when the row is clicked.', + }, + hrefLang: { + type: 'string', + description: + 'The language of the URL to navigate to when the row is clicked.', + }, + target: { + type: 'string', + description: + 'The target of the URL to navigate to when the row is clicked.', + }, + rel: { + type: 'string', + description: + 'The relationship of the URL to navigate to when the row is clicked.', + }, + onAction: { + type: 'enum', + values: ['() => void'], + description: + "Handler that is called when a user performs an action on the row. The exact user event depends on the collection's selectionBehavior prop and the interaction modality.", + }, + onHoverStart: { + type: 'enum', + values: ['(e: HoverEvent) => void'], + description: 'Handler that is called when a hover interaction starts.', + }, + onHoverEnd: { + type: 'enum', + values: ['(e: HoverEvent) => void'], + description: 'Handler that is called when a hover interaction ends.', + }, + onHoverChange: { + type: 'enum', + values: ['(isHovering: boolean) => void'], + description: 'Handler that is called when the hover state changes.', + }, + onPress: { + type: 'enum', + values: ['(e: PressEvent) => void'], + description: + 'Handler that is called when the press is released over the target.', + }, + onPressStart: { + type: 'enum', + values: ['(e: PressEvent) => void'], + description: 'Handler that is called when a press interaction starts.', + }, + onPressEnd: { + type: 'enum', + values: ['(e: PressEvent) => void'], + description: + 'Handler that is called when a press interaction ends, either over the target or when the pointer leaves the target.', + }, + onPressChange: { + type: 'enum', + values: ['(isPressed: boolean) => void'], + description: 'Handler that is called when the press state changes.', + }, + onPressUp: { + type: 'enum', + values: ['(e: PressEvent) => void'], + description: + 'Handler that is called when a press is released over the target, regardless of whether it started on the target or not.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const cellPropDefs: Record = { + id: { + type: 'enum', + values: ['Key'], + description: 'The unique id of the cell.', + }, + textValue: { + type: 'string', + description: + "A string representation of the cell's contents, used for features like typeahead.", + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const tablePaginationPropDefs: Record = { + pageIndex: { + type: 'number', + description: 'The current page index.', + }, + pageSize: { + type: 'number', + description: 'The number of items per page.', + }, + setPageIndex: { + type: 'enum', + values: ['(pageIndex: number) => void'], + description: 'Handler that is called when the page index changes.', + }, + setPageSize: { + type: 'enum', + values: ['(pageSize: number) => void'], + description: 'Handler that is called when the page size changes.', + }, + rowCount: { + type: 'number', + description: 'The total number of rows in the table.', + }, + onNextPage: { + type: 'enum', + values: ['() => void'], + description: 'Handler that is called when the next page is requested.', + }, + onPreviousPage: { + type: 'enum', + values: ['() => void'], + description: 'Handler that is called when the previous page is requested.', + }, + onPageSizeChange: { + type: 'enum', + values: ['(pageSize: number) => void'], + description: 'Handler that is called when the page size changes.', + }, + showPageSizeOptions: { + type: 'boolean', + description: 'Whether to show the page size options.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const tableUsageSnippet = `import { Cell, ..., TableHeader, TablePagination } from '@backstage/ui'; + + + + + + + + + + + +
+`; + +export const tableBasicSnippet = `import { Table, TablePagination } from '@backstage/ui'; + +const [pageIndex, setPageIndex] = useState(0); +const [pageSize, setPageSize] = useState(5); + +const data = [ + { + name: 'The Beatles', + image: 'https://upload.wikimedia.org/wikipedia/en/thumb/4/42/Beatles_-...jpg', + genre: 'Rock, Pop, Psychedelic Rock', + yearFormed: 1960, + albums: 13 + }, + // ... more data +]; + +const newData = data4.slice( + pageIndex * pageSize, + (pageIndex + 1) * pageSize, +); + + + + Band name + Genre + Year formed + Albums + + + {newData.map(item => ( + + + + + + + ))} + +
+`; diff --git a/docs-ui/src/content/components/tabs.mdx b/docs-ui/src/content/components/tabs.mdx new file mode 100644 index 0000000000..26905d4733 --- /dev/null +++ b/docs-ui/src/content/components/tabs.mdx @@ -0,0 +1,84 @@ +import { PropsTable } from '@/components/PropsTable'; +import { TabsSnippet } from '@/snippets/stories-snippets'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { + tabsPropDefs, + tabPropDefs, + tabsUsageSnippet, + tabsDefaultSnippet, + tabsWithTabPanelsSnippet, + tabsWithLinksSnippet, + tabsWithDeeplyNestedRoutesSnippet, +} from './tabs.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={tabsDefaultSnippet} +/> + +## Usage + + + +## API reference + +### Tabs + +Groups the tabs and the corresponding panels. Renders a `
` element. + + + +### Tab + +An individual interactive tab button that toggles the corresponding panel. Renders a ` + I am a tooltip +`; + +export const tooltipDefaultSnippet = ` + + I am a tooltip +`; diff --git a/canon-docs/src/css/globals.css b/docs-ui/src/css/globals.css similarity index 80% rename from canon-docs/src/css/globals.css rename to docs-ui/src/css/globals.css index 21f55587d1..f94dd74610 100644 --- a/canon-docs/src/css/globals.css +++ b/docs-ui/src/css/globals.css @@ -1,8 +1,10 @@ :root { - --bg: #f8f8f8; + --bg: #f4f4f4; --panel: #fff; + --panel-hover: #fafafa; --primary: #000; - --secondary: #757575; + --secondary: #929292; + --action: #fff; --link: #4f5ce0; --font-regular: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', @@ -11,25 +13,28 @@ 'Courier New', monospace; --font-weight-regular: 400; --font-weight-bold: 600; - --border: rgba(0, 0, 0, 0.1); + --border: #e5e5e5; + --border2: #cdcdcd; + --surface-1: #f4f4f4; } -[data-theme='dark'] { +[data-theme-mode='dark'] { --bg: #000; - --panel: #222222; + --panel: #181818; + --panel-hover: #202020; --primary: #fff; - --secondary: #9e9e9e; + --secondary: #818181; + --action: #202020; --link: #4856e8; - --border: rgba(255, 255, 255, 0.1); + --border: #2a2a2a; + --border2: #2a2a2a; + --surface-1: #282828; } body { - display: flex; - flex-direction: row; background-color: var(--bg); color: var(--primary); font-family: var(--font-regular); - transition: background-color 0.2s ease-in-out; } iframe { @@ -49,8 +54,8 @@ iframe { line-height: 1.7; } -[data-theme='dark'] .shiki, -[data-theme='dark'] .shiki span { +[data-theme-mode='dark'] .shiki, +[data-theme-mode='dark'] .shiki span { color: var(--shiki-dark) !important; /* Optional, if you also want font styles */ font-style: var(--shiki-dark-font-style) !important; diff --git a/canon-docs/src/css/mdx.module.css b/docs-ui/src/css/mdx.module.css similarity index 79% rename from canon-docs/src/css/mdx.module.css rename to docs-ui/src/css/mdx.module.css index 4900455504..6a65995003 100644 --- a/canon-docs/src/css/mdx.module.css +++ b/docs-ui/src/css/mdx.module.css @@ -1,8 +1,9 @@ .h1 { - font-size: 2.5rem; - margin-top: 4rem; - margin-bottom: 1.5rem; + font-size: 3rem; + margin-top: 0; + margin-bottom: 1.25rem; color: var(--primary); + font-weight: 400; } .h2 { @@ -10,6 +11,7 @@ margin-top: 3rem; margin-bottom: 1.5rem; color: var(--primary); + font-weight: 400; } .h3 { @@ -17,6 +19,7 @@ margin-top: 2rem; margin-bottom: 0.5rem; color: var(--primary); + font-weight: 400; } .p { @@ -35,6 +38,7 @@ .li { color: var(--secondary); margin-bottom: 0.5rem; + line-height: 1.5; } .a { diff --git a/docs-ui/src/hooks/useCurrentPage.ts b/docs-ui/src/hooks/useCurrentPage.ts new file mode 100644 index 0000000000..1f5f978846 --- /dev/null +++ b/docs-ui/src/hooks/useCurrentPage.ts @@ -0,0 +1,31 @@ +'use client'; + +import { usePathname } from 'next/navigation'; +import { getPageName } from '@/utils/getPageName'; + +export function useCurrentPage(): string | null { + const pathname = usePathname(); + + // Handle root path + if (pathname === '/') { + return 'Getting Started'; + } + + // Extract slug from various path patterns + const patterns = [ + /^\/components\/(.+)$/, // /components/button + /^\/theme\/(.+)$/, // /theme/typography + /^\/about$/, // /about + /^\/changelog$/, // /changelog + ]; + + for (const pattern of patterns) { + const match = pathname.match(pattern); + if (match) { + const slug = match[1] || pathname.slice(1); // Use full path for exact matches + return getPageName(slug); + } + } + + return null; +} diff --git a/docs-ui/src/mdx-components.tsx b/docs-ui/src/mdx-components.tsx new file mode 100644 index 0000000000..75dbc81df8 --- /dev/null +++ b/docs-ui/src/mdx-components.tsx @@ -0,0 +1,56 @@ +import { isValidElement, ReactNode } from 'react'; +import type { MDXComponents } from 'mdx/types'; +import Image, { ImageProps } from 'next/image'; +import { CodeBlock } from '@/components/CodeBlock'; +import styles from './css/mdx.module.css'; + +export const formattedMDXComponents: MDXComponents = { + h1: ({ children }) =>

{children as ReactNode}

, + h2: ({ children }) =>

{children as ReactNode}

, + h3: ({ children }) =>

{children as ReactNode}

, + p: ({ children }) =>

{children as ReactNode}

, + a: ({ children, href }) => ( +
+ {children as ReactNode} + + ), + ul: ({ children }) =>
    {children as ReactNode}
, + li: ({ children }) =>
  • {children as ReactNode}
  • , + pre: ({ children }) => { + const codeContent = isValidElement(children) + ? (children.props as { children: string }).children + : ''; + + return ; + }, + code: ({ children }) => ( + + {children} + + ), + img: ({ src, ...rest }) => ( + )} + /> + ), +}; + +export function useMDXComponents(components: MDXComponents): MDXComponents { + return { + ...formattedMDXComponents, + ...components, + }; +} diff --git a/docs-ui/src/snippets/code-snippets.ts b/docs-ui/src/snippets/code-snippets.ts new file mode 100644 index 0000000000..1a22a24340 --- /dev/null +++ b/docs-ui/src/snippets/code-snippets.ts @@ -0,0 +1,32 @@ +// Sometimes codes are not formatted correctly in the docs, so we need to use snippets + +export const customTheme = `:root { + --bui-font-regular: system-ui; + --bui-font-weight-regular: 400; + --bui-font-weight-bold: 600; + --bui-bg: #f8f8f8; + --bui-bg-surface-1: #fff; + /* ... other CSS variables */ + + /* Add your custom components styles here */ + .bui-Button { + background-color: #000; + color: #fff; + } +} + +[data-theme-mode='dark'] { + --bui-font-regular: system-ui; + --bui-font-weight-regular: 400; + --bui-font-weight-bold: 600; + --bui-bg: #f8f8f8; + --bui-bg-surface-1: #fff; + /* ... other CSS variables */ + + /* Add your custom components styles here */ + .bui-Button { + background-color: #000; + color: #fff; + } +} +`; diff --git a/docs-ui/src/snippets/stories-snippets.tsx b/docs-ui/src/snippets/stories-snippets.tsx new file mode 100644 index 0000000000..22b4117c5c --- /dev/null +++ b/docs-ui/src/snippets/stories-snippets.tsx @@ -0,0 +1,71 @@ +'use client'; + +import { composeStories } from '@storybook/react'; +import * as BoxStories from '../../../packages/ui/src/components/Box/Box.stories'; +import * as ButtonStories from '../../../packages/ui/src/components/Button/Button.stories'; +import * as ButtonIconStories from '../../../packages/ui/src/components/ButtonIcon/ButtonIcon.stories'; +import * as ButtonLinkStories from '../../../packages/ui/src/components/ButtonLink/ButtonLink.stories'; +import * as CheckboxStories from '../../../packages/ui/src/components/Checkbox/Checkbox.stories'; +import * as ContainerStories from '../../../packages/ui/src/components/Container/Container.stories'; +import * as GridStories from '../../../packages/ui/src/components/Grid/Grid.stories'; +import * as IconStories from '../../../packages/ui/src/components/Icon/Icon.stories'; +import * as TextFieldStories from '../../../packages/ui/src/components/TextField/TextField.stories'; +import * as TextStories from '../../../packages/ui/src/components/Text/Text.stories'; +import * as FlexStories from '../../../packages/ui/src/components/Flex/Flex.stories'; +import * as SelectStories from '../../../packages/ui/src/components/Select/Select.stories'; +import * as MenuStories from '../../../packages/ui/src/components/Menu/Menu.stories'; +import * as LinkStories from '../../../packages/ui/src/components/Link/Link.stories'; +import * as AvatarStories from '../../../packages/ui/src/components/Avatar/Avatar.stories'; +import * as CollapsibleStories from '../../../packages/ui/src/components/Collapsible/Collapsible.stories'; +import * as RadioGroupStories from '../../../packages/ui/src/components/RadioGroup/RadioGroup.stories'; +import * as TabsStories from '../../../packages/ui/src/components/Tabs/Tabs.stories'; +import * as SwitchStories from '../../../packages/ui/src/components/Switch/Switch.stories'; +import * as SearchFieldStories from '../../../packages/ui/src/components/SearchField/SearchField.stories'; +import * as TooltipStories from '../../../packages/ui/src/components/Tooltip/Tooltip.stories'; +import * as SkeletonStories from '../../../packages/ui/src/components/Skeleton/Skeleton.stories'; +import * as CardStories from '../../../packages/ui/src/components/Card/Card.stories'; +import * as HeaderStories from '../../../packages/ui/src/components/Header/Header.stories'; +import * as HeaderPageStories from '../../../packages/ui/src/components/HeaderPage/HeaderPage.stories'; +import * as TableStories from '../../../packages/ui/src/components/Table/Table.stories'; + +// Helper function to create snippet components +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const createSnippetComponent = (stories: any) => { + return function SnippetComponent({ story }: { story: string }) { + const composedStories = composeStories(stories); + const StoryComponent = composedStories[ + story as keyof typeof composedStories + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ] as any; + + return StoryComponent ? : null; + }; +}; + +// Create snippet components using the helper function +export const BoxSnippet = createSnippetComponent(BoxStories); +export const ButtonSnippet = createSnippetComponent(ButtonStories); +export const ButtonIconSnippet = createSnippetComponent(ButtonIconStories); +export const ButtonLinkSnippet = createSnippetComponent(ButtonLinkStories); +export const CheckboxSnippet = createSnippetComponent(CheckboxStories); +export const ContainerSnippet = createSnippetComponent(ContainerStories); +export const GridSnippet = createSnippetComponent(GridStories); +export const IconSnippet = createSnippetComponent(IconStories); +export const TextFieldSnippet = createSnippetComponent(TextFieldStories); +export const TextSnippet = createSnippetComponent(TextStories); +export const FlexSnippet = createSnippetComponent(FlexStories); +export const SelectSnippet = createSnippetComponent(SelectStories); +export const MenuSnippet = createSnippetComponent(MenuStories); +export const LinkSnippet = createSnippetComponent(LinkStories); +export const AvatarSnippet = createSnippetComponent(AvatarStories); +export const CollapsibleSnippet = createSnippetComponent(CollapsibleStories); +export const RadioGroupSnippet = createSnippetComponent(RadioGroupStories); +export const TabsSnippet = createSnippetComponent(TabsStories); +export const SwitchSnippet = createSnippetComponent(SwitchStories); +export const SearchFieldSnippet = createSnippetComponent(SearchFieldStories); +export const TooltipSnippet = createSnippetComponent(TooltipStories); +export const SkeletonSnippet = createSnippetComponent(SkeletonStories); +export const CardSnippet = createSnippetComponent(CardStories); +export const HeaderSnippet = createSnippetComponent(HeaderStories); +export const HeaderPageSnippet = createSnippetComponent(HeaderPageStories); +export const TableSnippet = createSnippetComponent(TableStories); diff --git a/docs-ui/src/utils/changelog.ts b/docs-ui/src/utils/changelog.ts new file mode 100644 index 0000000000..a66997cf26 --- /dev/null +++ b/docs-ui/src/utils/changelog.ts @@ -0,0 +1,536 @@ +export type Component = + | 'avatar' + | 'box' + | 'button' + | 'button-link' + | 'heading' + | 'text' + | 'button-icon' + | 'icon' + | 'tabs' + | 'menu' + | 'textfield' + | 'datatable' + | 'select' + | 'collapsible' + | 'checkbox' + | 'container' + | 'link' + | 'tooltip' + | 'scrollarea' + | 'flex' + | 'switch' + | 'grid' + | 'searchfield' + | 'radio-group' + | 'card' + | 'skeleton' + | 'header'; + +export type Version = `${number}.${number}.${number}`; + +export interface ChangelogProps { + components: Component[]; + description: string; + version: Version; + prs: string[]; + type?: 'breaking' | 'new' | 'fix'; +} + +export const changelog: ChangelogProps[] = [ + { + components: ['tooltip'], + version: '0.6.0', + description: 'New `Tooltip` component.', + prs: ['30461'], + type: 'new', + }, + { + components: ['card'], + version: '0.6.0', + description: 'New `Card` component.', + prs: ['30467'], + type: 'new', + }, + { + components: ['header'], + version: '0.6.0', + description: 'New `Header` component.', + prs: ['30410', '30459', '30493'], + type: 'new', + }, + { + components: ['button', 'button-link', 'button-icon'], + version: '0.6.0', + description: 'Improve `Button`, `ButtonIcon` and `ButtonLink` styling.', + prs: ['30448'], + type: 'fix', + }, + { + components: ['text', 'heading'], + version: '0.6.0', + description: + 'Update return types for `Heading` & `Text` components for React 19.', + prs: ['30440'], + type: 'fix', + }, + { + components: ['button', 'button-link', 'button-icon'], + version: '0.6.0', + description: + 'New `tertiary` variant to `Button`, `ButtonIcon` and `ButtonLink`.', + prs: ['30453'], + type: 'new', + }, + { + components: ['skeleton'], + version: '0.6.0', + description: 'New `Skeleton` component.', + prs: ['30466'], + type: 'new', + }, + { + components: ['button-icon'], + version: '0.6.0', + description: 'Rename `IconButton` to `ButtonIcon`.', + prs: ['30297'], + type: 'breaking', + }, + { + components: ['button-link'], + version: '0.6.0', + description: + 'New `ButtonLink`, which replaces the previous render prop pattern on `Button` and `IconButton`.', + prs: ['30297'], + type: 'new', + }, + { + components: ['button', 'button-link', 'button-icon'], + version: '0.6.0', + description: 'Remove the `render` prop from all button-related components.', + prs: ['30297'], + type: 'breaking', + }, + { + components: [], + version: '0.6.0', + description: 'We are consolidating all css files into a single styles.css.', + prs: ['30325'], + type: 'fix', + }, + { + components: ['searchfield'], + version: '0.6.0', + description: 'New `SearchField` component.', + prs: ['30357'], + type: 'new', + }, + { + components: ['radio-group'], + version: '0.6.0', + description: 'New `RadioGroup` + `Radio` component.', + prs: ['30327'], + type: 'new', + }, + { + components: ['textfield'], + version: '0.6.0', + description: 'Added placeholder prop back to `TextField` component.', + prs: ['30286'], + type: 'fix', + }, + { + components: ['textfield'], + version: '0.5.0', + description: '`TextField` now has multiple label sizes.', + prs: ['30249'], + type: 'breaking', + }, + { + components: ['textfield'], + version: '0.5.0', + description: + '`TextField` can now hide label and description while keeping them available for screen readers.', + prs: ['30249'], + type: 'breaking', + }, + { + components: ['textfield'], + version: '0.5.0', + description: + 'We are removing the `render` prop in favour of the `as` prop on `Heading` and `Text`.', + prs: ['30291'], + type: 'breaking', + }, + { + components: ['textfield'], + version: '0.5.0', + description: 'Move `TextField` component to use react Aria under the hood.', + prs: ['30286'], + type: 'breaking', + }, + { + components: ['button', 'button-link'], + version: '0.5.0', + description: + 'Added a render prop to the `Button` component to use it as a link.', + prs: ['30165'], + type: 'new', + }, + { + components: ['heading'], + version: '0.5.0', + description: 'Fix styling for the title4 prop on the Heading component.', + prs: ['30167'], + type: 'fix', + }, + { + components: ['flex'], + version: '0.5.0', + description: + 'Add `min-width: 0;` by default on Flex components to support truncated text.', + prs: ['30168'], + type: 'fix', + }, + { + components: ['button', 'button-link', 'button-icon'], + version: '0.5.0', + description: + '`Button`, `ButtonLink`, `ButtonIcon` now default to size `small` instead of `medium`', + prs: ['30085', '30097'], + type: 'breaking', + }, + { + components: ['grid'], + version: '0.5.0', + description: 'Rename Grid component to ``', + prs: ['30013'], + type: 'breaking', + }, + { + components: ['flex', 'container', 'grid', 'box'], + version: '0.5.0', + description: 'Fixes spacing props on layout components', + prs: ['30013'], + type: 'fix', + }, + { + components: ['switch'], + version: '0.5.0', + description: 'New `Switch` component', + prs: ['30251'], + type: 'new', + }, + { + components: ['tabs'], + version: '0.4.0', + description: 'New `Tabs` component', + prs: ['29996'], + type: 'new', + }, + { + components: ['text', 'heading'], + version: '0.4.0', + description: 'Add `truncate` prop to `Text` and `Heading`', + prs: ['29988'], + type: 'new', + }, + { + components: ['menu'], + version: '0.4.0', + description: 'Add combobox option to `Menu`', + prs: ['29986'], + type: 'new', + }, + { + components: ['textfield'], + version: '0.4.0', + description: 'Add icon prop on `TextField`', + prs: ['29820'], + type: 'new', + }, + { + components: ['button', 'button-icon'], + version: '0.4.0', + description: 'Improve icon props on `Button` and `IconButton`', + prs: ['29667'], + }, + { + components: ['text', 'heading'], + version: '0.4.0', + description: + 'Improve the way we treat custom render on `Text` and `Heading`', + prs: ['29989'], + }, + { + components: ['menu'], + version: '0.4.0', + description: 'Improve `Menu` styles', + prs: ['29986'], + }, + { + components: ['textfield'], + version: '0.4.0', + description: 'Improve `TextField` styles', + prs: ['29974'], + }, + { + components: ['textfield'], + version: '0.4.0', + description: 'Improve clear button on `TextField`', + prs: ['29878'], + }, + { + components: ['flex'], + version: '0.4.0', + description: 'Fix spacing props on all layout components', + prs: ['30013'], + }, + { + components: [], + version: '0.4.0', + description: 'Fix - Pin Base UI version', + prs: ['29782'], + }, + { + components: ['select'], + version: '0.4.0', + description: 'Fix - Clicking `Select` label moves focus to trigger', + prs: ['29755'], + }, + { + components: ['datatable'], + version: '0.4.0', + description: 'Fix `DataTable.Pagination` count issue', + prs: ['29688'], + }, + { + components: ['datatable'], + version: '0.3.0', + description: 'Add `DataTable` component', + prs: ['29484', '29603'], + type: 'new', + }, + { + components: ['select'], + version: '0.3.0', + description: 'Add `Select` component', + prs: ['29440'], + type: 'new', + }, + { + components: ['avatar'], + version: '0.3.0', + description: 'Add `Avatar` component', + prs: ['29594'], + type: 'new', + }, + { + components: ['collapsible'], + version: '0.3.0', + description: 'Add `Collapsible` component', + prs: ['29617'], + type: 'new', + }, + { + components: ['textfield'], + version: '0.3.0', + description: 'Add `TextField` component instead of `Field` + `Input`', + prs: ['29364'], + type: 'new', + }, + { + components: ['datatable'], + version: '0.3.0', + description: 'Add `TableCellProfile`', + prs: ['29600'], + type: 'new', + }, + { + components: [], + version: '0.3.0', + description: 'Add breakpoint hooks - `up()` and `down()`', + prs: ['29564'], + type: 'new', + }, + { + components: [], + version: '0.3.0', + description: 'Add gray scale css tokens', + prs: ['29543'], + type: 'new', + }, + { + components: [], + version: '0.3.0', + description: + 'Update CSS styling API using `[data-___]` instead of class names for props', + prs: ['29560'], + }, + { + components: ['checkbox'], + version: '0.3.0', + description: 'Update `Checkbox` dark mode', + prs: ['29544'], + }, + { + components: ['container'], + version: '0.3.0', + description: 'Update `Container` styles', + prs: ['29475'], + }, + { + components: ['menu'], + version: '0.3.0', + description: 'Update `Menu` styles', + prs: ['29351'], + }, + { + components: ['select'], + version: '0.3.0', + description: 'Fix `Select` styles on small sizes + with long option names', + prs: ['29545'], + }, + { + components: ['link'], + version: '0.3.0', + description: 'Fix render prop on `Link`', + prs: ['29247'], + }, + { + components: ['textfield', 'select'], + version: '0.3.0', + description: 'Remove `Field` from `TextField` + `Select`', + prs: ['29482'], + }, + { + components: ['text', 'heading'], + version: '0.3.0', + description: 'Update `textDecoration` to `none` on `Text` / `Heading`', + prs: ['29357'], + }, + { + components: ['text', 'heading'], + version: '0.3.0', + description: 'Update `textDecoration` to `none` on `Text` / `Heading`', + prs: ['29357'], + }, + { + components: [], + version: '0.3.0', + description: 'Docs - Use stories from Storybook for all examples in Nextjs', + prs: ['29306'], + }, + { + components: [], + version: '0.3.0', + description: 'Docs - Add release page (this one 🤗)', + prs: ['29461'], + }, + { + components: [], + version: '0.3.0', + description: 'Docs - Add docs for Menu, Link', + prs: ['29576'], + }, + { + components: [], + version: '0.3.0', + description: 'Fix CSS watch mode', + prs: ['29352'], + }, + { + components: ['menu'], + version: '0.2.0', + description: 'New `Menu` component', + prs: ['29151'], + type: 'new', + }, + { + components: ['button-icon'], + version: '0.2.0', + description: 'New `IconButton` component', + prs: ['29239'], + type: 'new', + }, + { + components: ['scrollarea'], + version: '0.2.0', + description: 'New `ScrollArea` component', + prs: ['29240'], + type: 'new', + }, + { + components: ['button', 'checkbox'], + version: '0.2.0', + description: 'Improve `Button` & `Checkbox` styles', + prs: ['29127', '28789'], + }, + { + components: ['text'], + version: '0.2.0', + description: 'Improve `Text` styles', + prs: ['29200'], + }, + { + components: ['icon'], + version: '0.2.0', + description: 'Renamed `CanonProvider` to `IconProvider`', + prs: ['29002'], + }, + { + components: ['icon'], + version: '0.2.0', + description: 'Added about 40+ new icons', + prs: ['29264'], + }, + { + components: [], + version: '0.2.0', + description: 'Simplified styling into a unique styles.css file', + prs: ['29199'], + }, + { + components: [], + version: '0.2.0', + description: 'Added global styles to Backstage', + prs: ['29137'], + type: 'new', + }, + { + components: [], + version: '0.2.0', + description: 'Update global CSS tokens', + prs: ['28804'], + }, + { + components: ['flex'], + version: '0.2.0', + description: 'Merge Stack + Inline into Flex', + prs: ['28634'], + }, + { + components: ['button'], + version: '0.2.0', + description: 'Improve `Button` types', + prs: ['29205'], + }, + { + components: [], + version: '0.2.0', + description: 'Move font weight and family back to each components', + prs: ['28972'], + }, + { + components: [], + version: '0.2.0', + description: 'Fix custom values in spacing props', + prs: ['28770'], + }, + { + components: [], + version: '0.2.0', + description: 'Multiple updates on the docs site', + prs: ['28760'], + }, +]; diff --git a/canon-docs/src/utils/data.ts b/docs-ui/src/utils/data.ts similarity index 69% rename from canon-docs/src/utils/data.ts rename to docs-ui/src/utils/data.ts index 891b057ad6..752f28c2c5 100644 --- a/canon-docs/src/utils/data.ts +++ b/docs-ui/src/utils/data.ts @@ -9,17 +9,21 @@ export const overview: Page[] = [ title: 'Getting Started', slug: '', }, + { + title: 'Installation', + slug: 'install', + }, { title: 'About', slug: 'about', }, { - title: 'Releases', - slug: 'releases', + title: 'Changelog', + slug: 'changelog', }, ]; -export const theme: Page[] = [ +export const coreConcepts: Page[] = [ { title: 'Iconography', slug: 'iconography', @@ -36,10 +40,6 @@ export const theme: Page[] = [ title: 'Theming', slug: 'theming', }, - { - title: 'Typography', - slug: 'typography', - }, ]; export const layoutComponents: Page[] = [ @@ -76,6 +76,21 @@ export const components: Page[] = [ slug: 'button', status: 'alpha', }, + { + title: 'ButtonIcon', + slug: 'button-icon', + status: 'alpha', + }, + { + title: 'ButtonLink', + slug: 'button-link', + status: 'alpha', + }, + { + title: 'Card', + slug: 'card', + status: 'alpha', + }, { title: 'Checkbox', slug: 'checkbox', @@ -87,8 +102,13 @@ export const components: Page[] = [ status: 'alpha', }, { - title: 'Heading', - slug: 'heading', + title: 'Header', + slug: 'header', + status: 'alpha', + }, + { + title: 'HeaderPage', + slug: 'header-page', status: 'alpha', }, { @@ -96,11 +116,6 @@ export const components: Page[] = [ slug: 'icon', status: 'alpha', }, - { - title: 'IconButton', - slug: 'icon-button', - status: 'alpha', - }, { title: 'Link', slug: 'link', @@ -111,16 +126,41 @@ export const components: Page[] = [ slug: 'menu', status: 'alpha', }, + { + title: 'RadioGroup', + slug: 'radio-group', + status: 'alpha', + }, + { + title: 'SearchField', + slug: 'search-field', + status: 'alpha', + }, { title: 'Select', slug: 'select', status: 'alpha', }, + { + title: 'Skeleton', + slug: 'skeleton', + status: 'alpha', + }, + { + title: 'Switch', + slug: 'switch', + status: 'alpha', + }, { title: 'Table', slug: 'table', status: 'inProgress', }, + { + title: 'Tabs', + slug: 'tabs', + status: 'alpha', + }, { title: 'Text', slug: 'text', @@ -131,6 +171,11 @@ export const components: Page[] = [ slug: 'text-field', status: 'alpha', }, + { + title: 'Tooltip', + slug: 'tooltip', + status: 'alpha', + }, ]; export type ScreenSize = { diff --git a/docs-ui/src/utils/getPageName.ts b/docs-ui/src/utils/getPageName.ts new file mode 100644 index 0000000000..a3298bc9b7 --- /dev/null +++ b/docs-ui/src/utils/getPageName.ts @@ -0,0 +1,29 @@ +import { overview, coreConcepts, components, layoutComponents } from './data'; + +export function getPageName(slug: string): string | null { + // Search in overview pages + const overviewPage = overview.find(p => p.slug === slug); + if (overviewPage) { + return overviewPage.title; + } + + // Search in core concepts array + const coreConcept = coreConcepts.find(c => c.slug === slug); + if (coreConcept) { + return coreConcept.title; + } + + // Search in components array + const component = components.find(c => c.slug === slug); + if (component) { + return component.title; + } + + // Search in layoutComponents array + const layoutComponent = layoutComponents.find(c => c.slug === slug); + if (layoutComponent) { + return layoutComponent.title; + } + + return null; +} diff --git a/canon-docs/src/utils/playground-context.tsx b/docs-ui/src/utils/playground-context.tsx similarity index 67% rename from canon-docs/src/utils/playground-context.tsx rename to docs-ui/src/utils/playground-context.tsx index 9d4e23217f..56ed9dc48a 100644 --- a/canon-docs/src/utils/playground-context.tsx +++ b/docs-ui/src/utils/playground-context.tsx @@ -8,7 +8,7 @@ import { import { components } from './data'; type Theme = 'light' | 'dark'; -type ThemeName = 'legacy' | 'default' | 'custom'; +type ThemeName = 'backstage' | 'spotify'; // Create a context with an empty array as the default value const PlaygroundContext = createContext<{ @@ -27,7 +27,7 @@ const PlaygroundContext = createContext<{ setSelectedComponents: () => {}, selectedTheme: 'light', setSelectedTheme: () => {}, - selectedThemeName: 'default', + selectedThemeName: 'backstage', setSelectedThemeName: () => {}, }); @@ -40,24 +40,37 @@ export const PlaygroundProvider = ({ children }: { children: ReactNode }) => { const [selectedComponents, setSelectedComponents] = useState( components.map(component => component.slug), ); - const [selectedTheme, setSelectedTheme] = useState(() => { - return isBrowser - ? (localStorage.getItem('theme') as Theme) || 'light' - : 'light'; - }); - const [selectedThemeName, setSelectedThemeName] = useState(() => { - return isBrowser - ? (localStorage.getItem('theme-name') as ThemeName) || 'default' - : 'default'; - }); + const [selectedTheme, setSelectedTheme] = useState('light'); + const [selectedThemeName, setSelectedThemeName] = + useState('backstage'); + + // Load saved theme from localStorage after hydration + useEffect(() => { + if (isBrowser) { + const savedTheme = localStorage.getItem('theme-mode') as Theme; + if (savedTheme) { + setSelectedTheme(savedTheme); + } + } + }, [isBrowser]); + + // Load saved theme name from localStorage after hydration + useEffect(() => { + if (isBrowser) { + const savedThemeName = localStorage.getItem('theme-name') as ThemeName; + if (savedThemeName) { + setSelectedThemeName(savedThemeName); + } + } + }, [isBrowser]); useEffect(() => { if (isBrowser) { document.documentElement.setAttribute( - 'data-theme', + 'data-theme-mode', selectedTheme || 'light', ); - localStorage.setItem('theme', selectedTheme || 'light'); + localStorage.setItem('theme-mode', selectedTheme || 'light'); } }, [selectedTheme, isBrowser]); @@ -65,9 +78,9 @@ export const PlaygroundProvider = ({ children }: { children: ReactNode }) => { if (isBrowser) { document.documentElement.setAttribute( 'data-theme-name', - selectedThemeName || 'default', + selectedThemeName || 'backstage', ); - localStorage.setItem('theme-name', selectedThemeName || 'default'); + localStorage.setItem('theme-name', selectedThemeName || 'backstage'); } }, [selectedThemeName, isBrowser]); diff --git a/canon-docs/src/utils/propDefs.ts b/docs-ui/src/utils/propDefs.ts similarity index 90% rename from canon-docs/src/utils/propDefs.ts rename to docs-ui/src/utils/propDefs.ts index 3805665346..ed4a07e7ee 100644 --- a/canon-docs/src/utils/propDefs.ts +++ b/docs-ui/src/utils/propDefs.ts @@ -1,13 +1,34 @@ -import type { Breakpoint } from '@backstage/canon/src/types'; +import type { Breakpoint } from '@backstage/ui/src/types'; const breakpoints = ['initial', 'xs', 'sm', 'md', 'lg', 'xl'] as Breakpoint[]; +export type ComplexTypeDef = { + name: string; + properties: Record< + string, + { + type: string; + required?: boolean; + description?: string; + } + >; +}; + export type PropDef = { - type: 'string' | 'enum' | 'enum | string' | 'number' | 'boolean'; + type: + | 'string' + | 'enum' + | 'enum | string' + | 'number' + | 'boolean' + | 'spacing' + | 'complex'; values?: string | string[]; + complexType?: ComplexTypeDef; default?: string; required?: boolean; responsive?: boolean; + description?: string; }; export { breakpoints }; diff --git a/docs-ui/src/utils/theme-switcher.ts b/docs-ui/src/utils/theme-switcher.ts new file mode 100644 index 0000000000..d2e82de958 --- /dev/null +++ b/docs-ui/src/utils/theme-switcher.ts @@ -0,0 +1,32 @@ +// Auto-generated theme switcher utility + +export const availableThemes = { + default: '/styles.css', + spotify: '/spotify-theme.css', +} as const; + +export type ThemeName = keyof typeof availableThemes; + +export function switchTheme(themeName: ThemeName) { + // Remove existing theme links + const existingLinks = document.querySelectorAll('link[data-theme-mode]'); + existingLinks.forEach(link => link.remove()); + + // Add new theme link + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = availableThemes[themeName]; + link.setAttribute('data-theme-mode', themeName); + document.head.appendChild(link); +} + +export function getCurrentTheme(): ThemeName { + const existingLink = document.querySelector('link[data-theme-mode]'); + if (existingLink) { + const href = existingLink.getAttribute('href'); + for (const [name, path] of Object.entries(availableThemes)) { + if (path === href) return name as ThemeName; + } + } + return 'default'; +} diff --git a/canon-docs/tsconfig.json b/docs-ui/tsconfig.json similarity index 81% rename from canon-docs/tsconfig.json rename to docs-ui/tsconfig.json index 2baa7cde8f..e1b3951aa8 100644 --- a/canon-docs/tsconfig.json +++ b/docs-ui/tsconfig.json @@ -3,7 +3,7 @@ "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, - "strict": true, + "strict": false, "noEmit": true, "esModuleInterop": true, "module": "esnext", @@ -12,6 +12,7 @@ "isolatedModules": true, "jsx": "preserve", "incremental": true, + "noImplicitAny": false, "plugins": [ { "name": "next" @@ -19,14 +20,16 @@ ], "paths": { "@/*": ["./src/*"] - } + }, + "target": "ES2017" }, "include": [ "next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", - "dist/types/**/*.ts" + "dist/types/**/*.ts", + "src/types/**/*.d.ts" ], "exclude": ["node_modules"] } diff --git a/canon-docs/yarn.lock b/docs-ui/yarn.lock similarity index 65% rename from canon-docs/yarn.lock rename to docs-ui/yarn.lock index cbcdc2a55c..a73bb03140 100644 --- a/canon-docs/yarn.lock +++ b/docs-ui/yarn.lock @@ -5,36 +5,52 @@ __metadata: version: 8 cacheKey: 10 -"@babel/runtime@npm:^7.18.6": - version: 7.26.10 - resolution: "@babel/runtime@npm:7.26.10" +"@babel/code-frame@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/code-frame@npm:7.27.1" dependencies: - regenerator-runtime: "npm:^0.14.0" - checksum: 10/9d7ff8e96abe3791047c1138789c742411e3ef19c4d7ca18ce916f83cec92c06ec5dc64401759f6dd1e377cf8a01bbd2c62e033eb7550f435cf6579768d0d4a5 + "@babel/helper-validator-identifier": "npm:^7.27.1" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10/721b8a6e360a1fa0f1c9fe7351ae6c874828e119183688b533c477aa378f1010f37cc9afbfc4722c686d1f5cdd00da02eab4ba7278a0c504fa0d7a321dcd4fdf + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-identifier@npm:7.27.1" + checksum: 10/75041904d21bdc0cd3b07a8ac90b11d64cd3c881e89cb936fa80edd734bf23c35e6bd1312611e8574c4eab1f3af0f63e8a5894f4699e9cfdf70c06fcf4252320 + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.21.0": + version: 7.27.6 + resolution: "@babel/runtime@npm:7.27.6" + checksum: 10/cc957a12ba3781241b83d528eb69ddeb86ca5ac43179a825e83aa81263a6b3eb88c57bed8a937cdeacfc3192e07ec24c73acdfea4507d0c0428c8e23d6322bfe languageName: node linkType: hard "@codemirror/autocomplete@npm:^6.0.0": - version: 6.18.4 - resolution: "@codemirror/autocomplete@npm:6.18.4" + version: 6.18.6 + resolution: "@codemirror/autocomplete@npm:6.18.6" dependencies: "@codemirror/language": "npm:^6.0.0" "@codemirror/state": "npm:^6.0.0" "@codemirror/view": "npm:^6.17.0" "@lezer/common": "npm:^1.0.0" - checksum: 10/7cd62db2ee87d6cb3936ced4f211bdcd9ce75515f64922119370c303a8bdbf5640e640a8d802c30d01cb55fb6c9b5299426ff799f04cff0bef8d0c3450fe2528 + checksum: 10/0574d96fd04ccf2d3b7ae3c4efe0a72f423fa81658876ec50865ce3371cea038aeddf026976ec0d0ccbee72ac66bdf7deec9106dee251ad49019ae7e1a871663 languageName: node linkType: hard "@codemirror/commands@npm:^6.0.0, @codemirror/commands@npm:^6.1.0": - version: 6.8.0 - resolution: "@codemirror/commands@npm:6.8.0" + version: 6.8.1 + resolution: "@codemirror/commands@npm:6.8.1" dependencies: "@codemirror/language": "npm:^6.0.0" "@codemirror/state": "npm:^6.4.0" "@codemirror/view": "npm:^6.27.0" "@lezer/common": "npm:^1.1.0" - checksum: 10/0c7991736bc84d0a7f8e49c311c78a78def84fca36ff61c2f9887067e2c06b1b232711cb99282cd410527ddbe0671d2b9aff93e0848fa731a2014c3691c737ea + checksum: 10/72b6343777d4fe4af1bcc91331eb64705b33e871c9f8e2a0cd177269478a6f3282bf25aeaecdbb55fd5e031b67aabfede068e919fe69e473d07e5090dd76b381 languageName: node linkType: hard @@ -65,8 +81,8 @@ __metadata: linkType: hard "@codemirror/language@npm:^6.0.0": - version: 6.10.8 - resolution: "@codemirror/language@npm:6.10.8" + version: 6.11.1 + resolution: "@codemirror/language@npm:6.11.1" dependencies: "@codemirror/state": "npm:^6.0.0" "@codemirror/view": "npm:^6.23.0" @@ -74,38 +90,38 @@ __metadata: "@lezer/highlight": "npm:^1.0.0" "@lezer/lr": "npm:^1.0.0" style-mod: "npm:^4.0.0" - checksum: 10/63b83b41d9f8475f757144cc204df08834bb14411c484aa265ffa3e93b7d8f696a21110df72101159a8675eda29018c6d08f864965bd4651b607a39f10ad32ed + checksum: 10/024969113d61ccb5d497b75a8a9875d43e1bfc8466de2819bb5db23f01b200937366800a812e8d33eb5e34e3a5d2aa22d0ff8205f34a5e8aeb4cd1d221bcaa78 languageName: node linkType: hard "@codemirror/lint@npm:^6.0.0": - version: 6.8.4 - resolution: "@codemirror/lint@npm:6.8.4" + version: 6.8.5 + resolution: "@codemirror/lint@npm:6.8.5" dependencies: "@codemirror/state": "npm:^6.0.0" "@codemirror/view": "npm:^6.35.0" crelt: "npm:^1.0.5" - checksum: 10/401ead0591d88d31d1bf6527d4caba26e0deb7b49382dfbb8c712037d858047b0699fa2c15831a07db928194549eea9b942004fee42f334b34ff5973c7dbec58 + checksum: 10/9eddfea1dd0615431d57687c2a0d4de510d725aac6f6bbc2eba4bc934963371304dfa4c49398b3043372ef34cead3ebc0ec3f652632a87f5d8a458fa911a309a languageName: node linkType: hard "@codemirror/search@npm:^6.0.0": - version: 6.5.8 - resolution: "@codemirror/search@npm:6.5.8" + version: 6.5.11 + resolution: "@codemirror/search@npm:6.5.11" dependencies: "@codemirror/state": "npm:^6.0.0" "@codemirror/view": "npm:^6.0.0" crelt: "npm:^1.0.5" - checksum: 10/1389fa4e05da72b621ae10ff00d4ef0d23b08a3a7ac48f6e0b867429da7b80036bd7a6feef1a54f5b4c4af9fc4277f754a313a858fbfc12ccdfa48e9bf300cef + checksum: 10/d057f37cb369460b25625d7eb72f40636bf78ecd140608da53010cf3660f982a9e613826e38d85d87c9c2ff11e45c9482429987bfd4f29cbbd192f1ee3fd2695 languageName: node linkType: hard "@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.1.1, @codemirror/state@npm:^6.4.0, @codemirror/state@npm:^6.5.0": - version: 6.5.1 - resolution: "@codemirror/state@npm:6.5.1" + version: 6.5.2 + resolution: "@codemirror/state@npm:6.5.2" dependencies: "@marijn/find-cluster-break": "npm:^1.0.0" - checksum: 10/117f21ba2ff5bc9f9b946ca9943279a29bb9461ec77fc2313cfd8908ed6eb7df2950d7c5b0ba0a0a23c3e0633c6bba56f8c105f97875665e0376a9e110d10a48 + checksum: 10/5ccd3acb0c0a5b88e83fb91be39099fceb9f44a5047cc41a75d53f160e736851f65c8de40950b90c6519e6d2828e12f468db0af658dde30e938896f1c39eec91 languageName: node linkType: hard @@ -121,200 +137,229 @@ __metadata: languageName: node linkType: hard -"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.35.0": - version: 6.36.2 - resolution: "@codemirror/view@npm:6.36.2" +"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.34.4, @codemirror/view@npm:^6.35.0": + version: 6.38.1 + resolution: "@codemirror/view@npm:6.38.1" dependencies: "@codemirror/state": "npm:^6.5.0" + crelt: "npm:^1.0.6" style-mod: "npm:^4.1.0" w3c-keyname: "npm:^2.2.4" - checksum: 10/9ef7fcf4f9d9b6e66645ae65da1bf0c90e08f6ba786de0373b9f3644632066b91b8ea20faf67bb81eb9adf310ae76888cc7fd0901e2bb4821193f5427455c137 + checksum: 10/e0c5a365608749dd096ba7a930c8393f316bf4c2cacd1465a47a057d0a9f9868ff372a0bb6eb696c926f88411139f79a97a05f8c884bcc380145445cc61e68c8 languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/aix-ppc64@npm:0.25.0" +"@emnapi/core@npm:^1.4.3": + version: 1.4.3 + resolution: "@emnapi/core@npm:1.4.3" + dependencies: + "@emnapi/wasi-threads": "npm:1.0.2" + tslib: "npm:^2.4.0" + checksum: 10/b511f66b897d2019835391544fdf11f4fa0ce06cc1181abfa17c7d4cf03aaaa4fc8a64fcd30bb3f901de488d0a6f370b53a8de2215a898f5a4ac98015265b3b7 + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.4.3": + version: 1.4.3 + resolution: "@emnapi/runtime@npm:1.4.3" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/4f90852a1a5912982cc4e176b6420556971bcf6a85ee23e379e2455066d616219751367dcf43e6a6eaf41ea7e95ba9dc830665a52b5d979dfe074237d19578f8 + languageName: node + linkType: hard + +"@emnapi/wasi-threads@npm:1.0.2": + version: 1.0.2 + resolution: "@emnapi/wasi-threads@npm:1.0.2" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/e82941776665eb958c2084728191d6b15a94383449975c4621b67a1c8217e1c0ec11056a693906c76863cb96f782f8be500510ecec6874e3f5da35a8e7968cfd + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/aix-ppc64@npm:0.25.5" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/android-arm64@npm:0.25.0" +"@esbuild/android-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/android-arm64@npm:0.25.5" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/android-arm@npm:0.25.0" +"@esbuild/android-arm@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/android-arm@npm:0.25.5" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/android-x64@npm:0.25.0" +"@esbuild/android-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/android-x64@npm:0.25.5" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/darwin-arm64@npm:0.25.0" +"@esbuild/darwin-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/darwin-arm64@npm:0.25.5" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/darwin-x64@npm:0.25.0" +"@esbuild/darwin-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/darwin-x64@npm:0.25.5" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/freebsd-arm64@npm:0.25.0" +"@esbuild/freebsd-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/freebsd-arm64@npm:0.25.5" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/freebsd-x64@npm:0.25.0" +"@esbuild/freebsd-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/freebsd-x64@npm:0.25.5" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-arm64@npm:0.25.0" +"@esbuild/linux-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-arm64@npm:0.25.5" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-arm@npm:0.25.0" +"@esbuild/linux-arm@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-arm@npm:0.25.5" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-ia32@npm:0.25.0" +"@esbuild/linux-ia32@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-ia32@npm:0.25.5" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-loong64@npm:0.25.0" +"@esbuild/linux-loong64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-loong64@npm:0.25.5" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-mips64el@npm:0.25.0" +"@esbuild/linux-mips64el@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-mips64el@npm:0.25.5" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-ppc64@npm:0.25.0" +"@esbuild/linux-ppc64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-ppc64@npm:0.25.5" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-riscv64@npm:0.25.0" +"@esbuild/linux-riscv64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-riscv64@npm:0.25.5" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-s390x@npm:0.25.0" +"@esbuild/linux-s390x@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-s390x@npm:0.25.5" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-x64@npm:0.25.0" +"@esbuild/linux-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-x64@npm:0.25.5" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/netbsd-arm64@npm:0.25.0" +"@esbuild/netbsd-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/netbsd-arm64@npm:0.25.5" conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/netbsd-x64@npm:0.25.0" +"@esbuild/netbsd-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/netbsd-x64@npm:0.25.5" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/openbsd-arm64@npm:0.25.0" +"@esbuild/openbsd-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/openbsd-arm64@npm:0.25.5" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/openbsd-x64@npm:0.25.0" +"@esbuild/openbsd-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/openbsd-x64@npm:0.25.5" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/sunos-x64@npm:0.25.0" +"@esbuild/sunos-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/sunos-x64@npm:0.25.5" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/win32-arm64@npm:0.25.0" +"@esbuild/win32-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/win32-arm64@npm:0.25.5" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/win32-ia32@npm:0.25.0" +"@esbuild/win32-ia32@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/win32-ia32@npm:0.25.5" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/win32-x64@npm:0.25.0" +"@esbuild/win32-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/win32-x64@npm:0.25.5" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": - version: 4.4.1 - resolution: "@eslint-community/eslint-utils@npm:4.4.1" +"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.7.0": + version: 4.7.0 + resolution: "@eslint-community/eslint-utils@npm:4.7.0" dependencies: eslint-visitor-keys: "npm:^3.4.3" peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10/ae92a11412674329b4bd38422518601ec9ceae28e251104d1cad83715da9d38e321f68c817c39b64e66d0af7d98df6f9a10ad2dc638911254b47fb8932df00ef + checksum: 10/43ed5d391526d9f5bbe452aef336389a473026fca92057cf97c576db11401ce9bcf8ef0bf72625bbaf6207ed8ba6bf0dcf4d7e809c24f08faa68a28533c491a7 languageName: node linkType: hard @@ -374,6 +419,195 @@ __metadata: languageName: node linkType: hard +"@img/sharp-darwin-arm64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-darwin-arm64@npm:0.34.2" + dependencies: + "@img/sharp-libvips-darwin-arm64": "npm:1.1.0" + dependenciesMeta: + "@img/sharp-libvips-darwin-arm64": + optional: true + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-darwin-x64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-darwin-x64@npm:0.34.2" + dependencies: + "@img/sharp-libvips-darwin-x64": "npm:1.1.0" + dependenciesMeta: + "@img/sharp-libvips-darwin-x64": + optional: true + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-arm64@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.1.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-x64@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.1.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm64@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.1.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-linux-arm@npm:1.1.0" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-ppc64@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-linux-ppc64@npm:1.1.0" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-s390x@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.1.0" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-x64@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-linux-x64@npm:1.1.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-arm64@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.1.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-x64@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.1.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linux-arm64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-linux-arm64@npm:0.34.2" + dependencies: + "@img/sharp-libvips-linux-arm64": "npm:1.1.0" + dependenciesMeta: + "@img/sharp-libvips-linux-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-arm@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-linux-arm@npm:0.34.2" + dependencies: + "@img/sharp-libvips-linux-arm": "npm:1.1.0" + dependenciesMeta: + "@img/sharp-libvips-linux-arm": + optional: true + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-s390x@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-linux-s390x@npm:0.34.2" + dependencies: + "@img/sharp-libvips-linux-s390x": "npm:1.1.0" + dependenciesMeta: + "@img/sharp-libvips-linux-s390x": + optional: true + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-x64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-linux-x64@npm:0.34.2" + dependencies: + "@img/sharp-libvips-linux-x64": "npm:1.1.0" + dependenciesMeta: + "@img/sharp-libvips-linux-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-arm64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.2" + dependencies: + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.1.0" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-x64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-linuxmusl-x64@npm:0.34.2" + dependencies: + "@img/sharp-libvips-linuxmusl-x64": "npm:1.1.0" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-wasm32@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-wasm32@npm:0.34.2" + dependencies: + "@emnapi/runtime": "npm:^1.4.3" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@img/sharp-win32-arm64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-win32-arm64@npm:0.34.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-win32-ia32@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-win32-ia32@npm:0.34.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@img/sharp-win32-x64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-win32-x64@npm:0.34.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -388,6 +622,15 @@ __metadata: languageName: node linkType: hard +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10/4412e9e6713c89c1e66d80bb0bb5a2a93192f10477623a27d08f228ba0316bb880affabc5bfe7f838f58a34d26c2c190da726e576cdfc18c49a72e89adabdcf5 + languageName: node + linkType: hard + "@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.0.2, @lezer/common@npm:^1.1.0, @lezer/common@npm:^1.2.0": version: 1.2.3 resolution: "@lezer/common@npm:1.2.3" @@ -396,13 +639,13 @@ __metadata: linkType: hard "@lezer/css@npm:^1.1.7": - version: 1.1.9 - resolution: "@lezer/css@npm:1.1.9" + version: 1.2.1 + resolution: "@lezer/css@npm:1.2.1" dependencies: "@lezer/common": "npm:^1.2.0" "@lezer/highlight": "npm:^1.0.0" - "@lezer/lr": "npm:^1.0.0" - checksum: 10/bcc277bb6b806961ebfd03e98faf8e5870dd7da2eb565c43922ac378d926b49f479309b28cc824fd4e17baac2c8952966807d06fbe2a45d53e9a3f1518134729 + "@lezer/lr": "npm:^1.3.0" + checksum: 10/726ea0e0b4d10c0326cc62d562a63c0d74970dde2002fab8172681c63e3210ff39d0f629370ad498b70e503ab2e9f74dd342d7c9e402b98f315b556116c3551a languageName: node linkType: hard @@ -415,7 +658,7 @@ __metadata: languageName: node linkType: hard -"@lezer/lr@npm:^1.0.0": +"@lezer/lr@npm:^1.0.0, @lezer/lr@npm:^1.3.0": version: 1.4.2 resolution: "@lezer/lr@npm:1.4.2" dependencies: @@ -425,13 +668,13 @@ __metadata: linkType: hard "@lezer/sass@npm:^1.0.0": - version: 1.0.7 - resolution: "@lezer/sass@npm:1.0.7" + version: 1.1.0 + resolution: "@lezer/sass@npm:1.1.0" dependencies: "@lezer/common": "npm:^1.2.0" "@lezer/highlight": "npm:^1.0.0" "@lezer/lr": "npm:^1.0.0" - checksum: 10/a7531cbf0766d3875480c24ae8929dd56053d71f9eaef8815e6f468e2252e66e38236a3f89395463e4cee43230dc35351386bedc6138cf1a19a44a64ce58a372 + checksum: 10/2f42dfaf5a328d4626a0a8ff8cd5973b7f52e8112541281cf1cbb1970acdc52c7c9f55c71f635bcff75a6151a55f7653d277306a15494b23b652e87d16df9ce3 languageName: node linkType: hard @@ -457,7 +700,7 @@ __metadata: languageName: node linkType: hard -"@mdx-js/mdx@npm:^3.0.0": +"@mdx-js/mdx@npm:^3.0.0, @mdx-js/mdx@npm:^3.1.0": version: 3.1.0 resolution: "@mdx-js/mdx@npm:3.1.0" dependencies: @@ -501,25 +744,36 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:14.2.26": - version: 14.2.26 - resolution: "@next/env@npm:14.2.26" - checksum: 10/0caa679cdf9367501daf1bff0e762b720613a3ee1689e5585bfb7c792fdd07c251bc1f7488919a296047eb1f2b82ceb13955b673143468cad338a87b7d98c196 - languageName: node - linkType: hard - -"@next/eslint-plugin-next@npm:14.2.23": - version: 14.2.23 - resolution: "@next/eslint-plugin-next@npm:14.2.23" +"@napi-rs/wasm-runtime@npm:^0.2.11": + version: 0.2.11 + resolution: "@napi-rs/wasm-runtime@npm:0.2.11" dependencies: - glob: "npm:10.3.10" - checksum: 10/e46513a7601bf682f1e60cc668188cf2c9f0471be73ffbbac69da240e9f91c6b9c3aad3e83a8da54c0ba8bce37cc6368bbafb04b74b385d5df0c36dde09a3217 + "@emnapi/core": "npm:^1.4.3" + "@emnapi/runtime": "npm:^1.4.3" + "@tybys/wasm-util": "npm:^0.9.0" + checksum: 10/e30fe3060474c5018e160231df0531d62b5e22f4736ecd49c04ca6cadacb2acf59b9205435794cd5b898e41e2e3ddb6523e93b97799bd1f4d0751557de6e38e4 languageName: node linkType: hard -"@next/mdx@npm:^15.1.4": - version: 15.1.6 - resolution: "@next/mdx@npm:15.1.6" +"@next/env@npm:15.3.4": + version: 15.3.4 + resolution: "@next/env@npm:15.3.4" + checksum: 10/40ea0bee2eca72dce6102d30ac50029309bf68a281585270e38f920ae47043f240352ad250058d462169a436dea3d8f78a935406ee61088bd4a614524b4a315b + languageName: node + linkType: hard + +"@next/eslint-plugin-next@npm:15.3.4": + version: 15.3.4 + resolution: "@next/eslint-plugin-next@npm:15.3.4" + dependencies: + fast-glob: "npm:3.3.1" + checksum: 10/8a473bd32a06f62c16f60f9c40b7b8149cf91170ce4c1546cda57e9c85ac8481c7ad3aa4a77e8c3c7000e80d3de809d2db7df6cb79e13fd258fff382729abf60 + languageName: node + linkType: hard + +"@next/mdx@npm:15.3.4": + version: 15.3.4 + resolution: "@next/mdx@npm:15.3.4" dependencies: source-map: "npm:^0.7.0" peerDependencies: @@ -530,69 +784,62 @@ __metadata: optional: true "@mdx-js/react": optional: true - checksum: 10/3b46e99934543df94bcb335dddab794db4fcaff09223343e9e00597d3df1e094f16dd619c433fb2fa7fff38c3da57b3118cbc010cdeb9790d0cf01e6c37016e8 + checksum: 10/07904beda049317e43857f9dfe659adb6e57c4417475de1d21a7605f8ace5d9aa992a06465d1a94fe4b62331901765b15659555e8aadedbd410f86df1bebd1bd languageName: node linkType: hard -"@next/swc-darwin-arm64@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-darwin-arm64@npm:14.2.26" +"@next/swc-darwin-arm64@npm:15.3.4": + version: 15.3.4 + resolution: "@next/swc-darwin-arm64@npm:15.3.4" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@next/swc-darwin-x64@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-darwin-x64@npm:14.2.26" +"@next/swc-darwin-x64@npm:15.3.4": + version: 15.3.4 + resolution: "@next/swc-darwin-x64@npm:15.3.4" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@next/swc-linux-arm64-gnu@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-linux-arm64-gnu@npm:14.2.26" +"@next/swc-linux-arm64-gnu@npm:15.3.4": + version: 15.3.4 + resolution: "@next/swc-linux-arm64-gnu@npm:15.3.4" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-arm64-musl@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-linux-arm64-musl@npm:14.2.26" +"@next/swc-linux-arm64-musl@npm:15.3.4": + version: 15.3.4 + resolution: "@next/swc-linux-arm64-musl@npm:15.3.4" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@next/swc-linux-x64-gnu@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-linux-x64-gnu@npm:14.2.26" +"@next/swc-linux-x64-gnu@npm:15.3.4": + version: 15.3.4 + resolution: "@next/swc-linux-x64-gnu@npm:15.3.4" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-x64-musl@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-linux-x64-musl@npm:14.2.26" +"@next/swc-linux-x64-musl@npm:15.3.4": + version: 15.3.4 + resolution: "@next/swc-linux-x64-musl@npm:15.3.4" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@next/swc-win32-arm64-msvc@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-win32-arm64-msvc@npm:14.2.26" +"@next/swc-win32-arm64-msvc@npm:15.3.4": + version: 15.3.4 + resolution: "@next/swc-win32-arm64-msvc@npm:15.3.4" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@next/swc-win32-ia32-msvc@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-win32-ia32-msvc@npm:14.2.26" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@next/swc-win32-x64-msvc@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-win32-x64-msvc@npm:14.2.26" +"@next/swc-win32-x64-msvc@npm:15.3.4": + version: 15.3.4 + resolution: "@next/swc-win32-x64-msvc@npm:15.3.4" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -631,6 +878,28 @@ __metadata: languageName: node linkType: hard +"@npmcli/agent@npm:^3.0.0": + version: 3.0.0 + resolution: "@npmcli/agent@npm:3.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^10.0.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10/775c9a7eb1f88c195dfb3bce70c31d0fe2a12b28b754e25c08a3edb4bc4816bfedb7ac64ef1e730579d078ca19dacf11630e99f8f3c3e0fd7b23caa5fd6d30a6 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/fs@npm:4.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10/405c4490e1ff11cf299775449a3c254a366a4b1ffc79d87159b0ee7d5558ac9f6a2f8c0735fd6ff3873cef014cb1a44a5f9127cb6a1b2dbc408718cca9365b5a + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -638,6 +907,15 @@ __metadata: languageName: node linkType: hard +"@remixicon/react@npm:^4.6.0": + version: 4.6.0 + resolution: "@remixicon/react@npm:4.6.0" + peerDependencies: + react: ">=18.2.0" + checksum: 10/d92c8131734062fca8d82d55dd8f3f4e28cb72c5eefda528c8aba5f3298f0c847342eecdbd7787d6da5af21b699d735f6cf27f151f0c982c1c9f06a78b235c94 + languageName: node + linkType: hard + "@rtsao/scc@npm:^1.1.0": version: 1.1.0 resolution: "@rtsao/scc@npm:1.1.0" @@ -645,97 +923,97 @@ __metadata: languageName: node linkType: hard -"@rushstack/eslint-patch@npm:^1.3.3": - version: 1.10.5 - resolution: "@rushstack/eslint-patch@npm:1.10.5" - checksum: 10/769d130dfb088c21e7ce72b552ea58c0d6d790cfe9fcadc4cd66e1282a0213a2c6b570d679723e0d3792a6b6ebfcbdeb5e785261d196017840853b8a5c57dfea +"@rushstack/eslint-patch@npm:^1.10.3": + version: 1.11.0 + resolution: "@rushstack/eslint-patch@npm:1.11.0" + checksum: 10/9bb3eb4a48a9a55e31d302b8b99f405e0f3e436fc3cda8c869fdd3fefd3ac398f5a353cceaa6c8cc5e5baf03ccd88d7965fbd25eb111f1f334415f95fa0f996d languageName: node linkType: hard -"@shikijs/core@npm:1.26.1": - version: 1.26.1 - resolution: "@shikijs/core@npm:1.26.1" +"@shikijs/core@npm:1.29.2": + version: 1.29.2 + resolution: "@shikijs/core@npm:1.29.2" dependencies: - "@shikijs/engine-javascript": "npm:1.26.1" - "@shikijs/engine-oniguruma": "npm:1.26.1" - "@shikijs/types": "npm:1.26.1" + "@shikijs/engine-javascript": "npm:1.29.2" + "@shikijs/engine-oniguruma": "npm:1.29.2" + "@shikijs/types": "npm:1.29.2" "@shikijs/vscode-textmate": "npm:^10.0.1" "@types/hast": "npm:^3.0.4" hast-util-to-html: "npm:^9.0.4" - checksum: 10/d70dd7e267da624bdc098f9f0295699d30d6bb0873a0c7f239f0664f253532af395208efe10cb0ad6f96b8c63fee439ce7ddbf5bd60b41149e2911fc3c55fc8f + checksum: 10/83dc5e86efc587d513268175ff43e8273567d2c6616d725bff15b08cf243d90f9371d6fa76ba49bb0e9823fc9947c8d8c650c81cfb19c3eb726178326f639a55 languageName: node linkType: hard -"@shikijs/engine-javascript@npm:1.26.1": - version: 1.26.1 - resolution: "@shikijs/engine-javascript@npm:1.26.1" +"@shikijs/engine-javascript@npm:1.29.2": + version: 1.29.2 + resolution: "@shikijs/engine-javascript@npm:1.29.2" dependencies: - "@shikijs/types": "npm:1.26.1" + "@shikijs/types": "npm:1.29.2" "@shikijs/vscode-textmate": "npm:^10.0.1" - oniguruma-to-es: "npm:0.10.0" - checksum: 10/fd7dac25fe4310bcd055af5163044fa598a65ec54376938f2ee70b8ab864a4d2f4bba2e76353d739ffcf071ffbc57b16147f47e5c9f950bf82f826cf88d3dd85 + oniguruma-to-es: "npm:^2.2.0" + checksum: 10/b49461ff7152650ffdbd77332d1c70e24a2ff1abe869e1038694b410194c6403fe5e8fce104fdd305d10c18702a50c1edbdb87172aa09f11340bc1203ed38488 languageName: node linkType: hard -"@shikijs/engine-oniguruma@npm:1.26.1": - version: 1.26.1 - resolution: "@shikijs/engine-oniguruma@npm:1.26.1" +"@shikijs/engine-oniguruma@npm:1.29.2": + version: 1.29.2 + resolution: "@shikijs/engine-oniguruma@npm:1.29.2" dependencies: - "@shikijs/types": "npm:1.26.1" + "@shikijs/types": "npm:1.29.2" "@shikijs/vscode-textmate": "npm:^10.0.1" - checksum: 10/17b3355fc226f58ef67d419c9e64a4dc99a63481c66f7ec55fdfb5035363605359414211e40ac8260db9c10c507e587dfac1061c0f57f111050e921e38cd58ef + checksum: 10/bb3e2c01da84d573251ebc289b1ecf815261024dea5bddb93ad56c3504a71cde3630db070be401ed3bbcd23a8a839ec78984a82317f9c9d0bba58daed935b781 languageName: node linkType: hard -"@shikijs/langs@npm:1.26.1": - version: 1.26.1 - resolution: "@shikijs/langs@npm:1.26.1" +"@shikijs/langs@npm:1.29.2": + version: 1.29.2 + resolution: "@shikijs/langs@npm:1.29.2" dependencies: - "@shikijs/types": "npm:1.26.1" - checksum: 10/b3909ffa30a89aa81e21da3c0af9388dfadab4f5ce9ce6f5a1cc3f7728e46702c02720123bad49dcd3f02e823c0ce9d533534164b0feb57856d2f8ee9504bce2 + "@shikijs/types": "npm:1.29.2" + checksum: 10/01f62d31c653c718a992918357e54d2d312c8da407997565fc19056fbf47f0fadc0f9f4b5fe1e1ba7b7d08e3984fb1f962159503ef0edd81fab5ee8bfdbf9080 languageName: node linkType: hard -"@shikijs/themes@npm:1.26.1": - version: 1.26.1 - resolution: "@shikijs/themes@npm:1.26.1" +"@shikijs/themes@npm:1.29.2": + version: 1.29.2 + resolution: "@shikijs/themes@npm:1.29.2" dependencies: - "@shikijs/types": "npm:1.26.1" - checksum: 10/5e2f5fe828f1b74dcc4027239768b3fe91567a5e07c3d014747627a7bd8a5ecf22460c59c20924172a96d99f4deed3e81e6f377e5f81239d6c6666d09e12fbd6 + "@shikijs/types": "npm:1.29.2" + checksum: 10/b81606dd882136e3fd751d0829133b5e66b10b8e32bd52ce16e7eac8755891c23e43f3ce06e65b97a75d2bc3b17e7fdb9115e9812679bb820ab163915868fd8b languageName: node linkType: hard -"@shikijs/types@npm:1.26.1": - version: 1.26.1 - resolution: "@shikijs/types@npm:1.26.1" +"@shikijs/types@npm:1.29.2": + version: 1.29.2 + resolution: "@shikijs/types@npm:1.29.2" dependencies: "@shikijs/vscode-textmate": "npm:^10.0.1" "@types/hast": "npm:^3.0.4" - checksum: 10/68ad9307424012cb833c02b7e0db4f0792d6ff9b633591318c76edb5a4ffb84da0de56dbe33b9b62ae810a878fdfd4f9ff6655a067997c664d9b0436266bcb16 + checksum: 10/579e64b6e8cb83023232b8060b08f51cff3909b199d0d1a0c58ed500c898dd34b74bf0457336fa2e6bee1005889e198d7d924347ad616eee30c6ae4c89a67ab8 languageName: node linkType: hard "@shikijs/vscode-textmate@npm:^10.0.1": - version: 10.0.1 - resolution: "@shikijs/vscode-textmate@npm:10.0.1" - checksum: 10/8bb005efbf472a9cac19bb5fb0bb1a2b612e128122e5a9d7b2bb2af674c29912db0e59c547ec458e5333f8f7ac7a8f054a24cb653f11dc20951e299933b416e4 + version: 10.0.2 + resolution: "@shikijs/vscode-textmate@npm:10.0.2" + checksum: 10/d924cba8a01cd9ca12f56ba097d628fdb81455abb85884c8d8a5ae85b628a37dd5907e7691019b97107bd6608c866adf91ba04a1c3bba391281c88e386c044ea languageName: node linkType: hard -"@storybook/components@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/components@npm:8.6.12" +"@storybook/components@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/components@npm:8.6.14" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/ccc6af275bdfbc66de8afb272f59b2b4b6b76bb2961903335cb62e7defae4a2368bd8f5a2008f8598dd8417e5368d824bce836ca96c3c6ff659dbf2f081ec0dd + checksum: 10/d3647505510313aa3c32fd1f8f202eda723ad99bd353b20aa929c5ecab4edc3c86ad06d0eac02f3c6d948e88f13f65ca5fa35ef27c079ef1b92abe8692f23699 languageName: node linkType: hard -"@storybook/core@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/core@npm:8.6.12" +"@storybook/core@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/core@npm:8.6.14" dependencies: - "@storybook/theming": "npm:8.6.12" + "@storybook/theming": "npm:8.6.14" better-opn: "npm:^3.0.2" browser-assert: "npm:^1.2.1" esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0" @@ -751,7 +1029,7 @@ __metadata: peerDependenciesMeta: prettier: optional: true - checksum: 10/78776f51b9eae00f9387421b33b646b1dc67ef833fd6272de03399daa7f0ffa248c65b5f24d5d2a9af923a029d06d84d5425e3455302ece542bf47c7a9ec0df6 + checksum: 10/8f8c811edd4ea8dcedcc63a79b3168dc83aa0401e3760990cb5995ea6beaac4026c6ccc1046182f41eed7c09333005f3348d8b42f89fc5dd5b2ce011a9d2a48f languageName: node linkType: hard @@ -762,92 +1040,91 @@ __metadata: languageName: node linkType: hard -"@storybook/manager-api@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/manager-api@npm:8.6.12" +"@storybook/manager-api@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/manager-api@npm:8.6.14" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/d1c44c6649a024c4007461c12a15337f5d13532dbaccc4c02f71bd99599fb973e2574eb8f1bc2d93e05da24e4ae43fa47ec637a7c4cccf5ffc67045cafbf087c + checksum: 10/4544b317050b81574f1cd6f911dcb99ee2adbd7190555209171a7dd2233cd331165fa11c16a5977ebe58eeaf26c86bbfcb23f701cfd79a10f0d034dae65197bd languageName: node linkType: hard -"@storybook/preview-api@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/preview-api@npm:8.6.12" +"@storybook/preview-api@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/preview-api@npm:8.6.14" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/d24f11e4e54e9e51297b0f87e1d462b3f14a974b4681f31d93b62b0706ce5b5ed4ffaaac521ec049dcb0e08e7aa7590f2e039aee4bbe9f85033d69474d982f23 + checksum: 10/9b77288f2f627a7c70cfd3e88bdc4348c1425fab6b333ed77efb913e45881bcdf2ca67f1174e47fe978a7993c2a653fb8accf518ba51441cfb9450145101e9d8 languageName: node linkType: hard -"@storybook/react-dom-shim@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/react-dom-shim@npm:8.6.12" +"@storybook/react-dom-shim@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/react-dom-shim@npm:8.6.14" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.12 - checksum: 10/7677b4fae978209af239471f9eb609db4318815bd4249a0f48b9875482d9ae910b93fbe4db5d7f794ecc2a1249edf40da26af9de673c941c48fccc4007819c96 + storybook: ^8.6.14 + checksum: 10/d14d970b98dc8266d4df80225538fb537449c15877f0f54465dfe52242f0c3f041c975824cc2894f72a3a8023fdffa0a281d90325b95297858e3684913b3cc15 languageName: node linkType: hard "@storybook/react@npm:^8.6.8": - version: 8.6.12 - resolution: "@storybook/react@npm:8.6.12" + version: 8.6.14 + resolution: "@storybook/react@npm:8.6.14" dependencies: - "@storybook/components": "npm:8.6.12" + "@storybook/components": "npm:8.6.14" "@storybook/global": "npm:^5.0.0" - "@storybook/manager-api": "npm:8.6.12" - "@storybook/preview-api": "npm:8.6.12" - "@storybook/react-dom-shim": "npm:8.6.12" - "@storybook/theming": "npm:8.6.12" + "@storybook/manager-api": "npm:8.6.14" + "@storybook/preview-api": "npm:8.6.14" + "@storybook/react-dom-shim": "npm:8.6.14" + "@storybook/theming": "npm:8.6.14" peerDependencies: - "@storybook/test": 8.6.12 + "@storybook/test": 8.6.14 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.12 + storybook: ^8.6.14 typescript: ">= 4.2.x" peerDependenciesMeta: "@storybook/test": optional: true typescript: optional: true - checksum: 10/d8258c82743906f48a872a781f3e5a63e9ce3fda2ba3b911e959cf62ebda43989b23746257d97905addb7142ac7e53d0089dc178bcbeea48ed4d37d025dd047b + checksum: 10/a8710dcb80da9df4a78cfb3560a8c0c31c6f6c04902aa95446d4e40f6a9179770ead6f9a0a06fa44d00677cd1cd92eb60fdc57f433035ec722275847139671a4 languageName: node linkType: hard -"@storybook/theming@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/theming@npm:8.6.12" +"@storybook/theming@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/theming@npm:8.6.14" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/c811d9dbb9eaaa680b922111fca126a2985f2238dfb01c1cd23184323eea12899dc9f079063ac42c5e63b0c83de326bd9cc17241e4060ff04e860c57a55fb8b9 + checksum: 10/6936ea3348968fe598ad47421c11a78c6ee2ce62336ea1ce9cb8257e9faa2553d3ac3e443f8a36d35a41b0d60eb169231516649c710582ec68fdead4f23ffc0e languageName: node linkType: hard -"@swc/counter@npm:^0.1.3": +"@swc/counter@npm:0.1.3": version: 0.1.3 resolution: "@swc/counter@npm:0.1.3" checksum: 10/df8f9cfba9904d3d60f511664c70d23bb323b3a0803ec9890f60133954173047ba9bdeabce28cd70ba89ccd3fd6c71c7b0bd58be85f611e1ffbe5d5c18616598 languageName: node linkType: hard -"@swc/helpers@npm:0.5.5": - version: 0.5.5 - resolution: "@swc/helpers@npm:0.5.5" +"@swc/helpers@npm:0.5.15": + version: 0.5.15 + resolution: "@swc/helpers@npm:0.5.15" dependencies: - "@swc/counter": "npm:^0.1.3" - tslib: "npm:^2.4.0" - checksum: 10/1c5ef04f642542212df28c669438f3e0f459dcde7b448a5b1fcafb2e9e4f13e76d8428535a270e91ed123dd2a21189dbed34086b88a8cf68baf84984d6d0e39b + tslib: "npm:^2.8.0" + checksum: 10/e3f32c6deeecfb0fa3f22edff03a7b358e7ce16d27b0f1c8b5cdc3042c5c4ce4da6eac0b781ab7cc4f54696ece657467d56734fb26883439fb00017385364c4c languageName: node linkType: hard -"@types/acorn@npm:^4.0.0": - version: 4.0.6 - resolution: "@types/acorn@npm:4.0.6" +"@tybys/wasm-util@npm:^0.9.0": + version: 0.9.0 + resolution: "@tybys/wasm-util@npm:0.9.0" dependencies: - "@types/estree": "npm:*" - checksum: 10/e00671d5055d06b07feccb8c2841467a4bdd1ab95a29e191d51cacc08c496e1ba1f54edeefab274bb2ba51cb45b0aaaa662a63897650e9d02e9997ad82124ae4 + tslib: "npm:^2.4.0" + checksum: 10/aa58e64753a420ad1eefaf7bacef3dda61d74f9336925943d9244132d5b48d9242f734f1e707fd5ccfa6dd1d8ec8e6debc234b4dedb3a5b0d8486d1f373350b2 languageName: node linkType: hard @@ -870,9 +1147,9 @@ __metadata: linkType: hard "@types/estree@npm:*, @types/estree@npm:^1.0.0": - version: 1.0.6 - resolution: "@types/estree@npm:1.0.6" - checksum: 10/9d35d475095199c23e05b431bcdd1f6fec7380612aed068b14b2a08aa70494de8a9026765a5a91b1073f636fb0368f6d8973f518a31391d519e20c59388ed88d + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10/25a4c16a6752538ffde2826c2cc0c6491d90e69cd6187bef4a006dd2c3c45469f049e643d7e516c515f21484dc3d48fd5c870be158a5beb72f5baf3dc43e4099 languageName: node linkType: hard @@ -892,7 +1169,7 @@ __metadata: languageName: node linkType: hard -"@types/mdast@npm:^4.0.0": +"@types/mdast@npm:^4.0.0, @types/mdast@npm:^4.0.4": version: 4.0.4 resolution: "@types/mdast@npm:4.0.4" dependencies: @@ -909,44 +1186,36 @@ __metadata: linkType: hard "@types/ms@npm:*": - version: 0.7.34 - resolution: "@types/ms@npm:0.7.34" - checksum: 10/f38d36e7b6edecd9badc9cf50474159e9da5fa6965a75186cceaf883278611b9df6669dc3a3cc122b7938d317b68a9e3d573d316fcb35d1be47ec9e468c6bd8a + version: 2.1.0 + resolution: "@types/ms@npm:2.1.0" + checksum: 10/532d2ebb91937ccc4a89389715e5b47d4c66e708d15942fe6cc25add6dc37b2be058230a327dd50f43f89b8b6d5d52b74685a9e8f70516edfc9bdd6be910eff4 languageName: node linkType: hard "@types/node@npm:^20": - version: 20.17.30 - resolution: "@types/node@npm:20.17.30" + version: 20.19.0 + resolution: "@types/node@npm:20.19.0" dependencies: - undici-types: "npm:~6.19.2" - checksum: 10/69fd3b177417be77b459e8f1dd4e78c85c686167086920fbf35a9fda301709bbeee6a87ad2591fb1ddd96c65e725ec6bb527a06496626a1c94367d1361048f8d + undici-types: "npm:~6.21.0" + checksum: 10/76cc44487faa0bf5302c84e5317de4b9727ea4e4b86d59d18d25435f4ccd7e6c584614dea061256f24a097c25a1d82dac1cd134357b42ae28006ba113c97f490 languageName: node linkType: hard -"@types/prop-types@npm:*": - version: 15.7.14 - resolution: "@types/prop-types@npm:15.7.14" - checksum: 10/d0c5407b9ccc3dd5fae0ccf9b1007e7622ba5e6f1c18399b4f24dff33619d469da4b9fa918a374f19dc0d9fe6a013362aab0b844b606cfc10676efba3f5f736d - languageName: node - linkType: hard - -"@types/react-dom@npm:^18.0.0": - version: 18.3.6 - resolution: "@types/react-dom@npm:18.3.6" +"@types/react-dom@npm:19.1.7": + version: 19.1.7 + resolution: "@types/react-dom@npm:19.1.7" peerDependencies: - "@types/react": ^18.0.0 - checksum: 10/ae179355401c64423d39946eda22c5f7f74c94ce61c21505024d4d82c33853ec40bc9b370f75e4a7750b0524aba4d95a43fcc328d8d14684dc2abb41ba529de8 + "@types/react": ^19.0.0 + checksum: 10/a99465e5a17d40725dedb3708357f8998c57caab768cee0992b4bb7822ce7ed2ec697a5f426cb98d3397b020756a6e4b0986dc5f6f4254e13b3536afb38538e6 languageName: node linkType: hard -"@types/react@npm:^18.0.0": - version: 18.3.20 - resolution: "@types/react@npm:18.3.20" +"@types/react@npm:19.1.9": + version: 19.1.9 + resolution: "@types/react@npm:19.1.9" dependencies: - "@types/prop-types": "npm:*" csstype: "npm:^3.0.2" - checksum: 10/020c51e63b60862e6d772f0cdea0b9441182eedab6289dabd8add0708ded62003834c4e7c6f23a1ccd3ca9486b46296057c3f881c34261a0483765351f8d0bc3 + checksum: 10/b1032eae52e3b4f2a8b9ea6aac936385a78b7eff55cad4ff4f0d7e726c6ea87c1f287a1ba0e57a76a8e4456d3fb918b4f97a01e71686fdc11a65b26b8d296be4 languageName: node linkType: hard @@ -965,120 +1234,144 @@ __metadata: linkType: hard "@typescript-eslint/eslint-plugin@npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": - version: 8.19.1 - resolution: "@typescript-eslint/eslint-plugin@npm:8.19.1" + version: 8.34.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.34.0" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.19.1" - "@typescript-eslint/type-utils": "npm:8.19.1" - "@typescript-eslint/utils": "npm:8.19.1" - "@typescript-eslint/visitor-keys": "npm:8.19.1" + "@typescript-eslint/scope-manager": "npm:8.34.0" + "@typescript-eslint/type-utils": "npm:8.34.0" + "@typescript-eslint/utils": "npm:8.34.0" + "@typescript-eslint/visitor-keys": "npm:8.34.0" graphemer: "npm:^1.4.0" - ignore: "npm:^5.3.1" + ignore: "npm:^7.0.0" natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^2.0.0" + ts-api-utils: "npm:^2.1.0" peerDependencies: - "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0 + "@typescript-eslint/parser": ^8.34.0 eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.8.0" - checksum: 10/c9a6d3181ec01068075b85ad3ac454910b4452281d60c775cc7229827f6d6a076b7336f5f07a7ad89bf08b3224f6a49aa20342b9438702393bee0aa7315d23b2 + typescript: ">=4.8.4 <5.9.0" + checksum: 10/224f9e8a596e3c37fade2f2a1a9efce2fad652a768710693458e2b7c7f88c3a0e7bbbbc46d34d839c9373861fac542de6b9a7e132e36e2819b63840b9529e605 languageName: node linkType: hard "@typescript-eslint/parser@npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": - version: 8.19.1 - resolution: "@typescript-eslint/parser@npm:8.19.1" + version: 8.34.0 + resolution: "@typescript-eslint/parser@npm:8.34.0" dependencies: - "@typescript-eslint/scope-manager": "npm:8.19.1" - "@typescript-eslint/types": "npm:8.19.1" - "@typescript-eslint/typescript-estree": "npm:8.19.1" - "@typescript-eslint/visitor-keys": "npm:8.19.1" + "@typescript-eslint/scope-manager": "npm:8.34.0" + "@typescript-eslint/types": "npm:8.34.0" + "@typescript-eslint/typescript-estree": "npm:8.34.0" + "@typescript-eslint/visitor-keys": "npm:8.34.0" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.8.0" - checksum: 10/da3db63ff655cf0fb91745ba8e52d853386f601cf6106d36f4541efcb9e2c6c3b82c6743b15680eff9eafeccaf31c9b26191a955e66ae19de9172f67335463ab + typescript: ">=4.8.4 <5.9.0" + checksum: 10/b4c03ff2f09fd800a8f28c24289d24e2f4bfb4745c122f5f496bf832b06f0f37b1ab31ce8d7590ff1f83253de3306d145ef7b3c7b853a4ae716cb7ff443d1c27 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/scope-manager@npm:8.19.1" +"@typescript-eslint/project-service@npm:8.34.0": + version: 8.34.0 + resolution: "@typescript-eslint/project-service@npm:8.34.0" dependencies: - "@typescript-eslint/types": "npm:8.19.1" - "@typescript-eslint/visitor-keys": "npm:8.19.1" - checksum: 10/6ffc78b15367f211eb6650459ca2bb6bfe4c1fa95a3474adc08ee9a20c250b2e0e02fd99be36bd3dad74967ecd9349e792b5d818d85735cba40f1b5c236074d1 - languageName: node - linkType: hard - -"@typescript-eslint/type-utils@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/type-utils@npm:8.19.1" - dependencies: - "@typescript-eslint/typescript-estree": "npm:8.19.1" - "@typescript-eslint/utils": "npm:8.19.1" + "@typescript-eslint/tsconfig-utils": "npm:^8.34.0" + "@typescript-eslint/types": "npm:^8.34.0" debug: "npm:^4.3.4" - ts-api-utils: "npm:^2.0.0" + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + checksum: 10/04763896215c208c6b29e0b4f66ee0621878cd88fb6d9008c543db57f1d6b5d7fcc88f048c9a66ba2ed797f68e563c350e1b65403349ef75a4bc419072cef3c8 + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:8.34.0": + version: 8.34.0 + resolution: "@typescript-eslint/scope-manager@npm:8.34.0" + dependencies: + "@typescript-eslint/types": "npm:8.34.0" + "@typescript-eslint/visitor-keys": "npm:8.34.0" + checksum: 10/fec7bb94fb3848bdf5ab9cfaf634e56aec3ed9bc4d546f65d83bb6511452e5a4b9eed5d09f54efceb9fa3b23a451d409735359237e8c0d51233d6537e5449fa7 + languageName: node + linkType: hard + +"@typescript-eslint/tsconfig-utils@npm:8.34.0, @typescript-eslint/tsconfig-utils@npm:^8.34.0": + version: 8.34.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.34.0" + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + checksum: 10/cbbca9526bd9c0309c77f9436f68c2c06712779a593a17757f1f7558ece27d9f40db2b37ebf12bd9e19cf227479083b7973c502436a0954a08406d8a598910ba + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:8.34.0": + version: 8.34.0 + resolution: "@typescript-eslint/type-utils@npm:8.34.0" + dependencies: + "@typescript-eslint/typescript-estree": "npm:8.34.0" + "@typescript-eslint/utils": "npm:8.34.0" + debug: "npm:^4.3.4" + ts-api-utils: "npm:^2.1.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.8.0" - checksum: 10/123ecda88b057d6a4b68226701f435661440a420fda88cba60b49d7fb3e4f49483164ff174f259e28c0beabb0ed04500462a20faefd78331ba202bf54b01e3ef + typescript: ">=4.8.4 <5.9.0" + checksum: 10/e7c565868b18d66ce5de016455c5ba2dc625a845e05ad563bfdf08b1753faa11d9aef22b9dc5071c57b6e73932748505715e7b47993757f1bc244d4d6f70d688 languageName: node linkType: hard -"@typescript-eslint/types@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/types@npm:8.19.1" - checksum: 10/5833a5f8fdac4a490dd3906a0243a0713fbf138fabb451870c70b0b089c539a9624b467b0913ddc0a225a8284342e7fd31cd506dec53c1a6d8f3c8c8902b9cae +"@typescript-eslint/types@npm:8.34.0, @typescript-eslint/types@npm:^8.34.0": + version: 8.34.0 + resolution: "@typescript-eslint/types@npm:8.34.0" + checksum: 10/da4dcee51e78139bdeb5832df836528c519a22c2e39b7737ae660afe024576030165424079f423a131ad56e2dca8f033943d6b48a54b4f4d296a6f7f83f5b494 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/typescript-estree@npm:8.19.1" +"@typescript-eslint/typescript-estree@npm:8.34.0": + version: 8.34.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.34.0" dependencies: - "@typescript-eslint/types": "npm:8.19.1" - "@typescript-eslint/visitor-keys": "npm:8.19.1" + "@typescript-eslint/project-service": "npm:8.34.0" + "@typescript-eslint/tsconfig-utils": "npm:8.34.0" + "@typescript-eslint/types": "npm:8.34.0" + "@typescript-eslint/visitor-keys": "npm:8.34.0" debug: "npm:^4.3.4" fast-glob: "npm:^3.3.2" is-glob: "npm:^4.0.3" minimatch: "npm:^9.0.4" semver: "npm:^7.6.0" - ts-api-utils: "npm:^2.0.0" + ts-api-utils: "npm:^2.1.0" peerDependencies: - typescript: ">=4.8.4 <5.8.0" - checksum: 10/5de467452d5ef1a380d441b06cd0134652a0c98cdb4ce31b93eb589f7dc75ef60364d03fd80ca0a48d0c8b268f7258d4f6528b16fe1b89442d60a4bc960fe5f5 + typescript: ">=4.8.4 <5.9.0" + checksum: 10/26817d4c948253eb6a8f49fcd7a8f74c4ffeae7943aef9e1cb90d1b7adbc8e0f66605b0b318dc6eee3eda212882e278a300776b26fe4e2319712cd9822a3a4e4 languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/utils@npm:8.19.1" +"@typescript-eslint/utils@npm:8.34.0": + version: 8.34.0 + resolution: "@typescript-eslint/utils@npm:8.34.0" dependencies: - "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:8.19.1" - "@typescript-eslint/types": "npm:8.19.1" - "@typescript-eslint/typescript-estree": "npm:8.19.1" + "@eslint-community/eslint-utils": "npm:^4.7.0" + "@typescript-eslint/scope-manager": "npm:8.34.0" + "@typescript-eslint/types": "npm:8.34.0" + "@typescript-eslint/typescript-estree": "npm:8.34.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.8.0" - checksum: 10/bb92116a53fe143ee87e830941afb21d4222a64ca3f2b6dac5c2d9984f981408e60e52b04c32d95208896075ac222fb4ee631c5b0c4826b87d4bd8091c421ab1 + typescript: ">=4.8.4 <5.9.0" + checksum: 10/c51d2015e8076dd2a9d8255746889130aaf298cf9ff8f73114dcf7148f34536d47d883880eec7e3d89ec3f746c2d3f2b749e8fef5e8ad9914132deb5c013efbd languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/visitor-keys@npm:8.19.1" +"@typescript-eslint/visitor-keys@npm:8.34.0": + version: 8.34.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.34.0" dependencies: - "@typescript-eslint/types": "npm:8.19.1" + "@typescript-eslint/types": "npm:8.34.0" eslint-visitor-keys: "npm:^4.2.0" - checksum: 10/510eb196e7b7d59d3981d672a75454615159e931fe78e2a64b09607c3cfa45110709b0eb5ac3dd271d757a0d98cf4868ad2f45bf9193f96e9efec3efa92a19c1 + checksum: 10/8a591cb9f922b6fd92107ebdf255425cf7ecd56281d032d944fb38e6be319e6cca7dc49bab6ad1d46390d2ca023c3413c03775e638ec5fd70172150debf7636a languageName: node linkType: hard -"@uiw/codemirror-extensions-basic-setup@npm:4.23.7": - version: 4.23.7 - resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.23.7" +"@uiw/codemirror-extensions-basic-setup@npm:4.24.2": + version: 4.24.2 + resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.24.2" dependencies: "@codemirror/autocomplete": "npm:^6.0.0" "@codemirror/commands": "npm:^6.0.0" @@ -1095,13 +1388,13 @@ __metadata: "@codemirror/search": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: 10/7b8cbb9ccc931bc1a6909d009e653f78797e4b851aa9fb8e11f90ae1cea233e643a1951ad4ad9e10123ef166808acee1acff24732ea9fc15cd77164f6b179b91 + checksum: 10/077f64b4bbb6178038d30a3cb2961c0ff89c0836b73210d0ac3fca828c3ba8fbce61e42635ec2e2997cdffff5472b75ad58541c4e62f98d52b83d475da0c56eb languageName: node linkType: hard "@uiw/codemirror-themes@npm:^4.23.7": - version: 4.23.12 - resolution: "@uiw/codemirror-themes@npm:4.23.12" + version: 4.23.13 + resolution: "@uiw/codemirror-themes@npm:4.23.13" dependencies: "@codemirror/language": "npm:^6.0.0" "@codemirror/state": "npm:^6.0.0" @@ -1110,19 +1403,19 @@ __metadata: "@codemirror/language": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: 10/472121efadf2b9eb110030f06d657cb9693861e1ca360c81c7b0d7aa4533e6e6791737dfd14bcbe496bcb818d33f4cf6a18caf504ec628807e273441f969ef83 + checksum: 10/8c168ed1aa6185c4cc92b66f873e38f6bb50778f1ccadcc99db04cbe97c928e7b924a2f41cb40c4b26328489038d986fe9360605e1668e6c0af2ba931b79523e languageName: node linkType: hard "@uiw/react-codemirror@npm:^4.23.7": - version: 4.23.7 - resolution: "@uiw/react-codemirror@npm:4.23.7" + version: 4.24.2 + resolution: "@uiw/react-codemirror@npm:4.24.2" dependencies: "@babel/runtime": "npm:^7.18.6" "@codemirror/commands": "npm:^6.1.0" "@codemirror/state": "npm:^6.1.1" "@codemirror/theme-one-dark": "npm:^6.0.0" - "@uiw/codemirror-extensions-basic-setup": "npm:4.23.7" + "@uiw/codemirror-extensions-basic-setup": "npm:4.24.2" codemirror: "npm:^6.0.0" peerDependencies: "@babel/runtime": ">=7.11.0" @@ -1130,16 +1423,144 @@ __metadata: "@codemirror/theme-one-dark": ">=6.0.0" "@codemirror/view": ">=6.0.0" codemirror: ">=6.0.0" - react: ">=16.8.0" - react-dom: ">=16.8.0" - checksum: 10/dc6f1fd2f1863120e29ea331b2e279054bf508f00656d3fed97779f17818ae00b6cf9d74d3b0996800174d444b59702da74ca88c303db563daad85c0824fc952 + react: ">=17.0.0" + react-dom: ">=17.0.0" + checksum: 10/f0816fcf40c451c8bc15319d62f5b8d62a04c12c2bc053569212b31edba7c7164e45fd161ebb683a03fd5a50697b91a6ac4cab2d2d1a45da5f2626d959c399a0 languageName: node linkType: hard "@ungap/structured-clone@npm:^1.0.0, @ungap/structured-clone@npm:^1.2.0": - version: 1.2.1 - resolution: "@ungap/structured-clone@npm:1.2.1" - checksum: 10/6770f71e8183311b2871601ddb02d62a26373be7cf2950cb546a345a2305c75b502e36ce80166120aa2f5f1ea1562141684651ebbfcc711c58acd32035d3e545 + version: 1.3.0 + resolution: "@ungap/structured-clone@npm:1.3.0" + checksum: 10/80d6910946f2b1552a2406650051c91bbd1f24a6bf854354203d84fe2714b3e8ce4618f49cc3410494173a1c1e8e9777372fe68dce74bd45faf0a7a1a6ccf448 + languageName: node + linkType: hard + +"@unrs/resolver-binding-darwin-arm64@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-darwin-arm64@npm:1.7.13" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-darwin-x64@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-darwin-x64@npm:1.7.13" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-freebsd-x64@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-freebsd-x64@npm:1.7.13" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.7.13" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm-musleabihf@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-arm-musleabihf@npm:1.7.13" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm64-gnu@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-arm64-gnu@npm:1.7.13" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm64-musl@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-arm64-musl@npm:1.7.13" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-ppc64-gnu@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-ppc64-gnu@npm:1.7.13" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-riscv64-gnu@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-riscv64-gnu@npm:1.7.13" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-riscv64-musl@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-riscv64-musl@npm:1.7.13" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-s390x-gnu@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-s390x-gnu@npm:1.7.13" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-x64-gnu@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-x64-gnu@npm:1.7.13" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-x64-musl@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-x64-musl@npm:1.7.13" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-wasm32-wasi@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-wasm32-wasi@npm:1.7.13" + dependencies: + "@napi-rs/wasm-runtime": "npm:^0.2.11" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@unrs/resolver-binding-win32-arm64-msvc@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-win32-arm64-msvc@npm:1.7.13" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-win32-ia32-msvc@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-win32-ia32-msvc@npm:1.7.13" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@unrs/resolver-binding-win32-x64-msvc@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-win32-x64-msvc@npm:1.7.13" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"abbrev@npm:^3.0.0": + version: 3.0.1 + resolution: "abbrev@npm:3.0.1" + checksum: 10/ebd2c149dda6f543b66ce3779ea612151bb3aa9d0824f169773ee9876f1ca5a4e0adbcccc7eed048c04da7998e1825e2aa76fcca92d9e67dea50ac2b0a58dc2e languageName: node linkType: hard @@ -1153,11 +1574,18 @@ __metadata: linkType: hard "acorn@npm:^8.0.0, acorn@npm:^8.9.0": - version: 8.14.0 - resolution: "acorn@npm:8.14.0" + version: 8.15.0 + resolution: "acorn@npm:8.15.0" bin: acorn: bin/acorn - checksum: 10/6df29c35556782ca9e632db461a7f97947772c6c1d5438a81f0c873a3da3a792487e83e404d1c6c25f70513e91aa18745f6eafb1fcc3a43ecd1920b21dd173d2 + checksum: 10/77f2de5051a631cf1729c090e5759148459cdb76b5f5c70f890503d629cf5052357b0ce783c0f976dd8a93c5150f59f6d18df1def3f502396a20f81282482fa4 + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.3 + resolution: "agent-base@npm:7.1.3" + checksum: 10/3db6d8d4651f2aa1a9e4af35b96ab11a7607af57a24f3bc721a387eaa3b5f674e901f0a648b0caefd48f3fd117c7761b79a3b55854e2aebaa96c3f32cf76af84 languageName: node linkType: hard @@ -1203,6 +1631,16 @@ __metadata: languageName: node linkType: hard +"anymatch@npm:~3.1.2": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: "npm:^3.0.0" + picomatch: "npm:^2.0.4" + checksum: 10/3e044fd6d1d26545f235a9fe4d7a534e2029d8e59fa7fd9f2a6eb21230f6b5380ea1eaf55136e60cbf8e613544b3b766e7a6fa2102e2a3a117505466e3025dc2 + languageName: node + linkType: hard + "argparse@npm:^2.0.1": version: 2.0.1 resolution: "argparse@npm:2.0.1" @@ -1228,16 +1666,18 @@ __metadata: linkType: hard "array-includes@npm:^3.1.6, array-includes@npm:^3.1.8": - version: 3.1.8 - resolution: "array-includes@npm:3.1.8" + version: 3.1.9 + resolution: "array-includes@npm:3.1.9" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - es-object-atoms: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.4" - is-string: "npm:^1.0.7" - checksum: 10/290b206c9451f181fb2b1f79a3bf1c0b66bb259791290ffbada760c79b284eef6f5ae2aeb4bcff450ebc9690edd25732c4c73a3c2b340fcc0f4563aed83bf488 + es-abstract: "npm:^1.24.0" + es-object-atoms: "npm:^1.1.1" + get-intrinsic: "npm:^1.3.0" + is-string: "npm:^1.1.1" + math-intrinsics: "npm:^1.1.0" + checksum: 10/8bfe9a58df74f326b4a76b04ee05c13d871759e888b4ee8f013145297cf5eb3c02cfa216067ebdaac5d74eb9763ac5cad77cdf2773b8ab475833701e032173aa languageName: node linkType: hard @@ -1256,16 +1696,17 @@ __metadata: linkType: hard "array.prototype.findlastindex@npm:^1.2.5": - version: 1.2.5 - resolution: "array.prototype.findlastindex@npm:1.2.5" + version: 1.2.6 + resolution: "array.prototype.findlastindex@npm:1.2.6" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" + es-abstract: "npm:^1.23.9" es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10/7c5c821f357cd53ab6cc305de8086430dd8d7a2485db87b13f843e868055e9582b1fd338f02338f67fc3a1603ceaf9610dd2a470b0b506f9d18934780f95b246 + es-object-atoms: "npm:^1.1.1" + es-shim-unscopables: "npm:^1.1.0" + checksum: 10/5ddb6420e820bef6ddfdcc08ce780d0fd5e627e97457919c27e32359916de5a11ce12f7c55073555e503856618eaaa70845d6ca11dcba724766f38eb1c22f7a2 languageName: node linkType: hard @@ -1346,6 +1787,13 @@ __metadata: languageName: node linkType: hard +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10/1a09379937d846f0ce7614e75071c12826945d4e417db634156bf0e4673c495989302f52186dfa9767a1d9181794554717badd193ca2bbab046ef1da741d8efd + languageName: node + linkType: hard + "available-typed-arrays@npm:^1.0.7": version: 1.0.7 resolution: "available-typed-arrays@npm:1.0.7" @@ -1356,9 +1804,9 @@ __metadata: linkType: hard "axe-core@npm:^4.10.0": - version: 4.10.2 - resolution: "axe-core@npm:4.10.2" - checksum: 10/a69423b2ff16c15922c4ea7cf9cc5112728a2817bbe0f2cc212248d648885ffd1ba554e3a341dfc289cd9e67fc0d06f333b5c6837c5c38ca6652507381216fc1 + version: 4.10.3 + resolution: "axe-core@npm:4.10.3" + checksum: 10/9ff51ad0fd0fdec5c0247ea74e8ace5990b54c7f01f8fa3e5cd8ba98b0db24d8ebd7bab4a9bd4d75c28c4edcd1eac455b44c8c6c258c6a98f3d2f88bc60af4cc languageName: node linkType: hard @@ -1392,26 +1840,33 @@ __metadata: languageName: node linkType: hard +"binary-extensions@npm:^2.0.0": + version: 2.3.0 + resolution: "binary-extensions@npm:2.3.0" + checksum: 10/bcad01494e8a9283abf18c1b967af65ee79b0c6a9e6fcfafebfe91dbe6e0fc7272bafb73389e198b310516ae04f7ad17d79aacf6cb4c0d5d5202a7e2e52c7d98 + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": - version: 1.1.11 - resolution: "brace-expansion@npm:1.1.11" + version: 1.1.12 + resolution: "brace-expansion@npm:1.1.12" dependencies: balanced-match: "npm:^1.0.0" concat-map: "npm:0.0.1" - checksum: 10/faf34a7bb0c3fcf4b59c7808bc5d2a96a40988addf2e7e09dfbb67a2251800e0d14cd2bfc1aa79174f2f5095c54ff27f46fb1289fe2d77dac755b5eb3434cc07 + checksum: 10/12cb6d6310629e3048cadb003e1aca4d8c9bb5c67c3c321bafdd7e7a50155de081f78ea3e0ed92ecc75a9015e784f301efc8132383132f4f7904ad1ac529c562 languageName: node linkType: hard "brace-expansion@npm:^2.0.1": - version: 2.0.1 - resolution: "brace-expansion@npm:2.0.1" + version: 2.0.2 + resolution: "brace-expansion@npm:2.0.2" dependencies: balanced-match: "npm:^1.0.0" - checksum: 10/a61e7cd2e8a8505e9f0036b3b6108ba5e926b4b55089eeb5550cd04a471fe216c96d4fe7e4c7f995c728c554ae20ddfc4244cad10aef255e72b62930afd233d1 + checksum: 10/01dff195e3646bc4b0d27b63d9bab84d2ebc06121ff5013ad6e5356daa5a9d6b60fa26cf73c74797f2dc3fbec112af13578d51f75228c1112b26c790a87b0488 languageName: node linkType: hard -"braces@npm:^3.0.3": +"braces@npm:^3.0.3, braces@npm:~3.0.2": version: 3.0.3 resolution: "braces@npm:3.0.3" dependencies: @@ -1436,6 +1891,26 @@ __metadata: languageName: node linkType: hard +"cacache@npm:^19.0.1": + version: 19.0.1 + resolution: "cacache@npm:19.0.1" + dependencies: + "@npmcli/fs": "npm:^4.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^10.2.2" + lru-cache: "npm:^10.0.1" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^7.0.2" + ssri: "npm:^12.0.0" + tar: "npm:^7.4.3" + unique-filename: "npm:^4.0.0" + checksum: 10/ea026b27b13656330c2bbaa462a88181dcaa0435c1c2e705db89b31d9bdf7126049d6d0445ba746dca21454a0cfdf1d6f47fd39d34c8c8435296b30bc5738a13 + languageName: node + linkType: hard + "call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": version: 1.0.2 resolution: "call-bind-apply-helpers@npm:1.0.2" @@ -1476,42 +1951,12 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.30001579": - version: 1.0.30001690 - resolution: "caniuse-lite@npm:1.0.30001690" - checksum: 10/9fb4659eb09a298601b9593739072c481e2f5cc524bd0530e5e0f002e66246da5e866669854dfc0d53195ee36b201dab02f7933a7cdf60ccba7adb2d4a304caf + version: 1.0.30001722 + resolution: "caniuse-lite@npm:1.0.30001722" + checksum: 10/60ad52c891221c894085d450c7308f94768702bed4f0b4b99199e3c3c5926b22d7c7c05f474fcdb484befbd43d1975f8529c8ad8f66770f5c32c5342716ecd0b languageName: node linkType: hard -"canon-docs@workspace:.": - version: 0.0.0-use.local - resolution: "canon-docs@workspace:." - dependencies: - "@codemirror/lang-sass": "npm:^6.0.2" - "@lezer/highlight": "npm:^1.2.1" - "@mdx-js/loader": "npm:^3.1.0" - "@mdx-js/react": "npm:^3.1.0" - "@next/mdx": "npm:^15.1.4" - "@storybook/react": "npm:^8.6.8" - "@types/mdx": "npm:^2.0.13" - "@types/node": "npm:^20" - "@types/react": "npm:^18.0.0" - "@types/react-dom": "npm:^18.0.0" - "@uiw/codemirror-themes": "npm:^4.23.7" - "@uiw/react-codemirror": "npm:^4.23.7" - eslint: "npm:^8" - eslint-config-next: "npm:14.2.23" - lightningcss: "npm:^1.28.2" - motion: "npm:^12.4.1" - next: "npm:14.2.26" - react: "npm:^18.0.2" - react-dom: "npm:^18.0.2" - react-frame-component: "npm:^5.2.7" - shiki: "npm:^1.26.1" - storybook: "npm:^8.6.8" - typescript: "npm:^5" - languageName: unknown - linkType: soft - "ccount@npm:^2.0.0": version: 2.0.1 resolution: "ccount@npm:2.0.1" @@ -1519,7 +1964,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0": +"chalk@npm:^4.0.0, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -1557,6 +2002,32 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^3.6.0": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: 10/c327fb07704443f8d15f7b4a7ce93b2f0bc0e6cea07ec28a7570aa22cd51fcf0379df589403976ea956c369f25aa82d84561947e227cd925902e1751371658df + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10/b63cb1f73d171d140a2ed8154ee6566c8ab775d3196b0e03a2a94b5f6a0ce7777ee5685ca56849403c8d17bd457a6540672f9a60696a6137c7a409097495b82c + languageName: node + linkType: hard + "client-only@npm:0.0.1": version: 0.0.1 resolution: "client-only@npm:0.0.1" @@ -1564,6 +2035,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10/eaa5561aeb3135c2cddf7a3b3f562fc4238ff3b3fc666869ef2adf264be0f372136702f16add9299087fb1907c2e4ec5dbfe83bd24bce815c70a80c6c1a2e950 + languageName: node + linkType: hard + "codemirror@npm:^6.0.0": version: 6.0.1 resolution: "codemirror@npm:6.0.1" @@ -1595,13 +2077,33 @@ __metadata: languageName: node linkType: hard -"color-name@npm:~1.1.4": +"color-name@npm:^1.0.0, color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" checksum: 10/b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 languageName: node linkType: hard +"color-string@npm:^1.9.0": + version: 1.9.1 + resolution: "color-string@npm:1.9.1" + dependencies: + color-name: "npm:^1.0.0" + simple-swizzle: "npm:^0.2.2" + checksum: 10/72aa0b81ee71b3f4fb1ac9cd839cdbd7a011a7d318ef58e6cb13b3708dca75c7e45029697260488709f1b1c7ac4e35489a87e528156c1e365917d1c4ccb9b9cd + languageName: node + linkType: hard + +"color@npm:^4.2.3": + version: 4.2.3 + resolution: "color@npm:4.2.3" + dependencies: + color-convert: "npm:^2.0.1" + color-string: "npm:^1.9.0" + checksum: 10/b23f5e500a79ea22428db43d1a70642d983405c0dd1f95ef59dbdb9ba66afbb4773b334fa0b75bb10b0552fd7534c6b28d4db0a8b528f91975976e70973c0152 + languageName: node + linkType: hard + "comma-separated-tokens@npm:^2.0.0": version: 2.0.3 resolution: "comma-separated-tokens@npm:2.0.3" @@ -1616,14 +2118,34 @@ __metadata: languageName: node linkType: hard -"crelt@npm:^1.0.5": +"concurrently@npm:^8.2.2": + version: 8.2.2 + resolution: "concurrently@npm:8.2.2" + dependencies: + chalk: "npm:^4.1.2" + date-fns: "npm:^2.30.0" + lodash: "npm:^4.17.21" + rxjs: "npm:^7.8.1" + shell-quote: "npm:^1.8.1" + spawn-command: "npm:0.0.2" + supports-color: "npm:^8.1.1" + tree-kill: "npm:^1.2.2" + yargs: "npm:^17.7.2" + bin: + conc: dist/bin/concurrently.js + concurrently: dist/bin/concurrently.js + checksum: 10/dcb1aa69d9c611a7bda9d4fc0fe1e388f971d1744acec7e0d52dffa2ef55743f1266ec9292f414c5789b9f61734b3fce772bd005d4de9564a949fb121b97bae1 + languageName: node + linkType: hard + +"crelt@npm:^1.0.5, crelt@npm:^1.0.6": version: 1.0.6 resolution: "crelt@npm:1.0.6" checksum: 10/5ed326ca6bd243b1dba6b943f665b21c2c04be03271824bc48f20dba324b0f8233e221f8c67312526d24af2b1243c023dc05a41bd8bd05d1a479fd2c72fb39c3 languageName: node linkType: hard -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2": +"cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -1681,6 +2203,27 @@ __metadata: languageName: node linkType: hard +"date-fns@npm:^2.30.0": + version: 2.30.0 + resolution: "date-fns@npm:2.30.0" + dependencies: + "@babel/runtime": "npm:^7.21.0" + checksum: 10/70b3e8ea7aaaaeaa2cd80bd889622a4bcb5d8028b4de9162cbcda359db06e16ff6e9309e54eead5341e71031818497f19aaf9839c87d1aba1e27bb4796e758a9 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.0": + version: 4.4.1 + resolution: "debug@npm:4.4.1" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10/8e2709b2144f03c7950f8804d01ccb3786373df01e406a0f66928e47001cf2d336cbed9ee137261d4f90d68d8679468c755e3548ed83ddacdc82b194d2468afe + languageName: node + linkType: hard + "debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" @@ -1690,24 +2233,12 @@ __metadata: languageName: node linkType: hard -"debug@npm:^4.0.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.7": - version: 4.4.0 - resolution: "debug@npm:4.4.0" - dependencies: - ms: "npm:^2.1.3" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10/1847944c2e3c2c732514b93d11886575625686056cd765336212dc15de2d2b29612b6cd80e1afba767bb8e1803b778caf9973e98169ef1a24a7a7009e1820367 - languageName: node - linkType: hard - "decode-named-character-reference@npm:^1.0.0": - version: 1.0.2 - resolution: "decode-named-character-reference@npm:1.0.2" + version: 1.1.0 + resolution: "decode-named-character-reference@npm:1.1.0" dependencies: character-entities: "npm:^2.0.0" - checksum: 10/f4c71d3b93105f20076052f9cb1523a22a9c796b8296cd35eef1ca54239c78d182c136a848b83ff8da2071e3ae2b1d300bf29d00650a6d6e675438cc31b11d78 + checksum: 10/102970fde2d011f307d3789776e68defd75ba4ade1a34951affd1fabb86cd32026fd809f2658c2b600d839a57b6b6a84e2b3a45166d38c8625d66ca11cd702b8 languageName: node linkType: hard @@ -1754,10 +2285,10 @@ __metadata: languageName: node linkType: hard -"detect-libc@npm:^2.0.3": - version: 2.0.3 - resolution: "detect-libc@npm:2.0.3" - checksum: 10/b4ea018d623e077bd395f168a9e81db77370dde36a5b01d067f2ad7989924a81d31cb547ff764acb2aa25d50bb7fdde0b0a93bec02212b0cb430621623246d39 +"detect-libc@npm:^2.0.3, detect-libc@npm:^2.0.4": + version: 2.0.4 + resolution: "detect-libc@npm:2.0.4" + checksum: 10/136e995f8c5ffbc515955b0175d441b967defd3d5f2268e89fa695e9c7170d8bed17993e31a34b04f0fad33d844a3a598e0fd519a8e9be3cad5f67662d96fee0 languageName: node linkType: hard @@ -1770,6 +2301,42 @@ __metadata: languageName: node linkType: hard +"docs-ui@workspace:.": + version: 0.0.0-use.local + resolution: "docs-ui@workspace:." + dependencies: + "@codemirror/lang-sass": "npm:^6.0.2" + "@codemirror/view": "npm:^6.34.4" + "@lezer/highlight": "npm:^1.2.1" + "@mdx-js/loader": "npm:^3.1.0" + "@mdx-js/react": "npm:^3.1.0" + "@next/mdx": "npm:15.3.4" + "@remixicon/react": "npm:^4.6.0" + "@storybook/react": "npm:^8.6.8" + "@types/mdx": "npm:^2.0.13" + "@types/node": "npm:^20" + "@types/react": "npm:19.1.9" + "@types/react-dom": "npm:19.1.7" + "@uiw/codemirror-themes": "npm:^4.23.7" + "@uiw/react-codemirror": "npm:^4.23.7" + chokidar: "npm:^3.6.0" + concurrently: "npm:^8.2.2" + eslint: "npm:^8" + eslint-config-next: "npm:15.3.4" + html-react-parser: "npm:^5.2.5" + lightningcss: "npm:^1.28.2" + motion: "npm:^12.4.1" + next: "npm:15.3.4" + next-mdx-remote-client: "npm:^2.1.2" + prop-types: "npm:^15.8.1" + react: "npm:19.1.1" + react-dom: "npm:19.1.1" + shiki: "npm:^1.26.1" + storybook: "npm:^8.6.8" + typescript: "npm:^5" + languageName: unknown + linkType: soft + "doctrine@npm:^2.1.0": version: 2.1.0 resolution: "doctrine@npm:2.1.0" @@ -1788,6 +2355,44 @@ __metadata: languageName: node linkType: hard +"dom-serializer@npm:^2.0.0": + version: 2.0.0 + resolution: "dom-serializer@npm:2.0.0" + dependencies: + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.2" + entities: "npm:^4.2.0" + checksum: 10/e3bf9027a64450bca0a72297ecdc1e3abb7a2912268a9f3f5d33a2e29c1e2c3502c6e9f860fc6625940bfe0cfb57a44953262b9e94df76872fdfb8151097eeb3 + languageName: node + linkType: hard + +"domelementtype@npm:^2.3.0": + version: 2.3.0 + resolution: "domelementtype@npm:2.3.0" + checksum: 10/ee837a318ff702622f383409d1f5b25dd1024b692ef64d3096ff702e26339f8e345820f29a68bcdcea8cfee3531776b3382651232fbeae95612d6f0a75efb4f6 + languageName: node + linkType: hard + +"domhandler@npm:5.0.3, domhandler@npm:^5.0.2, domhandler@npm:^5.0.3": + version: 5.0.3 + resolution: "domhandler@npm:5.0.3" + dependencies: + domelementtype: "npm:^2.3.0" + checksum: 10/809b805a50a9c6884a29f38aec0a4e1b4537f40e1c861950ed47d10b049febe6b79ab72adaeeebb3cc8fc1cd33f34e97048a72a9265103426d93efafa78d3e96 + languageName: node + linkType: hard + +"domutils@npm:^3.2.1": + version: 3.2.2 + resolution: "domutils@npm:3.2.2" + dependencies: + dom-serializer: "npm:^2.0.0" + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.3" + checksum: 10/2e08842151aa406f50fe5e6d494f4ec73c2373199fa00d1f77b56ec604e566b7f226312ae35ab8160bb7f27a27c7285d574c8044779053e499282ca9198be210 + languageName: node + linkType: hard + "dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": version: 1.0.1 resolution: "dunder-proto@npm:1.0.1" @@ -1827,36 +2432,63 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.15.0": - version: 5.18.0 - resolution: "enhanced-resolve@npm:5.18.0" +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" dependencies: - graceful-fs: "npm:^4.2.4" - tapable: "npm:^2.2.0" - checksum: 10/e88463ef97b68d40d0da0cd0c572e23f43dba0be622d6d44eae5cafed05f0c5dac43e463a83a86c4f70186d029357f82b56d9e1e47e8fc91dce3d6602f8bd6ce + iconv-lite: "npm:^0.6.2" + checksum: 10/bb98632f8ffa823996e508ce6a58ffcf5856330fde839ae42c9e1f436cc3b5cc651d4aeae72222916545428e54fd0f6aa8862fd8d25bdbcc4589f1e3f3715e7f languageName: node linkType: hard -"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9": - version: 1.23.9 - resolution: "es-abstract@npm:1.23.9" +"entities@npm:^4.2.0": + version: 4.5.0 + resolution: "entities@npm:4.5.0" + checksum: 10/ede2a35c9bce1aeccd055a1b445d41c75a14a2bb1cd22e242f20cf04d236cdcd7f9c859eb83f76885327bfae0c25bf03303665ee1ce3d47c5927b98b0e3e3d48 + languageName: node + linkType: hard + +"entities@npm:^6.0.0": + version: 6.0.1 + resolution: "entities@npm:6.0.1" + checksum: 10/62af1307202884349d2867f0aac5c60d8b57102ea0b0e768b16246099512c28e239254ad772d6834e7e14cb1b6f153fc3d0c031934e3183b086c86d3838d874a + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10/65b5df55a8bab92229ab2b40dad3b387fad24613263d103a97f91c9fe43ceb21965cd3392b1ccb5d77088021e525c4e0481adb309625d0cb94ade1d1fb8dc17e + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 10/1d20d825cdcce8d811bfbe86340f4755c02655a7feb2f13f8c880566d9d72a3f6c92c192a6867632e490d6da67b678271f46e01044996a6443e870331100dfdd + languageName: node + linkType: hard + +"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0": + version: 1.24.0 + resolution: "es-abstract@npm:1.24.0" dependencies: array-buffer-byte-length: "npm:^1.0.2" arraybuffer.prototype.slice: "npm:^1.0.4" available-typed-arrays: "npm:^1.0.7" call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" + call-bound: "npm:^1.0.4" data-view-buffer: "npm:^1.0.2" data-view-byte-length: "npm:^1.0.2" data-view-byte-offset: "npm:^1.0.1" es-define-property: "npm:^1.0.1" es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" + es-object-atoms: "npm:^1.1.1" es-set-tostringtag: "npm:^2.1.0" es-to-primitive: "npm:^1.3.0" function.prototype.name: "npm:^1.1.8" - get-intrinsic: "npm:^1.2.7" - get-proto: "npm:^1.0.0" + get-intrinsic: "npm:^1.3.0" + get-proto: "npm:^1.0.1" get-symbol-description: "npm:^1.1.0" globalthis: "npm:^1.0.4" gopd: "npm:^1.2.0" @@ -1868,21 +2500,24 @@ __metadata: is-array-buffer: "npm:^3.0.5" is-callable: "npm:^1.2.7" is-data-view: "npm:^1.0.2" + is-negative-zero: "npm:^2.0.3" is-regex: "npm:^1.2.1" + is-set: "npm:^2.0.3" is-shared-array-buffer: "npm:^1.0.4" is-string: "npm:^1.1.1" is-typed-array: "npm:^1.1.15" - is-weakref: "npm:^1.1.0" + is-weakref: "npm:^1.1.1" math-intrinsics: "npm:^1.1.0" - object-inspect: "npm:^1.13.3" + object-inspect: "npm:^1.13.4" object-keys: "npm:^1.1.1" object.assign: "npm:^4.1.7" own-keys: "npm:^1.0.1" - regexp.prototype.flags: "npm:^1.5.3" + regexp.prototype.flags: "npm:^1.5.4" safe-array-concat: "npm:^1.1.3" safe-push-apply: "npm:^1.0.0" safe-regex-test: "npm:^1.1.0" set-proto: "npm:^1.0.0" + stop-iteration-iterator: "npm:^1.1.0" string.prototype.trim: "npm:^1.2.10" string.prototype.trimend: "npm:^1.0.9" string.prototype.trimstart: "npm:^1.0.8" @@ -1891,8 +2526,8 @@ __metadata: typed-array-byte-offset: "npm:^1.0.4" typed-array-length: "npm:^1.0.7" unbox-primitive: "npm:^1.1.0" - which-typed-array: "npm:^1.1.18" - checksum: 10/31a321966d760d88fc2ed984104841b42f4f24fc322b246002b9be0af162e03803ee41fcc3cf8be89e07a27ba3033168f877dd983703cb81422ffe5322a27582 + which-typed-array: "npm:^1.1.19" + checksum: 10/64e07a886f7439cf5ccfc100f9716e6173e10af6071a50a5031afbdde474a3dbc9619d5965da54e55f8908746a9134a46be02af8c732d574b7b81ed3124e2daf languageName: node linkType: hard @@ -1955,12 +2590,12 @@ __metadata: languageName: node linkType: hard -"es-shim-unscopables@npm:^1.0.2": - version: 1.0.2 - resolution: "es-shim-unscopables@npm:1.0.2" +"es-shim-unscopables@npm:^1.0.2, es-shim-unscopables@npm:^1.1.0": + version: 1.1.0 + resolution: "es-shim-unscopables@npm:1.1.0" dependencies: - hasown: "npm:^2.0.0" - checksum: 10/6d3bf91f658a27cc7217cd32b407a0d714393a84d125ad576319b9e83a893bea165cf41270c29e9ceaa56d3cf41608945d7e2a2c31fd51c0009b0c31402b91c7 + hasown: "npm:^2.0.2" + checksum: 10/c351f586c30bbabc62355be49564b2435468b52c3532b8a1663672e3d10dc300197e69c247869dd173e56d86423ab95fc0c10b0939cdae597094e0fdca078cba languageName: node linkType: hard @@ -2011,34 +2646,34 @@ __metadata: linkType: hard "esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0": - version: 0.25.0 - resolution: "esbuild@npm:0.25.0" + version: 0.25.5 + resolution: "esbuild@npm:0.25.5" dependencies: - "@esbuild/aix-ppc64": "npm:0.25.0" - "@esbuild/android-arm": "npm:0.25.0" - "@esbuild/android-arm64": "npm:0.25.0" - "@esbuild/android-x64": "npm:0.25.0" - "@esbuild/darwin-arm64": "npm:0.25.0" - "@esbuild/darwin-x64": "npm:0.25.0" - "@esbuild/freebsd-arm64": "npm:0.25.0" - "@esbuild/freebsd-x64": "npm:0.25.0" - "@esbuild/linux-arm": "npm:0.25.0" - "@esbuild/linux-arm64": "npm:0.25.0" - "@esbuild/linux-ia32": "npm:0.25.0" - "@esbuild/linux-loong64": "npm:0.25.0" - "@esbuild/linux-mips64el": "npm:0.25.0" - "@esbuild/linux-ppc64": "npm:0.25.0" - "@esbuild/linux-riscv64": "npm:0.25.0" - "@esbuild/linux-s390x": "npm:0.25.0" - "@esbuild/linux-x64": "npm:0.25.0" - "@esbuild/netbsd-arm64": "npm:0.25.0" - "@esbuild/netbsd-x64": "npm:0.25.0" - "@esbuild/openbsd-arm64": "npm:0.25.0" - "@esbuild/openbsd-x64": "npm:0.25.0" - "@esbuild/sunos-x64": "npm:0.25.0" - "@esbuild/win32-arm64": "npm:0.25.0" - "@esbuild/win32-ia32": "npm:0.25.0" - "@esbuild/win32-x64": "npm:0.25.0" + "@esbuild/aix-ppc64": "npm:0.25.5" + "@esbuild/android-arm": "npm:0.25.5" + "@esbuild/android-arm64": "npm:0.25.5" + "@esbuild/android-x64": "npm:0.25.5" + "@esbuild/darwin-arm64": "npm:0.25.5" + "@esbuild/darwin-x64": "npm:0.25.5" + "@esbuild/freebsd-arm64": "npm:0.25.5" + "@esbuild/freebsd-x64": "npm:0.25.5" + "@esbuild/linux-arm": "npm:0.25.5" + "@esbuild/linux-arm64": "npm:0.25.5" + "@esbuild/linux-ia32": "npm:0.25.5" + "@esbuild/linux-loong64": "npm:0.25.5" + "@esbuild/linux-mips64el": "npm:0.25.5" + "@esbuild/linux-ppc64": "npm:0.25.5" + "@esbuild/linux-riscv64": "npm:0.25.5" + "@esbuild/linux-s390x": "npm:0.25.5" + "@esbuild/linux-x64": "npm:0.25.5" + "@esbuild/netbsd-arm64": "npm:0.25.5" + "@esbuild/netbsd-x64": "npm:0.25.5" + "@esbuild/openbsd-arm64": "npm:0.25.5" + "@esbuild/openbsd-x64": "npm:0.25.5" + "@esbuild/sunos-x64": "npm:0.25.5" + "@esbuild/win32-arm64": "npm:0.25.5" + "@esbuild/win32-ia32": "npm:0.25.5" + "@esbuild/win32-x64": "npm:0.25.5" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -2092,7 +2727,14 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10/451daf6a442df29ec5d528587caa4ce783d41ff4acb93252da5a852b8d36c22e9f84d17f6721d4fbef9a1ba9855bc9fe1f167dd732c11665fe53032f2b89f114 + checksum: 10/0fa4c3b42c6ddf1a008e75a4bb3dcab08ce22ac0b31dd59dc01f7fe8e21380bfaec07a2fe3730a7cf430da5a30142d016714b358666325a4733547afa42be405 + languageName: node + linkType: hard + +"escalade@npm:^3.1.1": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10/9d7169e3965b2f9ae46971afa392f6e5a25545ea30f2e2dd99c9b0a95a3f52b5653681a84f5b2911a413ddad2d7a93d3514165072f349b5ffc59c75a899970d6 languageName: node linkType: hard @@ -2103,27 +2745,27 @@ __metadata: languageName: node linkType: hard -"eslint-config-next@npm:14.2.23": - version: 14.2.23 - resolution: "eslint-config-next@npm:14.2.23" +"eslint-config-next@npm:15.3.4": + version: 15.3.4 + resolution: "eslint-config-next@npm:15.3.4" dependencies: - "@next/eslint-plugin-next": "npm:14.2.23" - "@rushstack/eslint-patch": "npm:^1.3.3" + "@next/eslint-plugin-next": "npm:15.3.4" + "@rushstack/eslint-patch": "npm:^1.10.3" "@typescript-eslint/eslint-plugin": "npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0" "@typescript-eslint/parser": "npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0" eslint-import-resolver-node: "npm:^0.3.6" eslint-import-resolver-typescript: "npm:^3.5.2" - eslint-plugin-import: "npm:^2.28.1" - eslint-plugin-jsx-a11y: "npm:^6.7.1" - eslint-plugin-react: "npm:^7.33.2" - eslint-plugin-react-hooks: "npm:^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + eslint-plugin-import: "npm:^2.31.0" + eslint-plugin-jsx-a11y: "npm:^6.10.0" + eslint-plugin-react: "npm:^7.37.0" + eslint-plugin-react-hooks: "npm:^5.0.0" peerDependencies: - eslint: ^7.23.0 || ^8.0.0 + eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 typescript: ">=3.3.1" peerDependenciesMeta: typescript: optional: true - checksum: 10/ee23822e91c155cb0531c786cf11573b8914bc7dfa3b506c45c1d13d4b0c1b515764bd2744daa9c36bddf3bb7cfff434fa6bd169540a2fb933a336df78e7d850 + checksum: 10/6c21254d3383b9158ff5f3b2881cc702bee3d2635b4326757965945691f6e65e25fdfef4f2964382fb4b2f52d9f03b929cb71d267709727df7365e7da80c8c3a languageName: node linkType: hard @@ -2139,17 +2781,16 @@ __metadata: linkType: hard "eslint-import-resolver-typescript@npm:^3.5.2": - version: 3.7.0 - resolution: "eslint-import-resolver-typescript@npm:3.7.0" + version: 3.10.1 + resolution: "eslint-import-resolver-typescript@npm:3.10.1" dependencies: "@nolyfill/is-core-module": "npm:1.0.39" - debug: "npm:^4.3.7" - enhanced-resolve: "npm:^5.15.0" - fast-glob: "npm:^3.3.2" - get-tsconfig: "npm:^4.7.5" - is-bun-module: "npm:^1.0.2" - is-glob: "npm:^4.0.3" - stable-hash: "npm:^0.0.4" + debug: "npm:^4.4.0" + get-tsconfig: "npm:^4.10.0" + is-bun-module: "npm:^2.0.0" + stable-hash: "npm:^0.0.5" + tinyglobby: "npm:^0.2.13" + unrs-resolver: "npm:^1.6.2" peerDependencies: eslint: "*" eslint-plugin-import: "*" @@ -2159,7 +2800,7 @@ __metadata: optional: true eslint-plugin-import-x: optional: true - checksum: 10/8158730c11e562c56ed9bf7236dc75bce35b6992dc32c39ac2f4177ab77fca97b95999850204a6458054243607b54aee88c028a61fed4184f24f425fa1afff01 + checksum: 10/b8d6a9b2045c70f043f722f78c9e65bc0283126f0ad92c8f07473f7647d77f7b1562f765a472f17e06b81897b407091c0ec9f2e4592b158c9fd92d0b0c33de89 languageName: node linkType: hard @@ -2175,7 +2816,7 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-import@npm:^2.28.1": +"eslint-plugin-import@npm:^2.31.0": version: 2.31.0 resolution: "eslint-plugin-import@npm:2.31.0" dependencies: @@ -2204,7 +2845,7 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-jsx-a11y@npm:^6.7.1": +"eslint-plugin-jsx-a11y@npm:^6.10.0": version: 6.10.2 resolution: "eslint-plugin-jsx-a11y@npm:6.10.2" dependencies: @@ -2229,18 +2870,18 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-react-hooks@npm:^4.5.0 || 5.0.0-canary-7118f5dd7-20230705": - version: 5.0.0-canary-7118f5dd7-20230705 - resolution: "eslint-plugin-react-hooks@npm:5.0.0-canary-7118f5dd7-20230705" +"eslint-plugin-react-hooks@npm:^5.0.0": + version: 5.2.0 + resolution: "eslint-plugin-react-hooks@npm:5.2.0" peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - checksum: 10/0e0e80c21552998b7af2b92a74aec5e862b33f6f16c3e1303d42b5821d68b7ba11c2037bd73e3581fc7d97d9a2e64dd19e202d47d6885f53e33aac87c048d641 + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + checksum: 10/ebb79e9cf69ae06e3a7876536653c5e556b5fd8cd9dc49577f10a6e728360e7b6f5ce91f4339b33e93b26e3bb23805418f8b5e75db80baddd617b1dffe73bed1 languageName: node linkType: hard -"eslint-plugin-react@npm:^7.33.2": - version: 7.37.3 - resolution: "eslint-plugin-react@npm:7.37.3" +"eslint-plugin-react@npm:^7.37.0": + version: 7.37.5 + resolution: "eslint-plugin-react@npm:7.37.5" dependencies: array-includes: "npm:^3.1.8" array.prototype.findlast: "npm:^1.2.5" @@ -2252,7 +2893,7 @@ __metadata: hasown: "npm:^2.0.2" jsx-ast-utils: "npm:^2.4.1 || ^3.0.0" minimatch: "npm:^3.1.2" - object.entries: "npm:^1.1.8" + object.entries: "npm:^1.1.9" object.fromentries: "npm:^2.0.8" object.values: "npm:^1.2.1" prop-types: "npm:^15.8.1" @@ -2262,7 +2903,7 @@ __metadata: string.prototype.repeat: "npm:^1.0.0" peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - checksum: 10/30042b95c440a962157192f67369d8c9171f7c73e992695e5674c9d28c3cfe4098488eca86dfa7c433d4c8a91243cbafdc80c7e42d2c1720a427ddb36e65457e + checksum: 10/ee1bd4e0ec64f29109d5a625bb703d179c82e0159c86c3f1b52fc1209d2994625a137dae303c333fb308a2e38315e44066d5204998177e31974382f9fda25d5c languageName: node linkType: hard @@ -2284,9 +2925,9 @@ __metadata: linkType: hard "eslint-visitor-keys@npm:^4.2.0": - version: 4.2.0 - resolution: "eslint-visitor-keys@npm:4.2.0" - checksum: 10/9651b3356b01760e586b4c631c5268c0e1a85236e3292bf754f0472f465bf9a856c0ddc261fceace155334118c0151778effafbab981413dbf9288349343fa25 + version: 4.2.1 + resolution: "eslint-visitor-keys@npm:4.2.1" + checksum: 10/3ee00fc6a7002d4b0ffd9dc99e13a6a7882c557329e6c25ab254220d71e5c9c4f89dca4695352949ea678eb1f3ba912a18ef8aac0a7fe094196fd92f441bfce2 languageName: node linkType: hard @@ -2459,6 +3100,13 @@ __metadata: languageName: node linkType: hard +"exponential-backoff@npm:^3.1.1": + version: 3.1.2 + resolution: "exponential-backoff@npm:3.1.2" + checksum: 10/ca2f01f1aa4dafd3f3917bd531ab5be08c6f5f4b2389d2e974f903de3cbeb50b9633374353516b6afd70905775e33aba11afab1232d3acf0aa2963b98a611c51 + languageName: node + linkType: hard + "extend@npm:^3.0.0": version: 3.0.2 resolution: "extend@npm:3.0.2" @@ -2473,6 +3121,19 @@ __metadata: languageName: node linkType: hard +"fast-glob@npm:3.3.1": + version: 3.3.1 + resolution: "fast-glob@npm:3.3.1" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.4" + checksum: 10/51bcd15472879dfe51d4b01c5b70bbc7652724d39cdd082ba11276dbd7d84db0f6b33757e1938af8b2768a4bf485d9be0c89153beae24ee8331d6dcc7550379f + languageName: node + linkType: hard + "fast-glob@npm:^3.3.2": version: 3.3.3 resolution: "fast-glob@npm:3.3.3" @@ -2501,11 +3162,23 @@ __metadata: linkType: hard "fastq@npm:^1.6.0": - version: 1.18.0 - resolution: "fastq@npm:1.18.0" + version: 1.19.1 + resolution: "fastq@npm:1.19.1" dependencies: reusify: "npm:^1.0.4" - checksum: 10/c5b501333dc8f5d188d828ea162aad03ff5a81aed185b6d4a5078aaeae0a42babc34296d7af13ebce86401cccd93c9b7b3cbf61280821c5f20af233378b42fbb + checksum: 10/75679dc226316341c4f2a6b618571f51eac96779906faecd8921b984e844d6ae42fabb2df69b1071327d398d5716693ea9c9c8941f64ac9e89ec2032ce59d730 + languageName: node + linkType: hard + +"fdir@npm:^6.4.4": + version: 6.4.6 + resolution: "fdir@npm:6.4.6" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10/c186ba387e7b75ccf874a098d9bc5fe0af0e9c52fc56f8eac8e80aa4edb65532684bf2bf769894ff90f53bf221d6136692052d31f07a9952807acae6cbe7ee50 languageName: node linkType: hard @@ -2549,9 +3222,9 @@ __metadata: linkType: hard "flatted@npm:^3.2.9": - version: 3.3.2 - resolution: "flatted@npm:3.3.2" - checksum: 10/ac3c159742e01d0e860a861164bcfd35bb567ccbebb8a0dd041e61cf3c64a435b917dd1e7ed1c380c2ebca85735fb16644485ec33665bc6aafc3b316aa1eed44 + version: 3.3.3 + resolution: "flatted@npm:3.3.3" + checksum: 10/8c96c02fbeadcf4e8ffd0fa24983241e27698b0781295622591fc13585e2f226609d95e422bcf2ef044146ffacb6b68b1f20871454eddf75ab3caa6ee5f4a1fe languageName: node linkType: hard @@ -2565,21 +3238,21 @@ __metadata: linkType: hard "foreground-child@npm:^3.1.0": - version: 3.3.0 - resolution: "foreground-child@npm:3.3.0" + version: 3.3.1 + resolution: "foreground-child@npm:3.3.1" dependencies: - cross-spawn: "npm:^7.0.0" + cross-spawn: "npm:^7.0.6" signal-exit: "npm:^4.0.1" - checksum: 10/e3a60480f3a09b12273ce2c5fcb9514d98dd0e528f58656a1b04680225f918d60a2f81f6a368f2f3b937fcee9cfc0cbf16f1ad9a0bc6a3a6e103a84c9a90087e + checksum: 10/427b33f997a98073c0424e5c07169264a62cda806d8d2ded159b5b903fdfc8f0a1457e06b5fc35506497acb3f1e353f025edee796300209ac6231e80edece835 languageName: node linkType: hard -"framer-motion@npm:^12.5.0": - version: 12.5.0 - resolution: "framer-motion@npm:12.5.0" +"framer-motion@npm:^12.17.0": + version: 12.17.0 + resolution: "framer-motion@npm:12.17.0" dependencies: - motion-dom: "npm:^12.5.0" - motion-utils: "npm:^12.5.0" + motion-dom: "npm:^12.17.0" + motion-utils: "npm:^12.12.1" tslib: "npm:^2.4.0" peerDependencies: "@emotion/is-prop-valid": "*" @@ -2592,7 +3265,16 @@ __metadata: optional: true react-dom: optional: true - checksum: 10/6659e0b704d3216dafdcb11175e3c5f55d536469ddd8ad62b37d00bc7e893d914989791c018cfdfa17b3142b655f0d85226adc94ed390c1524312c3653b8c75f + checksum: 10/803b57cb7ec7200938c9425392a66839c96bf5a800ff14b38d9e689a47341678c53ce54765f68f0a186cfad6c1270b2ad46a75aeff67b2a2e2f3b846852d792c + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10/af143246cf6884fe26fa281621d45cfe111d34b30535a475bfa38dafe343dadb466c047a924ffc7d6b7b18265df4110224ce3803806dbb07173bf2087b648d7f languageName: node linkType: hard @@ -2603,6 +3285,25 @@ __metadata: languageName: node linkType: hard +"fsevents@npm:~2.3.2": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10/4c1ade961ded57cdbfbb5cac5106ec17bc8bccd62e16343c569a0ceeca83b9dfef87550b4dc5cbb89642da412b20c5071f304c8c464b80415446e8e155a038c0 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + "function-bind@npm:^1.1.2": version: 1.1.2 resolution: "function-bind@npm:1.1.2" @@ -2631,6 +3332,13 @@ __metadata: languageName: node linkType: hard +"get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10/b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9 + languageName: node + linkType: hard + "get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": version: 1.3.0 resolution: "get-intrinsic@npm:1.3.0" @@ -2670,16 +3378,16 @@ __metadata: languageName: node linkType: hard -"get-tsconfig@npm:^4.7.5": - version: 4.8.1 - resolution: "get-tsconfig@npm:4.8.1" +"get-tsconfig@npm:^4.10.0": + version: 4.10.1 + resolution: "get-tsconfig@npm:4.10.1" dependencies: resolve-pkg-maps: "npm:^1.0.0" - checksum: 10/3fb5a8ad57b9633eaea085d81661e9e5c9f78b35d8f8689eaf8b8b45a2a3ebf3b3422266d4d7df765e308cc1e6231648d114803ab3d018332e29916f2c1de036 + checksum: 10/04d63f47fdecaefbd1f73ec02949be4ec4db7d6d9fbc8d4e81f9a4bb1c6f876e48943712f2f9236643d3e4d61d9a7b06da08564d08b034631ebe3f5605bef237 languageName: node linkType: hard -"glob-parent@npm:^5.1.2": +"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" dependencies: @@ -2697,18 +3405,19 @@ __metadata: languageName: node linkType: hard -"glob@npm:10.3.10": - version: 10.3.10 - resolution: "glob@npm:10.3.10" +"glob@npm:^10.2.2": + version: 10.4.5 + resolution: "glob@npm:10.4.5" dependencies: foreground-child: "npm:^3.1.0" - jackspeak: "npm:^2.3.5" - minimatch: "npm:^9.0.1" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry: "npm:^1.10.1" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" bin: glob: dist/esm/bin.mjs - checksum: 10/38bdb2c9ce75eb5ed168f309d4ed05b0798f640b637034800a6bf306f39d35409bf278b0eaaffaec07591085d3acb7184a201eae791468f0f617771c2486a6a8 + checksum: 10/698dfe11828b7efd0514cd11e573eaed26b2dff611f0400907281ce3eab0c1e56143ef9b35adc7c77ecc71fba74717b510c7c223d34ca8a98ec81777b293d4ac languageName: node linkType: hard @@ -2752,7 +3461,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4": +"graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 @@ -2814,7 +3523,7 @@ __metadata: languageName: node linkType: hard -"hasown@npm:^2.0.0, hasown@npm:^2.0.2": +"hasown@npm:^2.0.2": version: 2.0.2 resolution: "hasown@npm:2.0.2" dependencies: @@ -2824,8 +3533,8 @@ __metadata: linkType: hard "hast-util-to-estree@npm:^3.0.0": - version: 3.1.1 - resolution: "hast-util-to-estree@npm:3.1.1" + version: 3.1.3 + resolution: "hast-util-to-estree@npm:3.1.3" dependencies: "@types/estree": "npm:^1.0.0" "@types/estree-jsx": "npm:^1.0.0" @@ -2838,18 +3547,18 @@ __metadata: mdast-util-mdx-expression: "npm:^2.0.0" mdast-util-mdx-jsx: "npm:^3.0.0" mdast-util-mdxjs-esm: "npm:^2.0.0" - property-information: "npm:^6.0.0" + property-information: "npm:^7.0.0" space-separated-tokens: "npm:^2.0.0" - style-to-object: "npm:^1.0.0" + style-to-js: "npm:^1.0.0" unist-util-position: "npm:^5.0.0" zwitch: "npm:^2.0.0" - checksum: 10/f72b0d72d6f3e1d55a876e175716b78bf93b8246fac8dd6db6b46c03b0288d03f40644606f36c00e8f12829a6bef0e7a73b405825b651a94b41f0bc5b53b92b4 + checksum: 10/efe69c8af68f021d853e70916c6e940765be519aec8703765898c1c3814424b0470f70c0272cf4ac06dcaf6d6f3cc781ebf034701ed240a33ac691d1f5eaf65b languageName: node linkType: hard "hast-util-to-html@npm:^9.0.4": - version: 9.0.4 - resolution: "hast-util-to-html@npm:9.0.4" + version: 9.0.5 + resolution: "hast-util-to-html@npm:9.0.5" dependencies: "@types/hast": "npm:^3.0.0" "@types/unist": "npm:^3.0.0" @@ -2858,17 +3567,17 @@ __metadata: hast-util-whitespace: "npm:^3.0.0" html-void-elements: "npm:^3.0.0" mdast-util-to-hast: "npm:^13.0.0" - property-information: "npm:^6.0.0" + property-information: "npm:^7.0.0" space-separated-tokens: "npm:^2.0.0" stringify-entities: "npm:^4.0.0" zwitch: "npm:^2.0.4" - checksum: 10/a0b4ed9058e57fa2ca010d10c077fda78d2ab2af99f5bd09fe4b9948970025ac4a2a1a03ec7e2e0f3b0444066b1b35d602fa3e9fbd9b7fc9cdd35d0cafa909ca + checksum: 10/4278e5246f43adf7a30bf3b87db4a4e628b5362b8c149eb4360bf7e0f731f81fa31e4e844e8090d1dd5f739d0884678f8165b28f97c14139e4da4f945f9ff5f7 languageName: node linkType: hard "hast-util-to-jsx-runtime@npm:^2.0.0": - version: 2.3.2 - resolution: "hast-util-to-jsx-runtime@npm:2.3.2" + version: 2.3.6 + resolution: "hast-util-to-jsx-runtime@npm:2.3.6" dependencies: "@types/estree": "npm:^1.0.0" "@types/hast": "npm:^3.0.0" @@ -2880,12 +3589,12 @@ __metadata: mdast-util-mdx-expression: "npm:^2.0.0" mdast-util-mdx-jsx: "npm:^3.0.0" mdast-util-mdxjs-esm: "npm:^2.0.0" - property-information: "npm:^6.0.0" + property-information: "npm:^7.0.0" space-separated-tokens: "npm:^2.0.0" - style-to-object: "npm:^1.0.0" + style-to-js: "npm:^1.0.0" unist-util-position: "npm:^5.0.0" vfile-message: "npm:^4.0.0" - checksum: 10/3d72f83e2d8c29adc6576d2c6b41479902fd51fac8cfb2b67c35fd68fcb9c25c274699442e4dee901a7ab926a0ff6851713ed5d92448ac09ae0f10daf293476c + checksum: 10/111bd69f482952c7591cb4e1d3face25f1c18849b310a4d6cacc91e2d2cbc965d455fad35c059b8f0cfd762e933b826a7090b6f3098dece08307a6569de8f1d8 languageName: node linkType: hard @@ -2898,6 +3607,34 @@ __metadata: languageName: node linkType: hard +"html-dom-parser@npm:5.1.1": + version: 5.1.1 + resolution: "html-dom-parser@npm:5.1.1" + dependencies: + domhandler: "npm:5.0.3" + htmlparser2: "npm:10.0.0" + checksum: 10/85bbaa8699d40d54fd64f5472088199178a6c3dd6a907358ac476d9c511cb8ab10bff1469a785411e8b831a73ac08348392e56076eab6d100a3ec19dccc722ad + languageName: node + linkType: hard + +"html-react-parser@npm:^5.2.5": + version: 5.2.6 + resolution: "html-react-parser@npm:5.2.6" + dependencies: + domhandler: "npm:5.0.3" + html-dom-parser: "npm:5.1.1" + react-property: "npm:2.0.2" + style-to-js: "npm:1.1.17" + peerDependencies: + "@types/react": 0.14 || 15 || 16 || 17 || 18 || 19 + react: 0.14 || 15 || 16 || 17 || 18 || 19 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/be2903fd932d44ff6cae66bd18b025d318879e16eb1e67c0ae6dc640e5c77509da41b61948c0d404a43b5362b57b7cfa3963df242b9773d4c7a16f410db7f2ac + languageName: node + linkType: hard + "html-void-elements@npm:^3.0.0": version: 3.0.0 resolution: "html-void-elements@npm:3.0.0" @@ -2905,20 +3642,75 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.2.0, ignore@npm:^5.3.1": +"htmlparser2@npm:10.0.0": + version: 10.0.0 + resolution: "htmlparser2@npm:10.0.0" + dependencies: + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.3" + domutils: "npm:^3.2.1" + entities: "npm:^6.0.0" + checksum: 10/768870f0e020dca19dc45df206cb6ac466c5dba6566c8fca4ca880347eed409f9977028d08644ac516bca8628ac9c7ded5a3847dc3ee1c043f049abf9e817154 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 10/4efd2dfcfeea9d5e88c84af450b9980be8a43c2c8179508b1c57c7b4421c855f3e8efe92fa53e0b3f4a43c85824ada930eabbc306d1b3beab750b6dcc5187693 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10/d062acfa0cb82beeb558f1043c6ba770ea892b5fb7b28654dbc70ea2aeea55226dd34c02a294f6c1ca179a5aa483c4ea641846821b182edbd9cc5d89b54c6848 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10/784b628cbd55b25542a9d85033bdfd03d4eda630fb8b3c9477959367f3be95dc476ed2ecbb9836c359c7c698027fc7b45723a302324433590f45d6c1706e8c13 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10/24e3292dd3dadaa81d065c6f8c41b274a47098150d444b96e5f53b4638a9a71482921ea6a91a1f59bb71d9796de25e04afd05919fa64c360347ba65d3766f10f + languageName: node + linkType: hard + +"ignore@npm:^5.2.0": version: 5.3.2 resolution: "ignore@npm:5.3.2" checksum: 10/cceb6a457000f8f6a50e1196429750d782afce5680dd878aa4221bd79972d68b3a55b4b1458fc682be978f4d3c6a249046aa0880637367216444ab7b014cfc98 languageName: node linkType: hard +"ignore@npm:^7.0.0": + version: 7.0.5 + resolution: "ignore@npm:7.0.5" + checksum: 10/f134b96a4de0af419196f52c529d5c6120c4456ff8a6b5a14ceaaa399f883e15d58d2ce651c9b69b9388491d4669dda47285d307e827de9304a53a1824801bc6 + languageName: node + linkType: hard + "import-fresh@npm:^3.2.1": - version: 3.3.0 - resolution: "import-fresh@npm:3.3.0" + version: 3.3.1 + resolution: "import-fresh@npm:3.3.1" dependencies: parent-module: "npm:^1.0.0" resolve-from: "npm:^4.0.0" - checksum: 10/2cacfad06e652b1edc50be650f7ec3be08c5e5a6f6d12d035c440a42a8cc028e60a5b99ca08a77ab4d6b1346da7d971915828f33cdab730d3d42f08242d09baa + checksum: 10/a06b19461b4879cc654d46f8a6244eb55eb053437afd4cbb6613cad6be203811849ed3e4ea038783092879487299fda24af932b86bdfff67c9055ba3612b8c87 languageName: node linkType: hard @@ -2964,6 +3756,16 @@ __metadata: languageName: node linkType: hard +"ip-address@npm:^9.0.5": + version: 9.0.5 + resolution: "ip-address@npm:9.0.5" + dependencies: + jsbn: "npm:1.1.0" + sprintf-js: "npm:^1.1.3" + checksum: 10/1ed81e06721af012306329b31f532b5e24e00cb537be18ddc905a84f19fe8f83a09a1699862bf3a1ec4b9dea93c55a3fa5faf8b5ea380431469df540f38b092c + languageName: node + linkType: hard + "is-alphabetical@npm:^2.0.0": version: 2.0.1 resolution: "is-alphabetical@npm:2.0.1" @@ -3002,15 +3804,23 @@ __metadata: languageName: node linkType: hard +"is-arrayish@npm:^0.3.1": + version: 0.3.2 + resolution: "is-arrayish@npm:0.3.2" + checksum: 10/81a78d518ebd8b834523e25d102684ee0f7e98637136d3bdc93fd09636350fa06f1d8ca997ea28143d4d13cb1b69c0824f082db0ac13e1ab3311c10ffea60ade + languageName: node + linkType: hard + "is-async-function@npm:^2.0.0": - version: 2.1.0 - resolution: "is-async-function@npm:2.1.0" + version: 2.1.1 + resolution: "is-async-function@npm:2.1.1" dependencies: + async-function: "npm:^1.0.0" call-bound: "npm:^1.0.3" get-proto: "npm:^1.0.1" has-tostringtag: "npm:^1.0.2" safe-regex-test: "npm:^1.1.0" - checksum: 10/865f0e915b7d9aa5577327e7550bf77a4bb2b7bca497d32564e1d32dbe0ccb7eca1c9c56dd679b6dd2bd7feddb91574e773922276871a5958e53ae8473db4742 + checksum: 10/7c2ac7efdf671e03265e74a043bcb1c0a32e226bc2a42dfc5ec8644667df668bbe14b91c08e6c1414f392f8cf86cd1d489b3af97756e2c7a49dd1ba63fd40ca6 languageName: node linkType: hard @@ -3023,22 +3833,31 @@ __metadata: languageName: node linkType: hard -"is-boolean-object@npm:^1.2.1": - version: 1.2.1 - resolution: "is-boolean-object@npm:1.2.1" +"is-binary-path@npm:~2.1.0": + version: 2.1.0 + resolution: "is-binary-path@npm:2.1.0" dependencies: - call-bound: "npm:^1.0.2" - has-tostringtag: "npm:^1.0.2" - checksum: 10/5a15524635c9334ebbd668f20a6cbf023adceed5725ec96a50056d21ae65f52759d04a8fa7d7febf00ff3bc4e6d3837638eb84be572f287bcfd15f8b8facde43 + binary-extensions: "npm:^2.0.0" + checksum: 10/078e51b4f956c2c5fd2b26bb2672c3ccf7e1faff38e0ebdba45612265f4e3d9fc3127a1fa8370bbf09eab61339203c3d3b7af5662cbf8be4030f8fac37745b0e languageName: node linkType: hard -"is-bun-module@npm:^1.0.2": - version: 1.3.0 - resolution: "is-bun-module@npm:1.3.0" +"is-boolean-object@npm:^1.2.1": + version: 1.2.2 + resolution: "is-boolean-object@npm:1.2.2" dependencies: - semver: "npm:^7.6.3" - checksum: 10/b23d9ec7b4d4bfd89e4e72b5cd52e1bc153facad59fdd7394c656f8859a78740ef35996a2066240a32f39cc9a9da4b4eb69e68df3c71755a61ebbaf56d3daef0 + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10/051fa95fdb99d7fbf653165a7e6b2cba5d2eb62f7ffa81e793a790f3fb5366c91c1b7b6af6820aa2937dd86c73aa3ca9d9ca98f500988457b1c59692c52ba911 + languageName: node + linkType: hard + +"is-bun-module@npm:^2.0.0": + version: 2.0.0 + resolution: "is-bun-module@npm:2.0.0" + dependencies: + semver: "npm:^7.7.1" + checksum: 10/cded5a1a58368b847872d08617975d620ad94426d76a932f3e08d55b4574d199e0a62a4fb024fa2dc444200b71719eb0bffc5d3d1e1cc82e29b293bb8d66a990 languageName: node linkType: hard @@ -3130,7 +3949,7 @@ __metadata: languageName: node linkType: hard -"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3": +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": version: 4.0.3 resolution: "is-glob@npm:4.0.3" dependencies: @@ -3153,6 +3972,13 @@ __metadata: languageName: node linkType: hard +"is-negative-zero@npm:^2.0.3": + version: 2.0.3 + resolution: "is-negative-zero@npm:2.0.3" + checksum: 10/8fe5cffd8d4fb2ec7b49d657e1691889778d037494c6f40f4d1a524cadd658b4b53ad7b6b73a59bcb4b143ae9a3d15829af864b2c0f9d65ac1e678c4c80f17e5 + languageName: node + linkType: hard + "is-number-object@npm:^1.1.1": version: 1.1.1 resolution: "is-number-object@npm:1.1.1" @@ -3212,7 +4038,7 @@ __metadata: languageName: node linkType: hard -"is-string@npm:^1.0.7, is-string@npm:^1.1.1": +"is-string@npm:^1.1.1": version: 1.1.1 resolution: "is-string@npm:1.1.1" dependencies: @@ -3249,12 +4075,12 @@ __metadata: languageName: node linkType: hard -"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.0": - version: 1.1.0 - resolution: "is-weakref@npm:1.1.0" +"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.1": + version: 1.1.1 + resolution: "is-weakref@npm:1.1.1" dependencies: - call-bound: "npm:^1.0.2" - checksum: 10/89e627cc1763ea110574bb408fcf060ede47e70437d9278858bc939e3b3f7e4b7c558610b733da5f2ad6084d9f12b9c714b011ccf3fa771ec87e221c22bed910 + call-bound: "npm:^1.0.3" + checksum: 10/543506fd8259038b371bb083aac25b16cb4fd8b12fc58053aa3d45ac28dfd001cd5c6dffbba7aeea4213c74732d46b6cb2cfb5b412eed11f2db524f3f97d09a0 languageName: node linkType: hard @@ -3291,6 +4117,13 @@ __metadata: languageName: node linkType: hard +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 10/7fe1931ee4e88eb5aa524cd3ceb8c882537bc3a81b02e438b240e47012eef49c86904d0f0e593ea7c3a9996d18d0f1f3be8d3eaa92333977b0c3a9d353d5563e + languageName: node + linkType: hard + "iterator.prototype@npm:^1.1.4": version: 1.1.5 resolution: "iterator.prototype@npm:1.1.5" @@ -3305,20 +4138,20 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^2.3.5": - version: 2.3.6 - resolution: "jackspeak@npm:2.3.6" +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" dependencies: "@isaacs/cliui": "npm:^8.0.2" "@pkgjs/parseargs": "npm:^0.11.0" dependenciesMeta: "@pkgjs/parseargs": optional: true - checksum: 10/6e6490d676af8c94a7b5b29b8fd5629f21346911ebe2e32931c2a54210134408171c24cee1a109df2ec19894ad04a429402a8438cbf5cc2794585d35428ace76 + checksum: 10/96f8786eaab98e4bf5b2a5d6d9588ea46c4d06bbc4f2eb861fdd7b6b182b16f71d8a70e79820f335d52653b16d4843b29dd9cdcf38ae80406756db9199497cf3 languageName: node linkType: hard -"js-tokens@npm:^3.0.0 || ^4.0.0": +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" checksum: 10/af37d0d913fb56aec6dc0074c163cc71cd23c0b8aad5c2350747b6721d37ba118af35abdd8b33c47ec2800de07dedb16a527ca9c530ee004093e04958bd0cbf2 @@ -3336,6 +4169,13 @@ __metadata: languageName: node linkType: hard +"jsbn@npm:1.1.0": + version: 1.1.0 + resolution: "jsbn@npm:1.1.0" + checksum: 10/bebe7ae829bbd586ce8cbe83501dd8cb8c282c8902a8aeeed0a073a89dc37e8103b1244f3c6acd60278bcbfe12d93a3f83c9ac396868a3b3bbc3c5e5e3b648ef + languageName: node + linkType: hard + "jsdoc-type-pratt-parser@npm:^4.0.0": version: 4.1.0 resolution: "jsdoc-type-pratt-parser@npm:4.1.0" @@ -3422,91 +4262,91 @@ __metadata: languageName: node linkType: hard -"lightningcss-darwin-arm64@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-darwin-arm64@npm:1.29.3" +"lightningcss-darwin-arm64@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-darwin-arm64@npm:1.30.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"lightningcss-darwin-x64@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-darwin-x64@npm:1.29.3" +"lightningcss-darwin-x64@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-darwin-x64@npm:1.30.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"lightningcss-freebsd-x64@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-freebsd-x64@npm:1.29.3" +"lightningcss-freebsd-x64@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-freebsd-x64@npm:1.30.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"lightningcss-linux-arm-gnueabihf@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-linux-arm-gnueabihf@npm:1.29.3" +"lightningcss-linux-arm-gnueabihf@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.30.1" conditions: os=linux & cpu=arm languageName: node linkType: hard -"lightningcss-linux-arm64-gnu@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-linux-arm64-gnu@npm:1.29.3" +"lightningcss-linux-arm64-gnu@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-linux-arm64-gnu@npm:1.30.1" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"lightningcss-linux-arm64-musl@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-linux-arm64-musl@npm:1.29.3" +"lightningcss-linux-arm64-musl@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-linux-arm64-musl@npm:1.30.1" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"lightningcss-linux-x64-gnu@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-linux-x64-gnu@npm:1.29.3" +"lightningcss-linux-x64-gnu@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-linux-x64-gnu@npm:1.30.1" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"lightningcss-linux-x64-musl@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-linux-x64-musl@npm:1.29.3" +"lightningcss-linux-x64-musl@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-linux-x64-musl@npm:1.30.1" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"lightningcss-win32-arm64-msvc@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-win32-arm64-msvc@npm:1.29.3" +"lightningcss-win32-arm64-msvc@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-win32-arm64-msvc@npm:1.30.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"lightningcss-win32-x64-msvc@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-win32-x64-msvc@npm:1.29.3" +"lightningcss-win32-x64-msvc@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-win32-x64-msvc@npm:1.30.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "lightningcss@npm:^1.28.2": - version: 1.29.3 - resolution: "lightningcss@npm:1.29.3" + version: 1.30.1 + resolution: "lightningcss@npm:1.30.1" dependencies: detect-libc: "npm:^2.0.3" - lightningcss-darwin-arm64: "npm:1.29.3" - lightningcss-darwin-x64: "npm:1.29.3" - lightningcss-freebsd-x64: "npm:1.29.3" - lightningcss-linux-arm-gnueabihf: "npm:1.29.3" - lightningcss-linux-arm64-gnu: "npm:1.29.3" - lightningcss-linux-arm64-musl: "npm:1.29.3" - lightningcss-linux-x64-gnu: "npm:1.29.3" - lightningcss-linux-x64-musl: "npm:1.29.3" - lightningcss-win32-arm64-msvc: "npm:1.29.3" - lightningcss-win32-x64-msvc: "npm:1.29.3" + lightningcss-darwin-arm64: "npm:1.30.1" + lightningcss-darwin-x64: "npm:1.30.1" + lightningcss-freebsd-x64: "npm:1.30.1" + lightningcss-linux-arm-gnueabihf: "npm:1.30.1" + lightningcss-linux-arm64-gnu: "npm:1.30.1" + lightningcss-linux-arm64-musl: "npm:1.30.1" + lightningcss-linux-x64-gnu: "npm:1.30.1" + lightningcss-linux-x64-musl: "npm:1.30.1" + lightningcss-win32-arm64-msvc: "npm:1.30.1" + lightningcss-win32-x64-msvc: "npm:1.30.1" dependenciesMeta: lightningcss-darwin-arm64: optional: true @@ -3528,7 +4368,7 @@ __metadata: optional: true lightningcss-win32-x64-msvc: optional: true - checksum: 10/90933a666975f8e4f2b1bed1ec698a0072f96d2cd118e358932456a46105d62b72b701e4a5da5bd66f0b6e3beac2c65d0be2306acdf3f30603d86143502cce88 + checksum: 10/6c921135216cc498dfcb87e35dffe8432e99306cbd58009c598b1daf20c81cc14535abbd4c1066e5d1faf4080ed44a2995e8ecc343633db4897a2d041b76fb05 languageName: node linkType: hard @@ -3548,6 +4388,13 @@ __metadata: languageName: node linkType: hard +"lodash@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532 + languageName: node + linkType: hard + "longest-streak@npm:^3.0.0": version: 3.1.0 resolution: "longest-streak@npm:3.1.0" @@ -3555,7 +4402,7 @@ __metadata: languageName: node linkType: hard -"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": +"loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" dependencies: @@ -3566,13 +4413,32 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^10.2.0": +"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": version: 10.4.3 resolution: "lru-cache@npm:10.4.3" checksum: 10/e6e90267360476720fa8e83cc168aa2bf0311f3f2eea20a6ba78b90a885ae72071d9db132f40fda4129c803e7dcec3a6b6a6fbb44ca90b081630b810b5d6a41a languageName: node linkType: hard +"make-fetch-happen@npm:^14.0.3": + version: 14.0.3 + resolution: "make-fetch-happen@npm:14.0.3" + dependencies: + "@npmcli/agent": "npm:^3.0.0" + cacache: "npm:^19.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^4.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^1.0.0" + proc-log: "npm:^5.0.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^12.0.0" + checksum: 10/fce0385840b6d86b735053dfe941edc2dd6468fda80fe74da1eeff10cbd82a75760f406194f2bc2fa85b99545b2bc1f84c08ddf994b21830775ba2d1a87e8bdf + languageName: node + linkType: hard + "markdown-extensions@npm:^2.0.0": version: 2.0.0 resolution: "markdown-extensions@npm:2.0.0" @@ -3622,8 +4488,8 @@ __metadata: linkType: hard "mdast-util-mdx-jsx@npm:^3.0.0": - version: 3.1.3 - resolution: "mdast-util-mdx-jsx@npm:3.1.3" + version: 3.2.0 + resolution: "mdast-util-mdx-jsx@npm:3.2.0" dependencies: "@types/estree-jsx": "npm:^1.0.0" "@types/hast": "npm:^3.0.0" @@ -3637,7 +4503,7 @@ __metadata: stringify-entities: "npm:^4.0.0" unist-util-stringify-position: "npm:^4.0.0" vfile-message: "npm:^4.0.0" - checksum: 10/6c14f271f1380fd512038247f45887b7aa71bbf4acd8881651a317b61706b114f2582f62f7777d0eacd42c4a7b979802825c2a2fd8bb7c46a1ab931ccb1ddf3e + checksum: 10/62cd650a522e5d72ea6afd6d4a557fc86525b802d097a29a2fbe17d22e7b97c502a580611873e4d685777fe77c6ff8d39fb6e37d026b3acbc86c3b24927f4ad9 languageName: node linkType: hard @@ -3654,7 +4520,7 @@ __metadata: languageName: node linkType: hard -"mdast-util-mdxjs-esm@npm:^2.0.0": +"mdast-util-mdxjs-esm@npm:^2.0.0, mdast-util-mdxjs-esm@npm:^2.0.1": version: 2.0.1 resolution: "mdast-util-mdxjs-esm@npm:2.0.1" dependencies: @@ -3729,8 +4595,8 @@ __metadata: linkType: hard "micromark-core-commonmark@npm:^2.0.0": - version: 2.0.2 - resolution: "micromark-core-commonmark@npm:2.0.2" + version: 2.0.3 + resolution: "micromark-core-commonmark@npm:2.0.3" dependencies: decode-named-character-reference: "npm:^1.0.0" devlop: "npm:^1.0.0" @@ -3748,13 +4614,13 @@ __metadata: micromark-util-subtokenize: "npm:^2.0.0" micromark-util-symbol: "npm:^2.0.0" micromark-util-types: "npm:^2.0.0" - checksum: 10/eafa6b9cd6fd9f51efa7795824af9a765e24a4519855a5b6dfcb0f619a93d90599d39a261f626bfcc1dfa64f22430f7a677a83cb6ce4bd8e4eeabc892610c016 + checksum: 10/2b98b9eba1463850ebd8f338f966bd2113dafe764b490ebee3dccab3764d3c48b53fe67673297530e56bf54f58de27dfd1952ed79c5b4e32047cb7f29bd807f2 languageName: node linkType: hard "micromark-extension-mdx-expression@npm:^3.0.0": - version: 3.0.0 - resolution: "micromark-extension-mdx-expression@npm:3.0.0" + version: 3.0.1 + resolution: "micromark-extension-mdx-expression@npm:3.0.1" dependencies: "@types/estree": "npm:^1.0.0" devlop: "npm:^1.0.0" @@ -3764,15 +4630,14 @@ __metadata: micromark-util-events-to-acorn: "npm:^2.0.0" micromark-util-symbol: "npm:^2.0.0" micromark-util-types: "npm:^2.0.0" - checksum: 10/a5592160319d4617362f6b72a6fc44b5570466afa07419d44bcfdd9398a77a5693d7c5f8da7b3ff4682edf6209d4781835f5d2e3166fdf6bba37db456fd2d091 + checksum: 10/a185e1787fe6d49d0e435690affd4b83ce319f88a08c57d2460d37d5c0a75ea64aa49a4a116b6d37f91389dc04351e1826aa834519a9f25fc31e1424962c6eb7 languageName: node linkType: hard "micromark-extension-mdx-jsx@npm:^3.0.0": - version: 3.0.1 - resolution: "micromark-extension-mdx-jsx@npm:3.0.1" + version: 3.0.2 + resolution: "micromark-extension-mdx-jsx@npm:3.0.2" dependencies: - "@types/acorn": "npm:^4.0.0" "@types/estree": "npm:^1.0.0" devlop: "npm:^1.0.0" estree-util-is-identifier-name: "npm:^3.0.0" @@ -3783,7 +4648,7 @@ __metadata: micromark-util-symbol: "npm:^2.0.0" micromark-util-types: "npm:^2.0.0" vfile-message: "npm:^4.0.0" - checksum: 10/2cc0277d91c3c85d52e88755d17d021b5eab6fa03a578a9965f9d3d2c184dbc1accce63e7f8437a092ceeb602840ef451d4dce6dc9e8c13df0bc76e741080a89 + checksum: 10/a85cdb7c972fbb2cc8f0a64adc808b2b62bc2d79dbdd31fcd3208ff15aafa0198b002022840b2c65b5bff6f2a8c2c4a59a32a89f3482e6e183114b476e98e25c languageName: node linkType: hard @@ -3853,8 +4718,8 @@ __metadata: linkType: hard "micromark-factory-mdx-expression@npm:^2.0.0": - version: 2.0.2 - resolution: "micromark-factory-mdx-expression@npm:2.0.2" + version: 2.0.3 + resolution: "micromark-factory-mdx-expression@npm:2.0.3" dependencies: "@types/estree": "npm:^1.0.0" devlop: "npm:^1.0.0" @@ -3865,7 +4730,7 @@ __metadata: micromark-util-types: "npm:^2.0.0" unist-util-position-from-estree: "npm:^2.0.0" vfile-message: "npm:^4.0.0" - checksum: 10/d5285fa98018f14a058c7cd4a961aacedd2d3c4f4fddd4c58c16f1e640d1284a8f581f4d00fa3e18c06ed302ce23bca23f6a01edd66064c23c9057e65385a62d + checksum: 10/afadae88a18f31afa564747101e076011c56457454b30294ae55aeea7efee8626ddc3bad0f0f43649008f89b8784782b5adec143fdf477fb352354d76f08db55 languageName: node linkType: hard @@ -3972,10 +4837,9 @@ __metadata: linkType: hard "micromark-util-events-to-acorn@npm:^2.0.0": - version: 2.0.2 - resolution: "micromark-util-events-to-acorn@npm:2.0.2" + version: 2.0.3 + resolution: "micromark-util-events-to-acorn@npm:2.0.3" dependencies: - "@types/acorn": "npm:^4.0.0" "@types/estree": "npm:^1.0.0" "@types/unist": "npm:^3.0.0" devlop: "npm:^1.0.0" @@ -3983,7 +4847,7 @@ __metadata: micromark-util-symbol: "npm:^2.0.0" micromark-util-types: "npm:^2.0.0" vfile-message: "npm:^4.0.0" - checksum: 10/475367e716c4d24f2a57464a7f2c8aa507ae36c05b7767fd652895525f3f0a1179ea3219cabccc0f3038bb5e4f9cce5390d530dc56decaa5f1786bda42739810 + checksum: 10/0d87e49b897636dc0e84b4bd06b6fa9e6abcd40ab90c9431e36737c85c444d3db1e4f9b8f51433422b1bedc46f086890ce96671b5a795230c6b7b09cb53d9aba languageName: node linkType: hard @@ -4024,14 +4888,14 @@ __metadata: linkType: hard "micromark-util-subtokenize@npm:^2.0.0": - version: 2.0.3 - resolution: "micromark-util-subtokenize@npm:2.0.3" + version: 2.1.0 + resolution: "micromark-util-subtokenize@npm:2.1.0" dependencies: devlop: "npm:^1.0.0" micromark-util-chunked: "npm:^2.0.0" micromark-util-symbol: "npm:^2.0.0" micromark-util-types: "npm:^2.0.0" - checksum: 10/57b26f129f46424a4670bb47c50f13c7309bb1bc557c02150a788d1634337c1bb25a3523af3d6dffc29aaec873b3bd88fd931bfff34e64284e6436e23873ca22 + checksum: 10/5f18c70cb952a414a4d161f5d6a5254d33c7dfcd56577e592ef2e172a0414058d3531a3554f43538f14e243592fffbc2e68ddaf6a41c54577b3ba7beb555d3dc languageName: node linkType: hard @@ -4043,15 +4907,15 @@ __metadata: linkType: hard "micromark-util-types@npm:^2.0.0": - version: 2.0.1 - resolution: "micromark-util-types@npm:2.0.1" - checksum: 10/69c5e18e6ba4e12473d6fe5f1a7cc113ac1d4bfc23c7ad57b16a5e4bfd09ef48b7c17a40c39d43996f2078ad898efd3f1945007c14f395abd55f2af03d413acd + version: 2.0.2 + resolution: "micromark-util-types@npm:2.0.2" + checksum: 10/a9eb067bd9384eab61942285d53738aa22f3fef4819eaf20249bec6ec13f1e4da2800230fd0ceb7e705108987aa9062fe3e9a8e5e48aa60180db80b9489dc3e2 languageName: node linkType: hard "micromark@npm:^4.0.0": - version: 4.0.1 - resolution: "micromark@npm:4.0.1" + version: 4.0.2 + resolution: "micromark@npm:4.0.2" dependencies: "@types/debug": "npm:^4.0.0" debug: "npm:^4.0.0" @@ -4070,11 +4934,11 @@ __metadata: micromark-util-subtokenize: "npm:^2.0.0" micromark-util-symbol: "npm:^2.0.0" micromark-util-types: "npm:^2.0.0" - checksum: 10/b948b1b239e589826bdaf2835daa9e88873e23d4b9148cd22109a86d4af55b96345cf9fc9059b6b19ae828f64d55e66f376ca3aeb4af3d2b0241560125f5dae6 + checksum: 10/1b85e49c8f71013df2d07a59e477deb72cd325d41cc15f35b2aa52b8b7a93fed45498ce3e18ed34464a9afa9ba8a9210b2509454b2a2d16ac06c7429f562bfac languageName: node linkType: hard -"micromatch@npm:^4.0.8": +"micromatch@npm:^4.0.4, micromatch@npm:^4.0.8": version: 4.0.8 resolution: "micromatch@npm:4.0.8" dependencies: @@ -4093,7 +4957,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.1, minimatch@npm:^9.0.4": +"minimatch@npm:^9.0.4": version: 9.0.5 resolution: "minimatch@npm:9.0.5" dependencies: @@ -4109,34 +4973,112 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0": +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10/b251bceea62090f67a6cced7a446a36f4cd61ee2d5cea9aee7fff79ba8030e416327a1c5aa2908dc22629d06214b46d88fdab8c51ac76bacbf5703851b5ad342 + languageName: node + linkType: hard + +"minipass-fetch@npm:^4.0.0": + version: 4.0.1 + resolution: "minipass-fetch@npm:4.0.1" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^1.0.3" + minizlib: "npm:^3.0.1" + dependenciesMeta: + encoding: + optional: true + checksum: 10/7ddfebdbb87d9866e7b5f7eead5a9e3d9d507992af932a11d275551f60006cf7d9178e66d586dbb910894f3e3458d27c0ddf93c76e94d49d0a54a541ddc1263d + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10/56269a0b22bad756a08a94b1ffc36b7c9c5de0735a4dd1ab2b06c066d795cfd1f0ac44a0fcae13eece5589b908ecddc867f04c745c7009be0b566421ea0944cf + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10/b14240dac0d29823c3d5911c286069e36d0b81173d7bdf07a7e4a91ecdef92cdff4baaf31ea3746f1c61e0957f652e641223970870e2353593f382112257971b + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10/40982d8d836a52b0f37049a0a7e5d0f089637298e6d9b45df9c115d4f0520682a78258905e5c8b180fb41b593b0a82cc1361d2c74b45f7ada66334f84d1ecfdd + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10/a5c6ef069f70d9a524d3428af39f2b117ff8cd84172e19b754e7264a33df460873e6eb3d6e55758531580970de50ae950c496256bb4ad3691a2974cddff189f0 + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": version: 7.1.2 resolution: "minipass@npm:7.1.2" checksum: 10/c25f0ee8196d8e6036661104bacd743785b2599a21de5c516b32b3fa2b83113ac89a2358465bc04956baab37ffb956ae43be679b2262bf7be15fce467ccd7950 languageName: node linkType: hard -"motion-dom@npm:^12.5.0": - version: 12.5.0 - resolution: "motion-dom@npm:12.5.0" +"minizlib@npm:^3.0.1": + version: 3.0.2 + resolution: "minizlib@npm:3.0.2" dependencies: - motion-utils: "npm:^12.5.0" - checksum: 10/289e3d5567053e9b84223df9b482bb9180732c0cb6276e7982a73ddad0f76e47f8e0f69e4cdf93d7a689ad021fce837fa8a19c8b61bf56d71bdfc0e590bc3e7e + minipass: "npm:^7.1.2" + checksum: 10/c075bed1594f68dcc8c35122333520112daefd4d070e5d0a228bd4cf5580e9eed3981b96c0ae1d62488e204e80fd27b2b9d0068ca9a5ef3993e9565faf63ca41 languageName: node linkType: hard -"motion-utils@npm:^12.5.0": - version: 12.5.0 - resolution: "motion-utils@npm:12.5.0" - checksum: 10/f2d754a9e1c4cd829f0703ce47bbeb3e9967994a9cb35e759f34de16af6c520a4959b04ff6998211ca67dc7bfe0002d9b2465858167b1971b0403f63289f97fc +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 10/16fd79c28645759505914561e249b9a1f5fe3362279ad95487a4501e4467abeb714fd35b95307326b8fd03f3c7719065ef11a6f97b7285d7888306d1bd2232ba + languageName: node + linkType: hard + +"motion-dom@npm:^12.17.0": + version: 12.17.0 + resolution: "motion-dom@npm:12.17.0" + dependencies: + motion-utils: "npm:^12.12.1" + checksum: 10/54b482d643ca12d895e8a0860809b2b53f2e7c2981d12f3d1230f5bd6154cb59df6ab81a690681df7a526da283aaf76acbf351b148e2b805ad5f8ce97156e061 + languageName: node + linkType: hard + +"motion-utils@npm:^12.12.1": + version: 12.12.1 + resolution: "motion-utils@npm:12.12.1" + checksum: 10/679503df10a1cd7ad58f9ed2f864acb5ecbadf380a668874f0cc6bdc1155278bac0cce73e3f703df19532f2bd922e3500fde179e8f76386e9651f8b1bd0cfe6d languageName: node linkType: hard "motion@npm:^12.4.1": - version: 12.5.0 - resolution: "motion@npm:12.5.0" + version: 12.17.0 + resolution: "motion@npm:12.17.0" dependencies: - framer-motion: "npm:^12.5.0" + framer-motion: "npm:^12.17.0" tslib: "npm:^2.4.0" peerDependencies: "@emotion/is-prop-valid": "*" @@ -4149,7 +5091,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 10/15910d37a14b91236c27c8cc6d12ae157cd1e6b55700fc58a0d271954236fae8e676b1ac9f705cb770c235892925fc6bbdb1f612dfb4018a5d4c0171964f5b3a + checksum: 10/4fde3c4f39fa872cb27a41ddf39dcf20a9a066428ce07cf7455e2a4779d916a47cb3141e8c4c970f9d7b8e8818b822f3eeb86761d61904a18383b82426f5f3ca languageName: node linkType: hard @@ -4161,11 +5103,20 @@ __metadata: linkType: hard "nanoid@npm:^3.3.6": - version: 3.3.8 - resolution: "nanoid@npm:3.3.8" + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" bin: nanoid: bin/nanoid.cjs - checksum: 10/2d1766606cf0d6f47b6f0fdab91761bb81609b2e3d367027aff45e6ee7006f660fb7e7781f4a34799fe6734f1268eeed2e37a5fdee809ade0c2d4eb11b0f9c40 + checksum: 10/73b5afe5975a307aaa3c95dfe3334c52cdf9ae71518176895229b8d65ab0d1c0417dd081426134eb7571c055720428ea5d57c645138161e7d10df80815527c48 + languageName: node + linkType: hard + +"napi-postinstall@npm:^0.2.2": + version: 0.2.4 + resolution: "napi-postinstall@npm:0.2.4" + bin: + napi-postinstall: lib/cli.js + checksum: 10/286785f884b872102fb284847ecc693101f70126b1fc7a97e19293929ce7f08802b41f89398015cce0797070ea3ce6871939a3c1e693c04cf594f7939dbe8cfb languageName: node linkType: hard @@ -4176,31 +5127,57 @@ __metadata: languageName: node linkType: hard -"next@npm:14.2.26": - version: 14.2.26 - resolution: "next@npm:14.2.26" +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 10/b5734e87295324fabf868e36fb97c84b7d7f3156ec5f4ee5bf6e488079c11054f818290fc33804cef7b1ee21f55eeb14caea83e7dafae6492a409b3e573153e5 + languageName: node + linkType: hard + +"next-mdx-remote-client@npm:^2.1.2": + version: 2.1.3 + resolution: "next-mdx-remote-client@npm:2.1.3" dependencies: - "@next/env": "npm:14.2.26" - "@next/swc-darwin-arm64": "npm:14.2.26" - "@next/swc-darwin-x64": "npm:14.2.26" - "@next/swc-linux-arm64-gnu": "npm:14.2.26" - "@next/swc-linux-arm64-musl": "npm:14.2.26" - "@next/swc-linux-x64-gnu": "npm:14.2.26" - "@next/swc-linux-x64-musl": "npm:14.2.26" - "@next/swc-win32-arm64-msvc": "npm:14.2.26" - "@next/swc-win32-ia32-msvc": "npm:14.2.26" - "@next/swc-win32-x64-msvc": "npm:14.2.26" - "@swc/helpers": "npm:0.5.5" + "@babel/code-frame": "npm:^7.27.1" + "@mdx-js/mdx": "npm:^3.1.0" + "@mdx-js/react": "npm:^3.1.0" + remark-mdx-remove-esm: "npm:^1.2.0" + serialize-error: "npm:^12.0.0" + vfile: "npm:^6.0.3" + vfile-matter: "npm:^5.0.1" + peerDependencies: + react: ^19.1.0 + react-dom: ^19.1.0 + checksum: 10/3837c63edf7d707de2eea9b4c565eeb7e505a769e93c2673f746ec9993c644f7cd0aacb8bad911eff9ea63190f48c2c16ffe98d47c39591c5dac29c8bf9a9e0c + languageName: node + linkType: hard + +"next@npm:15.3.4": + version: 15.3.4 + resolution: "next@npm:15.3.4" + dependencies: + "@next/env": "npm:15.3.4" + "@next/swc-darwin-arm64": "npm:15.3.4" + "@next/swc-darwin-x64": "npm:15.3.4" + "@next/swc-linux-arm64-gnu": "npm:15.3.4" + "@next/swc-linux-arm64-musl": "npm:15.3.4" + "@next/swc-linux-x64-gnu": "npm:15.3.4" + "@next/swc-linux-x64-musl": "npm:15.3.4" + "@next/swc-win32-arm64-msvc": "npm:15.3.4" + "@next/swc-win32-x64-msvc": "npm:15.3.4" + "@swc/counter": "npm:0.1.3" + "@swc/helpers": "npm:0.5.15" busboy: "npm:1.6.0" caniuse-lite: "npm:^1.0.30001579" - graceful-fs: "npm:^4.2.11" postcss: "npm:8.4.31" - styled-jsx: "npm:5.1.1" + sharp: "npm:^0.34.1" + styled-jsx: "npm:5.1.6" peerDependencies: "@opentelemetry/api": ^1.1.0 "@playwright/test": ^1.41.2 - react: ^18.2.0 - react-dom: ^18.2.0 + babel-plugin-react-compiler: "*" + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 sass: ^1.3.0 dependenciesMeta: "@next/swc-darwin-arm64": @@ -4217,20 +5194,60 @@ __metadata: optional: true "@next/swc-win32-arm64-msvc": optional: true - "@next/swc-win32-ia32-msvc": - optional: true "@next/swc-win32-x64-msvc": optional: true + sharp: + optional: true peerDependenciesMeta: "@opentelemetry/api": optional: true "@playwright/test": optional: true + babel-plugin-react-compiler: + optional: true sass: optional: true bin: next: dist/bin/next - checksum: 10/9bd8b2ef40076b251d036c6d3a3b435095ff941bab7166ebc3c75bd832c1812eb368f8dae1e56ef4bf600a81d0ad8190d778d610aa062ffa7c4c4f126cae7f13 + checksum: 10/bcc781ca5abba6a29408431f81b57598708871796fe4c70d1187cc92ca4905467a6a3cde32840cb7911099d54a93c1543c130e4dea2b59dbf9753f5850a8efdf + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 11.2.0 + resolution: "node-gyp@npm:11.2.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^14.0.3" + nopt: "npm:^8.0.0" + proc-log: "npm:^5.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.4.3" + tinyglobby: "npm:^0.2.12" + which: "npm:^5.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10/806fd8e3adc9157e17bf0d4a2c899cf6b98a0bbe9f453f630094ce791866271f6cddcaf2133e6513715d934fcba2014d287c7053d5d7934937b3a34d5a3d84ad + languageName: node + linkType: hard + +"nopt@npm:^8.0.0": + version: 8.1.0 + resolution: "nopt@npm:8.1.0" + dependencies: + abbrev: "npm:^3.0.0" + bin: + nopt: bin/nopt.js + checksum: 10/26ab456c51a96f02a9e5aa8d1b80ef3219f2070f3f3528a040e32fb735b1e651e17bdf0f1476988d3a46d498f35c65ed662d122f340d38ce4a7e71dd7b20c4bc + languageName: node + linkType: hard + +"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 10/88eeb4da891e10b1318c4b2476b6e2ecbeb5ff97d946815ffea7794c31a89017c70d7f34b3c2ebf23ef4e9fc9fb99f7dffe36da22011b5b5c6ffa34f4873ec20 languageName: node linkType: hard @@ -4241,10 +5258,10 @@ __metadata: languageName: node linkType: hard -"object-inspect@npm:^1.13.3": - version: 1.13.3 - resolution: "object-inspect@npm:1.13.3" - checksum: 10/14cb973d8381c69e14d7f1c8c75044eb4caf04c6dabcf40ca5c2ce42dc2073ae0bb2a9939eeca142b0c05215afaa1cd5534adb7c8879c32cba2576e045ed8368 +"object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10/aa13b1190ad3e366f6c83ad8a16ed37a19ed57d267385aa4bfdccda833d7b90465c057ff6c55d035a6b2e52c1a2295582b294217a0a3a1ae7abdd6877ef781fb languageName: node linkType: hard @@ -4269,14 +5286,15 @@ __metadata: languageName: node linkType: hard -"object.entries@npm:^1.1.8": - version: 1.1.8 - resolution: "object.entries@npm:1.1.8" +"object.entries@npm:^1.1.9": + version: 1.1.9 + resolution: "object.entries@npm:1.1.9" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10/2301918fbd1ee697cf6ff7cd94f060c738c0a7d92b22fd24c7c250e9b593642c9707ad2c44d339303c1439c5967d8964251cdfc855f7f6ec55db2dd79e8dc2a7 + es-object-atoms: "npm:^1.1.1" + checksum: 10/24163ab1e1e013796693fc5f5d349e8b3ac0b6a34a7edb6c17d3dd45c6a8854145780c57d302a82512c1582f63720f4b4779d6c1cfba12cbb1420b978802d8a3 languageName: node linkType: hard @@ -4324,14 +5342,14 @@ __metadata: languageName: node linkType: hard -"oniguruma-to-es@npm:0.10.0": - version: 0.10.0 - resolution: "oniguruma-to-es@npm:0.10.0" +"oniguruma-to-es@npm:^2.2.0": + version: 2.3.0 + resolution: "oniguruma-to-es@npm:2.3.0" dependencies: emoji-regex-xs: "npm:^1.0.0" regex: "npm:^5.1.1" regex-recursion: "npm:^5.1.1" - checksum: 10/09756001a74218b26d18940bea3ebb00d1949aa7ac142def3a94a4d256371fea169f52088ea32a6a23ca21338be56427fd9f0264907cee663e7ab147152638f7 + checksum: 10/7ac89fe04791650c21a146e58c985de394597e0ab3eb7d7c46699d09f87d6aca63d9f2470f1473c5adfa16961c360df6858e6a24b2429ebd7c73620ca68aec98 languageName: node linkType: hard @@ -4389,6 +5407,20 @@ __metadata: languageName: node linkType: hard +"p-map@npm:^7.0.2": + version: 7.0.3 + resolution: "p-map@npm:7.0.3" + checksum: 10/2ef48ccfc6dd387253d71bf502604f7893ed62090b2c9d73387f10006c342606b05233da0e4f29388227b61eb5aeface6197e166520c465c234552eeab2fe633 + languageName: node + linkType: hard + +"package-json-from-dist@npm:^1.0.0": + version: 1.0.1 + resolution: "package-json-from-dist@npm:1.0.1" + checksum: 10/58ee9538f2f762988433da00e26acc788036914d57c71c246bf0be1b60cdbd77dd60b6a3e1a30465f0b248aeb80079e0b34cb6050b1dfa18c06953bb1cbc7602 + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -4441,7 +5473,7 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.10.1": +"path-scurry@npm:^1.11.1": version: 1.11.1 resolution: "path-scurry@npm:1.11.1" dependencies: @@ -4451,24 +5483,31 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.0.0": +"picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 languageName: node linkType: hard -"picomatch@npm:^2.3.1": +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" checksum: 10/60c2595003b05e4535394d1da94850f5372c9427ca4413b71210f437f7b2ca091dbd611c45e8b37d10036fa8eade25c1b8951654f9d3973bfa66a2ff4d3b08bc languageName: node linkType: hard +"picomatch@npm:^4.0.2": + version: 4.0.2 + resolution: "picomatch@npm:4.0.2" + checksum: 10/ce617b8da36797d09c0baacb96ca8a44460452c89362d7cb8f70ca46b4158ba8bc3606912de7c818eb4a939f7f9015cef3c766ec8a0c6bfc725fdc078e39c717 + languageName: node + linkType: hard + "possible-typed-array-names@npm:^1.0.0": - version: 1.0.0 - resolution: "possible-typed-array-names@npm:1.0.0" - checksum: 10/8ed3e96dfeea1c5880c1f4c9cb707e5fb26e8be22f14f82ef92df20fd2004e635c62ba47fbe8f2bb63bfd80dac1474be2fb39798da8c2feba2815435d1f749af + version: 1.1.0 + resolution: "possible-typed-array-names@npm:1.1.0" + checksum: 10/2f44137b8d3dd35f4a7ba7469eec1cd9cfbb46ec164b93a5bc1f4c3d68599c9910ee3b91da1d28b4560e9cc8414c3cd56fedc07259c67e52cc774476270d3302 languageName: node linkType: hard @@ -4490,6 +5529,13 @@ __metadata: languageName: node linkType: hard +"proc-log@npm:^5.0.0": + version: 5.0.0 + resolution: "proc-log@npm:5.0.0" + checksum: 10/35610bdb0177d3ab5d35f8827a429fb1dc2518d9e639f2151ac9007f01a061c30e0c635a970c9b00c39102216160f6ec54b62377c92fac3b7bfc2ad4b98d195c + languageName: node + linkType: hard + "process@npm:^0.11.10": version: 0.11.10 resolution: "process@npm:0.11.10" @@ -4497,6 +5543,16 @@ __metadata: languageName: node linkType: hard +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 10/96e1a82453c6c96eef53a37a1d6134c9f2482f94068f98a59145d0986ca4e497bf110a410adf73857e588165eab3899f0ebcf7b3890c1b3ce802abc0d65967d4 + languageName: node + linkType: hard + "prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" @@ -4508,10 +5564,10 @@ __metadata: languageName: node linkType: hard -"property-information@npm:^6.0.0": - version: 6.5.0 - resolution: "property-information@npm:6.5.0" - checksum: 10/fced94f3a09bf651ad1824d1bdc8980428e3e480e6d01e98df6babe2cc9d45a1c52eee9a7736d2006958f9b394eb5964dedd37e23038086ddc143fc2fd5e426c +"property-information@npm:^7.0.0": + version: 7.1.0 + resolution: "property-information@npm:7.1.0" + checksum: 10/896d38a52ad7170de73f832d277c69e76a9605d941ebb3f0d6e56271414a7fdf95ff6d2819e68036b8a0c7d2d4d88bf1d4a5765c032cb19c2343567ee3a14b15 languageName: node linkType: hard @@ -4529,26 +5585,14 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:^18.0.2": - version: 18.3.1 - resolution: "react-dom@npm:18.3.1" +"react-dom@npm:19.1.1": + version: 19.1.1 + resolution: "react-dom@npm:19.1.1" dependencies: - loose-envify: "npm:^1.1.0" - scheduler: "npm:^0.23.2" + scheduler: "npm:^0.26.0" peerDependencies: - react: ^18.3.1 - checksum: 10/3f4b73a3aa083091173b29812b10394dd06f4ac06aff410b74702cfb3aa29d7b0ced208aab92d5272919b612e5cda21aeb1d54191848cf6e46e9e354f3541f81 - languageName: node - linkType: hard - -"react-frame-component@npm:^5.2.7": - version: 5.2.7 - resolution: "react-frame-component@npm:5.2.7" - peerDependencies: - prop-types: ^15.5.9 - react: ">= 16.3" - react-dom: ">= 16.3" - checksum: 10/bedee0c25075cbfb6d592991937640a9c8680f9017284e73ced338cc07348b3efc99121052b60ba0923d3d4ec1ed050348ddec452f059e0b947f6bff1d5bfe24 + react: ^19.1.1 + checksum: 10/9005415d2175b1f1eb4a544ad04afb29691bb7b6dd43bbdaa09932146b310b73bd4552bc772ad78fa481f409eada1560cf887606c83c1a53a922c1e30f1b3a34 languageName: node linkType: hard @@ -4559,25 +5603,39 @@ __metadata: languageName: node linkType: hard -"react@npm:^18.0.2": - version: 18.3.1 - resolution: "react@npm:18.3.1" +"react-property@npm:2.0.2": + version: 2.0.2 + resolution: "react-property@npm:2.0.2" + checksum: 10/3a4bc1951b2b7992cb8a2d3f12016dd0920d1c06eb58b456204a6ae1210401d62baece098d3200ed8a0513dde247a5d96ffdb24f354e32ce5a9b26fbd8552668 + languageName: node + linkType: hard + +"react@npm:19.1.1": + version: 19.1.1 + resolution: "react@npm:19.1.1" + checksum: 10/9801530fdc939e1a7a499422e930515b2400809cb39c2872984e99f832d233f61659a693871183dac3155c2f9b2c9dcf4440a56bd18983277ae92860e38c3a61 + languageName: node + linkType: hard + +"readdirp@npm:~3.6.0": + version: 3.6.0 + resolution: "readdirp@npm:3.6.0" dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10/261137d3f3993eaa2368a83110466fc0e558bc2c7f7ae7ca52d94f03aac945f45146bd85e5f481044db1758a1dbb57879e2fcdd33924e2dde1bdc550ce73f7bf + picomatch: "npm:^2.2.1" + checksum: 10/196b30ef6ccf9b6e18c4e1724b7334f72a093d011a99f3b5920470f0b3406a51770867b3e1ae9711f227ef7a7065982f6ee2ce316746b2cb42c88efe44297fe7 languageName: node linkType: hard "recast@npm:^0.23.5": - version: 0.23.9 - resolution: "recast@npm:0.23.9" + version: 0.23.11 + resolution: "recast@npm:0.23.11" dependencies: ast-types: "npm:^0.16.1" esprima: "npm:~4.0.0" source-map: "npm:~0.6.1" tiny-invariant: "npm:^1.3.3" tslib: "npm:^2.0.1" - checksum: 10/d60584be179d81a82fbe58b5bbe009aa42831ee114a21a3e3a22bda91334e0b8a1a4610dca8ecb7f9ea1426da4febc08134d3003085ad6ecee478d1808eb8796 + checksum: 10/a622b7848efe13a59a40c9a1a3a8178433eae1048780e04d7392406e2d67fc29e3efa84b3aa8cfda28fd58989f4b59fa968bed295b739987a666bd11cc57a5b2 languageName: node linkType: hard @@ -4645,13 +5703,6 @@ __metadata: languageName: node linkType: hard -"regenerator-runtime@npm:^0.14.0": - version: 0.14.1 - resolution: "regenerator-runtime@npm:0.14.1" - checksum: 10/5db3161abb311eef8c45bcf6565f4f378f785900ed3945acf740a9888c792f75b98ecb77f0775f3bf95502ff423529d23e94f41d80c8256e8fa05ed4b07cf471 - languageName: node - linkType: hard - "regex-recursion@npm:^5.1.1": version: 5.1.1 resolution: "regex-recursion@npm:5.1.1" @@ -4678,7 +5729,7 @@ __metadata: languageName: node linkType: hard -"regexp.prototype.flags@npm:^1.5.3": +"regexp.prototype.flags@npm:^1.5.3, regexp.prototype.flags@npm:^1.5.4": version: 1.5.4 resolution: "regexp.prototype.flags@npm:1.5.4" dependencies: @@ -4703,6 +5754,19 @@ __metadata: languageName: node linkType: hard +"remark-mdx-remove-esm@npm:^1.2.0": + version: 1.2.0 + resolution: "remark-mdx-remove-esm@npm:1.2.0" + dependencies: + "@types/mdast": "npm:^4.0.4" + mdast-util-mdxjs-esm: "npm:^2.0.1" + unist-util-remove: "npm:^4.0.0" + peerDependencies: + unified: ^11 + checksum: 10/0a1155e08395ab73f877948455fc04d7457864f7556617efabecae7ea1b441cec2ca7ee9672e16a6cd56f2a16c57a4778f901e77d19052d1dd245de5bdf4618f + languageName: node + linkType: hard + "remark-mdx@npm:^3.0.0": version: 3.1.0 resolution: "remark-mdx@npm:3.1.0" @@ -4726,15 +5790,22 @@ __metadata: linkType: hard "remark-rehype@npm:^11.0.0": - version: 11.1.1 - resolution: "remark-rehype@npm:11.1.1" + version: 11.1.2 + resolution: "remark-rehype@npm:11.1.2" dependencies: "@types/hast": "npm:^3.0.0" "@types/mdast": "npm:^4.0.0" mdast-util-to-hast: "npm:^13.0.0" unified: "npm:^11.0.0" vfile: "npm:^6.0.0" - checksum: 10/39404bd19c57b2b69660be7e3d587ddb2240495845d42fad3bcc506c9c132d07abacb0a20182b73c530857b2da0c463ad5658382b448243ce432152ab49af08d + checksum: 10/b5374a0bf08398431c92740d0cd9b20aea9df44cee12326820ddcc1b7ee642706604006461ea9799554c347e7caf31e7432132a03b97c508e1f77d29c423bd86 + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10/a72468e2589270d91f06c7d36ec97a88db53ae5d6fe3787fadc943f0b0276b10347f89b363b2a82285f650bdcc135ad4a257c61bdd4d00d6df1fa24875b0ddaf languageName: node linkType: hard @@ -4804,10 +5875,17 @@ __metadata: languageName: node linkType: hard +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10/1f914879f97e7ee931ad05fe3afa629bd55270fc6cf1c1e589b6a99fab96d15daad0fa1a52a00c729ec0078045fe3e399bd4fd0c93bcc906957bdc17f89cb8e6 + languageName: node + linkType: hard + "reusify@npm:^1.0.4": - version: 1.0.4 - resolution: "reusify@npm:1.0.4" - checksum: 10/14222c9e1d3f9ae01480c50d96057228a8524706db79cdeb5a2ce5bb7070dd9f409a6f84a02cbef8cdc80d39aef86f2dd03d155188a1300c599b05437dcd2ffb + version: 1.1.0 + resolution: "reusify@npm:1.1.0" + checksum: 10/af47851b547e8a8dc89af144fceee17b80d5beaf5e6f57ed086432d79943434ff67ca526e92275be6f54b6189f6920a24eace75c2657eed32d02c400312b21ec languageName: node linkType: hard @@ -4831,6 +5909,15 @@ __metadata: languageName: node linkType: hard +"rxjs@npm:^7.8.1": + version: 7.8.2 + resolution: "rxjs@npm:7.8.2" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10/03dff09191356b2b87d94fbc1e97c4e9eb3c09d4452399dddd451b09c2f1ba8d56925a40af114282d7bc0c6fe7514a2236ca09f903cf70e4bbf156650dddb49d + languageName: node + linkType: hard + "safe-array-concat@npm:^1.1.3": version: 1.1.3 resolution: "safe-array-concat@npm:1.1.3" @@ -4865,12 +5952,17 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:^0.23.2": - version: 0.23.2 - resolution: "scheduler@npm:0.23.2" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10/e8d68b89d18d5b028223edf090092846868a765a591944760942b77ea1f69b17235f7e956696efbb62c8130ab90af7e0949bfb8eba7896335507317236966bc9 +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10/7eaf7a0cf37cc27b42fb3ef6a9b1df6e93a1c6d98c6c6702b02fe262d5fcbd89db63320793b99b21cb5348097d0a53de81bd5f4e8b86e20cc9412e3f1cfb4e83 + languageName: node + linkType: hard + +"scheduler@npm:^0.26.0": + version: 0.26.0 + resolution: "scheduler@npm:0.26.0" + checksum: 10/1ecf2e5d7de1a7a132796834afe14a2d589ba7e437615bd8c06f3e0786a3ac3434655e67aac8755d9b14e05754c177e49c064261de2673aaa3c926bc98caa002 languageName: node linkType: hard @@ -4883,12 +5975,21 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3": - version: 7.6.3 - resolution: "semver@npm:7.6.3" +"semver@npm:^7.3.5, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.7.1, semver@npm:^7.7.2": + version: 7.7.2 + resolution: "semver@npm:7.7.2" bin: semver: bin/semver.js - checksum: 10/36b1fbe1a2b6f873559cd57b238f1094a053dbfd997ceeb8757d79d1d2089c56d1321b9f1069ce263dc64cfa922fa1d2ad566b39426fe1ac6c723c1487589e10 + checksum: 10/7a24cffcaa13f53c09ce55e05efe25cd41328730b2308678624f8b9f5fc3093fc4d189f47950f0b811ff8f3c3039c24a2c36717ba7961615c682045bf03e1dda + languageName: node + linkType: hard + +"serialize-error@npm:^12.0.0": + version: 12.0.0 + resolution: "serialize-error@npm:12.0.0" + dependencies: + type-fest: "npm:^4.31.0" + checksum: 10/733ddb9a2bd86515d18ef43f6bcb1f55a9d9b900944420e5606725668e64b752e5da971071d8fe1562961a926796257d2edb9fad828ec90a35cc6c32a75449bb languageName: node linkType: hard @@ -4929,6 +6030,81 @@ __metadata: languageName: node linkType: hard +"sharp@npm:^0.34.1": + version: 0.34.2 + resolution: "sharp@npm:0.34.2" + dependencies: + "@img/sharp-darwin-arm64": "npm:0.34.2" + "@img/sharp-darwin-x64": "npm:0.34.2" + "@img/sharp-libvips-darwin-arm64": "npm:1.1.0" + "@img/sharp-libvips-darwin-x64": "npm:1.1.0" + "@img/sharp-libvips-linux-arm": "npm:1.1.0" + "@img/sharp-libvips-linux-arm64": "npm:1.1.0" + "@img/sharp-libvips-linux-ppc64": "npm:1.1.0" + "@img/sharp-libvips-linux-s390x": "npm:1.1.0" + "@img/sharp-libvips-linux-x64": "npm:1.1.0" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.1.0" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.1.0" + "@img/sharp-linux-arm": "npm:0.34.2" + "@img/sharp-linux-arm64": "npm:0.34.2" + "@img/sharp-linux-s390x": "npm:0.34.2" + "@img/sharp-linux-x64": "npm:0.34.2" + "@img/sharp-linuxmusl-arm64": "npm:0.34.2" + "@img/sharp-linuxmusl-x64": "npm:0.34.2" + "@img/sharp-wasm32": "npm:0.34.2" + "@img/sharp-win32-arm64": "npm:0.34.2" + "@img/sharp-win32-ia32": "npm:0.34.2" + "@img/sharp-win32-x64": "npm:0.34.2" + color: "npm:^4.2.3" + detect-libc: "npm:^2.0.4" + semver: "npm:^7.7.2" + dependenciesMeta: + "@img/sharp-darwin-arm64": + optional: true + "@img/sharp-darwin-x64": + optional: true + "@img/sharp-libvips-darwin-arm64": + optional: true + "@img/sharp-libvips-darwin-x64": + optional: true + "@img/sharp-libvips-linux-arm": + optional: true + "@img/sharp-libvips-linux-arm64": + optional: true + "@img/sharp-libvips-linux-ppc64": + optional: true + "@img/sharp-libvips-linux-s390x": + optional: true + "@img/sharp-libvips-linux-x64": + optional: true + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + "@img/sharp-libvips-linuxmusl-x64": + optional: true + "@img/sharp-linux-arm": + optional: true + "@img/sharp-linux-arm64": + optional: true + "@img/sharp-linux-s390x": + optional: true + "@img/sharp-linux-x64": + optional: true + "@img/sharp-linuxmusl-arm64": + optional: true + "@img/sharp-linuxmusl-x64": + optional: true + "@img/sharp-wasm32": + optional: true + "@img/sharp-win32-arm64": + optional: true + "@img/sharp-win32-ia32": + optional: true + "@img/sharp-win32-x64": + optional: true + checksum: 10/8c7a6f20d58849a6e33bc69c4525f471d57eb3dea0072331b55ab12bae4b8bd8b99b65264aeaec38e54d52d692db13e3261f6e7bc29430b39b507c409838cbb0 + languageName: node + linkType: hard + "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -4945,19 +6121,26 @@ __metadata: languageName: node linkType: hard +"shell-quote@npm:^1.8.1": + version: 1.8.3 + resolution: "shell-quote@npm:1.8.3" + checksum: 10/5473e354637c2bd698911224129c9a8961697486cff1fb221f234d71c153fc377674029b0223d1d3c953a68d451d79366abfe53d1a0b46ee1f28eb9ade928f4c + languageName: node + linkType: hard + "shiki@npm:^1.26.1": - version: 1.26.1 - resolution: "shiki@npm:1.26.1" + version: 1.29.2 + resolution: "shiki@npm:1.29.2" dependencies: - "@shikijs/core": "npm:1.26.1" - "@shikijs/engine-javascript": "npm:1.26.1" - "@shikijs/engine-oniguruma": "npm:1.26.1" - "@shikijs/langs": "npm:1.26.1" - "@shikijs/themes": "npm:1.26.1" - "@shikijs/types": "npm:1.26.1" + "@shikijs/core": "npm:1.29.2" + "@shikijs/engine-javascript": "npm:1.29.2" + "@shikijs/engine-oniguruma": "npm:1.29.2" + "@shikijs/langs": "npm:1.29.2" + "@shikijs/themes": "npm:1.29.2" + "@shikijs/types": "npm:1.29.2" "@shikijs/vscode-textmate": "npm:^10.0.1" "@types/hast": "npm:^3.0.4" - checksum: 10/10b54241b2c58b0af228dbd00ebf2f7f820ec3527933c7fcbe7c1b634893fe003703f660cbf3ab4d0bdf61d2062590d3c7af5be57cd3192ebb4e5f2900d32f58 + checksum: 10/93de1ebc768ff497ba36720f3b385bff65a853248fdfc0afcb2aea81a43560b1bdc7db9e8417df11a87e106b0de0943989c6301daf62a43d09bd9ac7bc776bf3 languageName: node linkType: hard @@ -5016,6 +6199,43 @@ __metadata: languageName: node linkType: hard +"simple-swizzle@npm:^0.2.2": + version: 0.2.2 + resolution: "simple-swizzle@npm:0.2.2" + dependencies: + is-arrayish: "npm:^0.3.1" + checksum: 10/c6dffff17aaa383dae7e5c056fbf10cf9855a9f79949f20ee225c04f06ddde56323600e0f3d6797e82d08d006e93761122527438ee9531620031c08c9e0d73cc + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10/927484aa0b1640fd9473cee3e0a0bcad6fce93fd7bbc18bac9ad0c33686f5d2e2c422fba24b5899c184524af01e11dd2bd051c2bf2b07e47aff8ca72cbfc60d2 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10/ee99e1dacab0985b52cbe5a75640be6e604135e9489ebdc3048635d186012fbaecc20fbbe04b177dee434c319ba20f09b3e7dfefb7d932466c0d707744eac05c + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.5 + resolution: "socks@npm:2.8.5" + dependencies: + ip-address: "npm:^9.0.5" + smart-buffer: "npm:^4.2.0" + checksum: 10/0109090ec2bcb8d12d3875a987e85539ed08697500ad971a603c3057e4c266b4bf6a603e07af6d19218c422dd9b72d923aaa6c1f20abae275510bba458e4ccc9 + languageName: node + linkType: hard + "source-map-js@npm:^1.0.2": version: 1.2.1 resolution: "source-map-js@npm:1.2.1" @@ -5044,18 +6264,51 @@ __metadata: languageName: node linkType: hard -"stable-hash@npm:^0.0.4": - version: 0.0.4 - resolution: "stable-hash@npm:0.0.4" - checksum: 10/21c039d21c1cb739cf8342561753a5e007cb95ea682ccd452e76310bbb9c6987a89de8eda023e320b019f3e4691aabda75079cdbb7dadf7ab9013e931f2f23cd +"spawn-command@npm:0.0.2": + version: 0.0.2 + resolution: "spawn-command@npm:0.0.2" + checksum: 10/f13e8c3c63abd4a0b52fb567eba5f7940d480c5ed3ec61781d38a1850f179b1196c39e6efa2bbd301f82c1bf1cd7807abc8fbd8fc8e44bcaa3975a124c0d1657 + languageName: node + linkType: hard + +"sprintf-js@npm:^1.1.3": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 10/e7587128c423f7e43cc625fe2f87e6affdf5ca51c1cc468e910d8aaca46bb44a7fbcfa552f787b1d3987f7043aeb4527d1b99559e6621e01b42b3f45e5a24cbb + languageName: node + linkType: hard + +"ssri@npm:^12.0.0": + version: 12.0.0 + resolution: "ssri@npm:12.0.0" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10/7024c1a6e39b3f18aa8f1c8290e884fe91b0f9ca5a6c6d410544daad54de0ba664db879afe16412e187c6c292fd60b937f047ee44292e5c2af2dcc6d8e1a9b48 + languageName: node + linkType: hard + +"stable-hash@npm:^0.0.5": + version: 0.0.5 + resolution: "stable-hash@npm:0.0.5" + checksum: 10/9222ea2c558e37c4a576cb4e406966b9e6aa05b93f5c4f09ef4aaabe3577439b9b8fbff407b16840b63e2ae83de74290c7b1c2da7360d571e480e46a4aec0a56 + languageName: node + linkType: hard + +"stop-iteration-iterator@npm:^1.1.0": + version: 1.1.0 + resolution: "stop-iteration-iterator@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + internal-slot: "npm:^1.1.0" + checksum: 10/ff36c4db171ee76c936ccfe9541946b77017f12703d4c446652017356816862d3aa029a64e7d4c4ceb484e00ed4a81789333896390d808458638f3a216aa1f41 languageName: node linkType: hard "storybook@npm:^8.6.8": - version: 8.6.12 - resolution: "storybook@npm:8.6.12" + version: 8.6.14 + resolution: "storybook@npm:8.6.14" dependencies: - "@storybook/core": "npm:8.6.12" + "@storybook/core": "npm:8.6.14" peerDependencies: prettier: ^2 || ^3 peerDependenciesMeta: @@ -5065,7 +6318,7 @@ __metadata: getstorybook: ./bin/index.cjs sb: ./bin/index.cjs storybook: ./bin/index.cjs - checksum: 10/babd1d086eb02ba25ee659e02e619f7797a6b91028ad74d2da0ab77e72021cd5c2ac4f239668f15156aabf00bd97066a774370dceadf178b1e649bf971160a26 + checksum: 10/104932fe29ebf49bef24c90285741cec964d1c36b3f0b38da1dace31ac664be457f2a510e41ba69fe8aa0d90c20e3446fec27d1ced41e5eefb3a9ca713d99e79 languageName: node linkType: hard @@ -5076,7 +6329,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -5227,28 +6480,37 @@ __metadata: languageName: node linkType: hard -"style-to-object@npm:^1.0.0": - version: 1.0.8 - resolution: "style-to-object@npm:1.0.8" +"style-to-js@npm:1.1.17, style-to-js@npm:^1.0.0": + version: 1.1.17 + resolution: "style-to-js@npm:1.1.17" dependencies: - inline-style-parser: "npm:0.2.4" - checksum: 10/530b067325e3119bfaf75bdbe25cc86b02b559db00d881a74b98a2d5bb10ac953d1b455ed90c825963cf3b4bdaa1bda45f406d78d987391434b8d8ab3835df4e + style-to-object: "npm:1.0.9" + checksum: 10/431f2fca8a55a61939a83ff0f58638e2996621ad93a97cf93f2be5115f411330d4e506ccf18621bd45607ec161546b763bb6961ad08238ad939b6261ff377230 languageName: node linkType: hard -"styled-jsx@npm:5.1.1": - version: 5.1.1 - resolution: "styled-jsx@npm:5.1.1" +"style-to-object@npm:1.0.9": + version: 1.0.9 + resolution: "style-to-object@npm:1.0.9" + dependencies: + inline-style-parser: "npm:0.2.4" + checksum: 10/fd0c131a83103fe4025afd8e0fd90c605054d485ad80f2ab402e7afa79f482f4b05fff40b6aa661cb1b835e5c56bb0644dc38cbf9b3d2982fc552435db3dae50 + languageName: node + linkType: hard + +"styled-jsx@npm:5.1.6": + version: 5.1.6 + resolution: "styled-jsx@npm:5.1.6" dependencies: client-only: "npm:0.0.1" peerDependencies: - react: ">= 16.8.0 || 17.x.x || ^18.0.0-0" + react: ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" peerDependenciesMeta: "@babel/core": optional: true babel-plugin-macros: optional: true - checksum: 10/4f6a5d0010770fdeea1183d919d528fd46c484e23c0535ef3e1dd49488116f639c594f3bd4440e3bc8a8686c9f8d53c5761599870ff039ede11a5c3bfe08a4be + checksum: 10/ba01200e8227fe1441a719c2e7da96c8aa7ef61d14211d1500e1abce12efa118479bcb6e7e12beecb9e1db76432caad2f4e01bbc0c9be21c134b088a4ca5ffe0 languageName: node linkType: hard @@ -5261,6 +6523,15 @@ __metadata: languageName: node linkType: hard +"supports-color@npm:^8.1.1": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10/157b534df88e39c5518c5e78c35580c1eca848d7dbaf31bbe06cdfc048e22c7ff1a9d046ae17b25691128f631a51d9ec373c1b740c12ae4f0de6e292037e4282 + languageName: node + linkType: hard + "supports-preserve-symlinks-flag@npm:^1.0.0": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -5268,10 +6539,17 @@ __metadata: languageName: node linkType: hard -"tapable@npm:^2.2.0": - version: 2.2.1 - resolution: "tapable@npm:2.2.1" - checksum: 10/1769336dd21481ae6347611ca5fca47add0962fd8e80466515032125eca0084a4f0ede11e65341b9c0018ef4e1cf1ad820adbb0fba7cc99865c6005734000b0a +"tar@npm:^7.4.3": + version: 7.4.3 + resolution: "tar@npm:7.4.3" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.0.1" + mkdirp: "npm:^3.0.1" + yallist: "npm:^5.0.0" + checksum: 10/12a2a4fc6dee23e07cc47f1aeb3a14a1afd3f16397e1350036a8f4cdfee8dcac7ef5978337a4e7b2ac2c27a9a6d46388fc2088ea7c80cb6878c814b1425f8ecf languageName: node linkType: hard @@ -5289,6 +6567,16 @@ __metadata: languageName: node linkType: hard +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13": + version: 0.2.14 + resolution: "tinyglobby@npm:0.2.14" + dependencies: + fdir: "npm:^6.4.4" + picomatch: "npm:^4.0.2" + checksum: 10/3d306d319718b7cc9d79fb3f29d8655237aa6a1f280860a217f93417039d0614891aee6fc47c5db315f4fcc6ac8d55eb8e23e2de73b2c51a431b42456d9e5764 + languageName: node + linkType: hard + "to-regex-range@npm:^5.0.1": version: 5.0.1 resolution: "to-regex-range@npm:5.0.1" @@ -5298,6 +6586,15 @@ __metadata: languageName: node linkType: hard +"tree-kill@npm:^1.2.2": + version: 1.2.2 + resolution: "tree-kill@npm:1.2.2" + bin: + tree-kill: cli.js + checksum: 10/49117f5f410d19c84b0464d29afb9642c863bc5ba40fcb9a245d474c6d5cc64d1b177a6e6713129eb346b40aebb9d4631d967517f9fbe8251c35b21b13cd96c7 + languageName: node + linkType: hard + "trim-lines@npm:^3.0.0": version: 3.0.1 resolution: "trim-lines@npm:3.0.1" @@ -5312,12 +6609,12 @@ __metadata: languageName: node linkType: hard -"ts-api-utils@npm:^2.0.0": - version: 2.0.0 - resolution: "ts-api-utils@npm:2.0.0" +"ts-api-utils@npm:^2.1.0": + version: 2.1.0 + resolution: "ts-api-utils@npm:2.1.0" peerDependencies: typescript: ">=4.8.4" - checksum: 10/485bdf8bbba98d58712243d958f4fd44742bbe49e559cd77882fb426d866eec6dd05c67ef91935dc4f8a3c776f235859735e1f05be399e4dc9e7ffd580120974 + checksum: 10/02e55b49d9617c6eebf8aadfa08d3ca03ca0cd2f0586ad34117fdfc7aa3cd25d95051843fde9df86665ad907f99baed179e7a117b11021417f379e4d2614eacd languageName: node linkType: hard @@ -5333,7 +6630,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.1, tslib@npm:^2.4.0": +"tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.8.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 @@ -5356,6 +6653,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^4.31.0": + version: 4.41.0 + resolution: "type-fest@npm:4.41.0" + checksum: 10/617ace794ac0893c2986912d28b3065ad1afb484cad59297835a0807dc63286c39e8675d65f7de08fafa339afcb8fe06a36e9a188b9857756ae1e92ee8bda212 + languageName: node + linkType: hard + "typed-array-buffer@npm:^1.0.3": version: 1.0.3 resolution: "typed-array-buffer@npm:1.0.3" @@ -5441,10 +6745,10 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~6.19.2": - version: 6.19.8 - resolution: "undici-types@npm:6.19.8" - checksum: 10/cf0b48ed4fc99baf56584afa91aaffa5010c268b8842f62e02f752df209e3dea138b372a60a963b3b2576ed932f32329ce7ddb9cb5f27a6c83040d8cd74b7a70 +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10/ec8f41aa4359d50f9b59fa61fe3efce3477cc681908c8f84354d8567bb3701fafdddf36ef6bff307024d3feb42c837cf6f670314ba37fc8145e219560e473d14 languageName: node linkType: hard @@ -5463,6 +6767,24 @@ __metadata: languageName: node linkType: hard +"unique-filename@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-filename@npm:4.0.0" + dependencies: + unique-slug: "npm:^5.0.0" + checksum: 10/6a62094fcac286b9ec39edbd1f8f64ff92383baa430af303dfed1ffda5e47a08a6b316408554abfddd9730c78b6106bef4ca4d02c1231a735ddd56ced77573df + languageName: node + linkType: hard + +"unique-slug@npm:^5.0.0": + version: 5.0.0 + resolution: "unique-slug@npm:5.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10/beafdf3d6f44990e0a5ce560f8f881b4ee811be70b6ba0db25298c31c8cf525ed963572b48cd03be1c1349084f9e339be4241666d7cf1ebdad20598d3c652b27 + languageName: node + linkType: hard + "unist-util-is@npm:^6.0.0": version: 6.0.0 resolution: "unist-util-is@npm:6.0.0" @@ -5490,6 +6812,17 @@ __metadata: languageName: node linkType: hard +"unist-util-remove@npm:^4.0.0": + version: 4.0.0 + resolution: "unist-util-remove@npm:4.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + unist-util-is: "npm:^6.0.0" + unist-util-visit-parents: "npm:^6.0.0" + checksum: 10/24a84224720f4c2d2ffce1d570c00b83ef545de80ec32a246698feff880446af6c410fc94deec4b4daafe09851d59f17cd1770e20f66d68092e3cd60c2147fb8 + languageName: node + linkType: hard + "unist-util-stringify-position@npm:^4.0.0": version: 4.0.0 resolution: "unist-util-stringify-position@npm:4.0.0" @@ -5520,6 +6853,67 @@ __metadata: languageName: node linkType: hard +"unrs-resolver@npm:^1.6.2": + version: 1.7.13 + resolution: "unrs-resolver@npm:1.7.13" + dependencies: + "@unrs/resolver-binding-darwin-arm64": "npm:1.7.13" + "@unrs/resolver-binding-darwin-x64": "npm:1.7.13" + "@unrs/resolver-binding-freebsd-x64": "npm:1.7.13" + "@unrs/resolver-binding-linux-arm-gnueabihf": "npm:1.7.13" + "@unrs/resolver-binding-linux-arm-musleabihf": "npm:1.7.13" + "@unrs/resolver-binding-linux-arm64-gnu": "npm:1.7.13" + "@unrs/resolver-binding-linux-arm64-musl": "npm:1.7.13" + "@unrs/resolver-binding-linux-ppc64-gnu": "npm:1.7.13" + "@unrs/resolver-binding-linux-riscv64-gnu": "npm:1.7.13" + "@unrs/resolver-binding-linux-riscv64-musl": "npm:1.7.13" + "@unrs/resolver-binding-linux-s390x-gnu": "npm:1.7.13" + "@unrs/resolver-binding-linux-x64-gnu": "npm:1.7.13" + "@unrs/resolver-binding-linux-x64-musl": "npm:1.7.13" + "@unrs/resolver-binding-wasm32-wasi": "npm:1.7.13" + "@unrs/resolver-binding-win32-arm64-msvc": "npm:1.7.13" + "@unrs/resolver-binding-win32-ia32-msvc": "npm:1.7.13" + "@unrs/resolver-binding-win32-x64-msvc": "npm:1.7.13" + napi-postinstall: "npm:^0.2.2" + dependenciesMeta: + "@unrs/resolver-binding-darwin-arm64": + optional: true + "@unrs/resolver-binding-darwin-x64": + optional: true + "@unrs/resolver-binding-freebsd-x64": + optional: true + "@unrs/resolver-binding-linux-arm-gnueabihf": + optional: true + "@unrs/resolver-binding-linux-arm-musleabihf": + optional: true + "@unrs/resolver-binding-linux-arm64-gnu": + optional: true + "@unrs/resolver-binding-linux-arm64-musl": + optional: true + "@unrs/resolver-binding-linux-ppc64-gnu": + optional: true + "@unrs/resolver-binding-linux-riscv64-gnu": + optional: true + "@unrs/resolver-binding-linux-riscv64-musl": + optional: true + "@unrs/resolver-binding-linux-s390x-gnu": + optional: true + "@unrs/resolver-binding-linux-x64-gnu": + optional: true + "@unrs/resolver-binding-linux-x64-musl": + optional: true + "@unrs/resolver-binding-wasm32-wasi": + optional: true + "@unrs/resolver-binding-win32-arm64-msvc": + optional: true + "@unrs/resolver-binding-win32-ia32-msvc": + optional: true + "@unrs/resolver-binding-win32-x64-msvc": + optional: true + checksum: 10/362aa31e5cd635cf5955a7eb3fe58230a1a32bd5daba4c71e7620e0a0fcd35fffb926655e32d4834a82d48b676cf8a2208cf1de30e30b4bc85c2dc67fa60acb8 + languageName: node + linkType: hard + "uri-js@npm:^4.2.2": version: 4.4.1 resolution: "uri-js@npm:4.4.1" @@ -5542,6 +6936,16 @@ __metadata: languageName: node linkType: hard +"vfile-matter@npm:^5.0.1": + version: 5.0.1 + resolution: "vfile-matter@npm:5.0.1" + dependencies: + vfile: "npm:^6.0.0" + yaml: "npm:^2.0.0" + checksum: 10/5eddf848617050df7b539b68c60f4850d4f87e848accb44eb541719cece48a170897fae759023d0f3a87bcfc65fc2183a9875f11c7b3dc9ceaab7b4d739f74d6 + languageName: node + linkType: hard + "vfile-message@npm:^4.0.0": version: 4.0.2 resolution: "vfile-message@npm:4.0.2" @@ -5552,7 +6956,7 @@ __metadata: languageName: node linkType: hard -"vfile@npm:^6.0.0": +"vfile@npm:^6.0.0, vfile@npm:^6.0.3": version: 6.0.3 resolution: "vfile@npm:6.0.3" dependencies: @@ -5615,7 +7019,7 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18, which-typed-array@npm:^1.1.2": +"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19, which-typed-array@npm:^1.1.2": version: 1.1.19 resolution: "which-typed-array@npm:1.1.19" dependencies: @@ -5641,6 +7045,17 @@ __metadata: languageName: node linkType: hard +"which@npm:^5.0.0": + version: 5.0.0 + resolution: "which@npm:5.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10/6ec99e89ba32c7e748b8a3144e64bfc74aa63e2b2eacbb61a0060ad0b961eb1a632b08fb1de067ed59b002cec3e21de18299216ebf2325ef0f78e0f121e14e90 + languageName: node + linkType: hard + "word-wrap@npm:^1.2.5": version: 1.2.5 resolution: "word-wrap@npm:1.2.5" @@ -5648,7 +7063,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" dependencies: @@ -5678,8 +7093,8 @@ __metadata: linkType: hard "ws@npm:^8.2.3": - version: 8.18.1 - resolution: "ws@npm:8.18.1" + version: 8.18.2 + resolution: "ws@npm:8.18.2" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -5688,7 +7103,59 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10/3f38e9594f2af5b6324138e86b74df7d77bbb8e310bf8188679dd80bac0d1f47e51536a1923ac3365f31f3d8b25ea0b03e4ade466aa8292a86cd5defca64b19b + checksum: 10/018e04ec95561d88248d53a2eaf094b4ae131e9b062f2679e6e8a62f04649bc543448f1e038125225ac6bbb25f54c1e65d7a2cc9dbc1e28b43e5e6b7162ad88e + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10/5f1b5f95e3775de4514edbb142398a2c37849ccfaf04a015be5d75521e9629d3be29bd4432d23c57f37e5b61ade592fb0197022e9993f81a06a5afbdcda9346d + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10/4cb02b42b8a93b5cf50caf5d8e9beb409400a8a4d85e83bb0685c1457e9ac0b7a00819e9f5991ac25ffabb56a78e2f017c1acc010b3a1babfe6de690ba531abd + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10/1884d272d485845ad04759a255c71775db0fac56308764b4c77ea56a20d56679fad340213054c8c9c9c26fcfd4c4b2a90df993b7e0aaf3cdb73c618d1d1a802a + languageName: node + linkType: hard + +"yaml@npm:^2.0.0": + version: 2.8.0 + resolution: "yaml@npm:2.8.0" + bin: + yaml: bin.mjs + checksum: 10/7d4bd9c10d0e467601f496193f2ac254140f8e36f96f5ff7f852b9ce37974168eb7354f4c36dc8837dde527a2043d004b6aff48818ec24a69ab2dd3c6b6c381c + languageName: node + linkType: hard + +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10/9dc2c217ea3bf8d858041252d43e074f7166b53f3d010a8c711275e09cd3d62a002969a39858b92bbda2a6a63a585c7127014534a560b9c69ed2d923d113406e + languageName: node + linkType: hard + +"yargs@npm:^17.7.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10/abb3e37678d6e38ea85485ed86ebe0d1e3464c640d7d9069805ea0da12f69d5a32df8e5625e370f9c96dd1c2dc088ab2d0a4dd32af18222ef3c4224a19471576 languageName: node linkType: hard diff --git a/docs/architecture-decisions/adr000-template.md b/docs/architecture-decisions/adr000-template.md index f16ae7d39f..267eceab44 100644 --- a/docs/architecture-decisions/adr000-template.md +++ b/docs/architecture-decisions/adr000-template.md @@ -1,7 +1,6 @@ --- id: adrs-adr000 title: 'ADR000: [TITLE]' -# prettier-ignore description: Architecture Decision Record (ADR) for [TITLE] [DESCRIPTION] --- diff --git a/docs/architecture-decisions/adr001-add-adr-log.md b/docs/architecture-decisions/adr001-add-adr-log.md index de4283319a..8ad0fa3748 100644 --- a/docs/architecture-decisions/adr001-add-adr-log.md +++ b/docs/architecture-decisions/adr001-add-adr-log.md @@ -1,7 +1,6 @@ --- id: adrs-adr001 title: 'ADR001: Architecture Decision Record (ADR) log' -# prettier-ignore description: Architecture Decision Record (ADR) logs as a reference point for the team --- diff --git a/docs/architecture-decisions/adr002-default-catalog-file-format.md b/docs/architecture-decisions/adr002-default-catalog-file-format.md index 3b606cf46b..eb84913313 100644 --- a/docs/architecture-decisions/adr002-default-catalog-file-format.md +++ b/docs/architecture-decisions/adr002-default-catalog-file-format.md @@ -1,7 +1,6 @@ --- id: adrs-adr002 title: 'ADR002: Default Software Catalog File Format' -# prettier-ignore description: Architecture Decision Record (ADR) log on Default Software Catalog File Format --- diff --git a/docs/architecture-decisions/adr003-avoid-default-exports.md b/docs/architecture-decisions/adr003-avoid-default-exports.md index 24445f0be1..8ba37914df 100644 --- a/docs/architecture-decisions/adr003-avoid-default-exports.md +++ b/docs/architecture-decisions/adr003-avoid-default-exports.md @@ -1,7 +1,6 @@ --- id: adrs-adr003 title: 'ADR003: Avoid Default Exports and Prefer Named Exports' -# prettier-ignore description: Architecture Decision Record (ADR) log on Avoid Default Exports and Prefer Named Exports --- diff --git a/docs/architecture-decisions/adr004-module-export-structure.md b/docs/architecture-decisions/adr004-module-export-structure.md index 95d0f27811..507859ef01 100644 --- a/docs/architecture-decisions/adr004-module-export-structure.md +++ b/docs/architecture-decisions/adr004-module-export-structure.md @@ -1,7 +1,6 @@ --- id: adrs-adr004 title: 'ADR004: Module Export Structure' -# prettier-ignore description: Architecture Decision Record (ADR) log on Module Export Structure --- diff --git a/docs/architecture-decisions/adr005-catalog-core-entities.md b/docs/architecture-decisions/adr005-catalog-core-entities.md index 1206d9e83d..ca65425645 100644 --- a/docs/architecture-decisions/adr005-catalog-core-entities.md +++ b/docs/architecture-decisions/adr005-catalog-core-entities.md @@ -1,7 +1,6 @@ --- id: adrs-adr005 title: 'ADR005: Catalog Core Entities' -# prettier-ignore description: Architecture Decision Record (ADR) log on Catalog Core Entities --- diff --git a/docs/architecture-decisions/adr006-avoid-react-fc.md b/docs/architecture-decisions/adr006-avoid-react-fc.md index 6a7c431655..0630718763 100644 --- a/docs/architecture-decisions/adr006-avoid-react-fc.md +++ b/docs/architecture-decisions/adr006-avoid-react-fc.md @@ -1,7 +1,6 @@ --- id: adrs-adr006 title: 'ADR006: Avoid React.FC and React.SFC' -# prettier-ignore description: Architecture Decision Record (ADR) log on Avoid React.FC and React.SFC --- diff --git a/docs/architecture-decisions/adr007-use-msw-to-mock-service-requests.md b/docs/architecture-decisions/adr007-use-msw-to-mock-service-requests.md index a61bb78787..535adea8cb 100644 --- a/docs/architecture-decisions/adr007-use-msw-to-mock-service-requests.md +++ b/docs/architecture-decisions/adr007-use-msw-to-mock-service-requests.md @@ -1,7 +1,6 @@ --- id: adrs-adr007 title: 'ADR007: Use MSW to mock http requests' -# prettier-ignore description: Architecture Decision Record (ADR) log on Use MSW to mock http requests --- diff --git a/docs/architecture-decisions/adr008-default-catalog-file-name.md b/docs/architecture-decisions/adr008-default-catalog-file-name.md index fca65c8355..1f00c19b7e 100644 --- a/docs/architecture-decisions/adr008-default-catalog-file-name.md +++ b/docs/architecture-decisions/adr008-default-catalog-file-name.md @@ -1,7 +1,6 @@ --- id: adrs-adr008 title: 'ADR008: Default Catalog File Name' -# prettier-ignore description: Architecture Decision Record (ADR) log on Default Catalog File Name --- diff --git a/docs/architecture-decisions/adr009-entity-references.md b/docs/architecture-decisions/adr009-entity-references.md index 7cac946388..5779503113 100644 --- a/docs/architecture-decisions/adr009-entity-references.md +++ b/docs/architecture-decisions/adr009-entity-references.md @@ -1,7 +1,6 @@ --- id: adrs-adr009 title: 'ADR009: Entity References' -# prettier-ignore description: Architecture Decision Record (ADR) log on Entity References --- diff --git a/docs/architecture-decisions/adr010-luxon-date-library.md b/docs/architecture-decisions/adr010-luxon-date-library.md index a7b287ae0d..262a468fa9 100644 --- a/docs/architecture-decisions/adr010-luxon-date-library.md +++ b/docs/architecture-decisions/adr010-luxon-date-library.md @@ -1,7 +1,6 @@ --- id: adrs-adr010 title: 'ADR010: Use the Luxon Date Library' -# prettier-ignore description: Architecture Decision Record (ADR) for Luxon Date Library --- diff --git a/docs/architecture-decisions/adr011-plugin-package-structure.md b/docs/architecture-decisions/adr011-plugin-package-structure.md index 8e4b5435f9..323483e2d9 100644 --- a/docs/architecture-decisions/adr011-plugin-package-structure.md +++ b/docs/architecture-decisions/adr011-plugin-package-structure.md @@ -1,7 +1,6 @@ --- id: adrs-adr011 title: 'ADR011: Plugin Package Structure' -# prettier-ignore description: Architecture Decision Record (ADR) for Plugin Package Structure --- diff --git a/docs/architecture-decisions/adr012-use-luxon-locale-and-date-presets.md b/docs/architecture-decisions/adr012-use-luxon-locale-and-date-presets.md index 25bb6c1833..423e2c58f6 100644 --- a/docs/architecture-decisions/adr012-use-luxon-locale-and-date-presets.md +++ b/docs/architecture-decisions/adr012-use-luxon-locale-and-date-presets.md @@ -1,7 +1,6 @@ --- id: adrs-adr012 title: 'ADR012: Use Luxon.toLocaleString and date/time presets' -# prettier-ignore description: Architecture Decision Record (ADR) for using Luxon's toLocaleString method and date/time presets for displaying dates and times --- diff --git a/docs/architecture-decisions/adr013-use-node-fetch.md b/docs/architecture-decisions/adr013-use-node-fetch.md index e65cf0a97b..664faed1db 100644 --- a/docs/architecture-decisions/adr013-use-node-fetch.md +++ b/docs/architecture-decisions/adr013-use-node-fetch.md @@ -1,7 +1,6 @@ --- id: adrs-adr013 title: 'ADR013: [superseded] Proper use of HTTP fetching libraries' -# prettier-ignore description: Architecture Decision Record (ADR) for the proper use of fetchApiRef, node-fetch, and cross-fetch for data fetching. --- diff --git a/docs/architecture-decisions/adr014-use-fetch.md b/docs/architecture-decisions/adr014-use-fetch.md index 8454e69e7c..9faa6c9ead 100644 --- a/docs/architecture-decisions/adr014-use-fetch.md +++ b/docs/architecture-decisions/adr014-use-fetch.md @@ -1,7 +1,6 @@ --- id: adrs-adr014 title: 'ADR014: Proper use of HTTP fetching libraries' -# prettier-ignore description: Architecture Decision Record (ADR) for the proper use of fetchApiRef, native fetch, and cross-fetch for data fetching. --- diff --git a/docs/architecture-decisions/adr015-jsx-loader-structure.md b/docs/architecture-decisions/adr015-jsx-loader-structure.md new file mode 100644 index 0000000000..b50ccb90e4 --- /dev/null +++ b/docs/architecture-decisions/adr015-jsx-loader-structure.md @@ -0,0 +1,77 @@ +--- +id: adrs-adr015 +title: 'ADR015: Types and naming for element and component options' +description: Architecture Decision Record (ADR) for the proper types and naming for element and component options +--- + +## Context + +Until now there hasn't been a clear standard for how to define options that are intended to provide JSX elements or components. This led to a mix of different patterns in public APIs, which this ADR aims to standardize. + +## Decision + +We will use one of the following option property names and types when defining options that are intended to provide JSX elements or components: + +### Simple element + +This option is used when a simple synchronous JSX element is provided. It must only be used in areas where lazy-loading is not needed. + +```tsx +{ + element: JSX.Element; +} +``` + +### Simple component + +This option is used when a simple synchronous component is provided. It must only be used in areas where lazy-loading is not needed. + +```tsx +{ + component: (props: { ... }) => JSX.Element | null +} +``` + +### Async element loader + +This option is used when a simple asynchronous JSX element is provided. It is the preferred option when only producing a single instance and there is no need to pass properties to the component. This format simplifies the creation of closures for passing additional properties in the loader implementation. + +```tsx +{ + loader: () => Promise; +} +``` + +### Async component loader + +This option is used when a simple asynchronous component is provided. It is the preferred option when properties need to be passed to the component or multiple instance are needed, and lazy-loading is required. + +```tsx +{ + loader: () => Promise<(props: { ... }) => JSX.Element | null> +} +``` + +### Any component loader + +This option is used in the same cases as the async component loader, but when the option of synchronous loading is also needed. The structure of always having the outer loader function, even in the synchronous case, makes it possible to determine the type of the loader at runtime. + +```tsx +{ + loader: (() => props => JSX.Element | null) | (() => Promise JSX.Element | null>) +} +``` + +Note that when consuming this loader we'll need to unconditionally wrap it with `React.lazy`. This is because you can't delay the call to `React.lazy` until rendering, because you're not allowed to call it within a render function. This means that we can't first call the loader to check whether the returned value is a promise or not, and we must instead unconditionally wrap it with `React.lazy`. Therefore the implementation of accepting one of these loaders as an option needs to look something like this: + +```tsx +const LazyComponent = React.lazy(() => + Promise.resolve(options.loader()).then(loaded => ({ default: loaded })), +); +``` + +## Consequences + +We will update all APIs for the new frontend system in the `@backstage/frontend-*` packages. + +We will not update any of the existing APIs for the old frontend system in the `@backstage/core-*` packages. diff --git a/docs/architecture-decisions/index.md b/docs/architecture-decisions/index.md index b4ba84da0e..eddbdbe81e 100644 --- a/docs/architecture-decisions/index.md +++ b/docs/architecture-decisions/index.md @@ -2,13 +2,12 @@ id: adrs-overview title: Architecture Decision Records (ADR) sidebar_label: Overview -# prettier-ignore description: Overview of Architecture Decision Records (ADR) --- The substantial architecture decisions made in the Backstage project live here. For more information about ADRs, when to write them, and why, please see -[this blog post](https://engineering.atspotify.com/2020/04/14/when-should-i-write-an-architecture-decision-record/). +[this blog post](https://engineering.atspotify.com/2020/04/when-should-i-write-an-architecture-decision-record/). Records are never deleted but can be marked as superseded by new decisions or deprecated. diff --git a/docs/auth/add-auth-provider.md b/docs/auth/add-auth-provider.md index 7cfa21dd4a..963b58dcea 100644 --- a/docs/auth/add-auth-provider.md +++ b/docs/auth/add-auth-provider.md @@ -117,11 +117,9 @@ due to its comprehensive set of supported authentication ### Quick guide -[1.](#create-new-auth-provider-module) Create a new auth provider module +[1.](#create-new-auth-provider-module) Create a new auth provider module or [add a proxy auth based provider](#creating-proxy-auth-based-provider) depending on your needs. -[3.](#adding-an-oauth-based-provider) or [adding a proxy auth based provider](#creating-proxy-auth-based-provider) depending on your needs. - -[4.](#add-the-provider-to-the-backend) Add the provider to the backend. +[2.](#add-the-provider-to-the-backend) Add the provider to the backend. ### Create new auth provider module diff --git a/docs/auth/autologout.md b/docs/auth/autologout.md index b85f6f486d..e2462522f4 100644 --- a/docs/auth/autologout.md +++ b/docs/auth/autologout.md @@ -1,7 +1,6 @@ --- id: autologout title: Auto Logout -# prettier-ignore description: This section describes how to setup the Auto Logout mechanism in Backstage --- @@ -69,7 +68,7 @@ These are the available settings: | Configuration Key | Component Property | Description | Allowed Values | Default Value | | ----------------------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | --------------------------- | | `auth.autologout.enabled` | `enabled` | Enable/disable the Auto Logout feature. | `true`/`false` | Default is enabled (`true`) | -| `auth.autologout.idleTimeoutMinutes` | `idleTimeoutMinutes` | Sets the idle time (in minutes) after which the user will be logged out. | `>= 0.5` minutes | `60` | +| `auth.autologout.idleTimeoutMinutes` | `idleTimeoutMinutes` | Sets the idle time (in minutes) after which the user will be logged out. | `>= 0.5` minutes | `0.5` | | `auth.autologout.promptBeforeIdleSeconds` | `promptBeforeIdleSeconds` | Determines the time (in seconds) prior to idle state when a prompt will appear. A value of 0 disables the prompt. This must be less than the value of `idleTimeoutMinutes`. | `>= 0` seconds | `10` | | `auth.autologout.useWorkerTimers` | `useWorkerTimers` | Enables or disables the use of Node's worker thread timers instead of main thread timers. This can be beneficial if the browser is terminating timers in inactive tabs, like those used by auto logout. In case of browser incompatibility, try setting this to false. | `true`/`false` | `true` | | `auth.autologout.logoutIfDisconnected` | `logoutIfDisconnected` | Enable/disable auto logout for disconnected users. Disconnected users are those who are logged in but do not have any active Backstage tabs open in their browsers. If enabled, such users will be automatically logged out after `idleTimeoutMinutes`. | `true`/`false` | `true` | diff --git a/docs/auth/google/gcp-iap-auth.md b/docs/auth/google/gcp-iap-auth.md index 6fe8b56d9e..386fab383f 100644 --- a/docs/auth/google/gcp-iap-auth.md +++ b/docs/auth/google/gcp-iap-auth.md @@ -2,7 +2,6 @@ id: gcp-iap-auth title: Google Identity-Aware Proxy Provider sidebar_label: Google IAP -# prettier-ignore description: Adding Google Identity-Aware Proxy as an authentication provider in Backstage --- diff --git a/docs/auth/identity-resolver.md b/docs/auth/identity-resolver.md index 3d15c9541e..5fb69c7955 100644 --- a/docs/auth/identity-resolver.md +++ b/docs/auth/identity-resolver.md @@ -351,8 +351,12 @@ users to sign in, for example by checking email domains. While populating the catalog with organizational data unlocks more powerful ways to browse your software ecosystem, it might not always be a viable or prioritized option. However, even if you do not have user entities populated in your catalog, you -can still sign in users. As there are currently no built-in sign-in resolvers for -this scenario you will need to implement your own. +can still sign in users. + +##### Custom Sign-in Resolver to bypass user in catalog requirement + +As there are currently no built-in sign-in resolvers for +this scenario you may want to implement your own. Signing in a user that doesn't exist in the catalog is as simple as skipping the catalog lookup step from the above example. Rather than looking up the user, we @@ -446,6 +450,36 @@ return ctx.issueToken({ }); ``` +##### Using the `dangerouslyAllowSignInWithoutUserInCatalog` Option + +Another way to bypass this requirement is to enable the `dangerouslyAllowSignInWithoutUserInCatalog` option for resolvers. +Users will still be authenticated as usual but this config will bypass the check that ensures the user is present in the catalog. +If the user entity is not found in the catalog, a Backstage user token will still be issued based on the identifying information available at the resolver level. + +For example: + +```yaml title="Within the provider configuration" +auth: + providers: + github: + development: + ... + signIn: + resolvers: + - resolver: emailLocalPartMatchingUserEntityName + dangerouslyAllowSignInWithoutUserInCatalog: true +``` + +:::warning +Enabling this option in production poses security risks. +::: + +This option may grant access to unexpected users who haven’t been onboarded into +Backstage. Since there is no user entity to associate with the signed-in user, permissions +may not apply as expected and they will have the same permissions as a guest user. +Careful consideration should be given to the permissions assigned to such users, +particularly when using the permission system. + ## Profile Transforms Similar to a custom sign-in resolver, you can also write a custom profile transform diff --git a/docs/auth/oauth.md b/docs/auth/oauth.md index fbd87d653b..06b204fdaf 100644 --- a/docs/auth/oauth.md +++ b/docs/auth/oauth.md @@ -1,7 +1,6 @@ --- id: oauth title: OAuth and OpenID Connect -# prettier-ignore description: This section describes how Backstage allows plugins to request OAuth Access Tokens and OpenID Connect ID Tokens on behalf of the user, to be used for auth to various third party APIs --- diff --git a/docs/auth/oauth2-proxy/provider.md b/docs/auth/oauth2-proxy/provider.md index 2fa027e0ad..0c28acadc2 100644 --- a/docs/auth/oauth2-proxy/provider.md +++ b/docs/auth/oauth2-proxy/provider.md @@ -11,7 +11,7 @@ The Backstage `@backstage/plugin-auth-backend` package comes with an actual Backstage instance. This enables to reuse existing authentications within a cluster. In general the `oauth2-proxy` supports all OpenID Connect providers, for more details check this -[list of supported providers](https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider). +[list of supported providers](https://oauth2-proxy.github.io/oauth2-proxy/configuration/providers/). :::note Note diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index fecb3f5191..d35aed364d 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -1,7 +1,6 @@ --- id: service-to-service-auth title: Service to Service Auth -# prettier-ignore description: This section describes service to service authentication works, both internally within Backstage plugins and when external callers want to make requests. --- diff --git a/docs/backend-system/architecture/01-index.md b/docs/backend-system/architecture/01-index.md index 968ff2f001..293a46b7ca 100644 --- a/docs/backend-system/architecture/01-index.md +++ b/docs/backend-system/architecture/01-index.md @@ -2,7 +2,6 @@ id: index title: Backend System Architecture sidebar_label: Overview -# prettier-ignore description: The structure and architecture of the new Backend System and its component parts --- diff --git a/docs/backend-system/architecture/02-backends.md b/docs/backend-system/architecture/02-backends.md index 1310453a3f..8f903eb7a8 100644 --- a/docs/backend-system/architecture/02-backends.md +++ b/docs/backend-system/architecture/02-backends.md @@ -2,7 +2,6 @@ id: backends title: Backend Instances sidebar_label: Backend -# prettier-ignore description: Backend instances --- diff --git a/docs/backend-system/architecture/03-services.md b/docs/backend-system/architecture/03-services.md index ad437e471e..cfcd606d92 100644 --- a/docs/backend-system/architecture/03-services.md +++ b/docs/backend-system/architecture/03-services.md @@ -2,7 +2,6 @@ id: services title: Backend Services sidebar_label: Services -# prettier-ignore description: Services for backend plugins --- diff --git a/docs/backend-system/architecture/04-plugins.md b/docs/backend-system/architecture/04-plugins.md index f2a6b856b9..fdb5012569 100644 --- a/docs/backend-system/architecture/04-plugins.md +++ b/docs/backend-system/architecture/04-plugins.md @@ -2,7 +2,6 @@ id: plugins title: Backend Plugins sidebar_label: Plugins -# prettier-ignore description: Backend plugins --- diff --git a/docs/backend-system/architecture/05-extension-points.md b/docs/backend-system/architecture/05-extension-points.md index 12c4ec75f5..2e11d2763f 100644 --- a/docs/backend-system/architecture/05-extension-points.md +++ b/docs/backend-system/architecture/05-extension-points.md @@ -2,7 +2,6 @@ id: extension-points title: Backend Plugin Extension Points sidebar_label: Extension Points -# prettier-ignore description: Extension points of backend plugins --- diff --git a/docs/backend-system/architecture/06-modules.md b/docs/backend-system/architecture/06-modules.md index 010f36109c..e8905808ee 100644 --- a/docs/backend-system/architecture/06-modules.md +++ b/docs/backend-system/architecture/06-modules.md @@ -2,7 +2,6 @@ id: modules title: Plugin Modules sidebar_label: Modules -# prettier-ignore description: Modules for backend plugins --- diff --git a/docs/backend-system/architecture/07-feature-loaders.md b/docs/backend-system/architecture/07-feature-loaders.md index b2cc574bf9..0a2d611b7b 100644 --- a/docs/backend-system/architecture/07-feature-loaders.md +++ b/docs/backend-system/architecture/07-feature-loaders.md @@ -2,7 +2,6 @@ id: feature-loaders title: Backend Feature Loaders sidebar_label: Feature Loaders -# prettier-ignore description: Backend feature loaders --- diff --git a/docs/backend-system/architecture/08-naming-patterns.md b/docs/backend-system/architecture/08-naming-patterns.md index 32ed1d9035..585a9fe87c 100644 --- a/docs/backend-system/architecture/08-naming-patterns.md +++ b/docs/backend-system/architecture/08-naming-patterns.md @@ -2,7 +2,6 @@ id: naming-patterns title: Backend System Naming Patterns sidebar_label: Naming Patterns -# prettier-ignore description: Naming patterns in the backend system --- diff --git a/docs/backend-system/building-backends/01-index.md b/docs/backend-system/building-backends/01-index.md index ec95e57973..0f0a8e01ea 100644 --- a/docs/backend-system/building-backends/01-index.md +++ b/docs/backend-system/building-backends/01-index.md @@ -2,7 +2,6 @@ id: index title: Building Backends sidebar_label: Overview -# prettier-ignore description: Building backends using the new backend system --- diff --git a/docs/backend-system/building-backends/08-migrating.md b/docs/backend-system/building-backends/08-migrating.md index a827171ff7..c07862cc3c 100644 --- a/docs/backend-system/building-backends/08-migrating.md +++ b/docs/backend-system/building-backends/08-migrating.md @@ -2,7 +2,6 @@ id: migrating title: Migrating your Backend to the New Backend System sidebar_label: Migration Guide -# prettier-ignore description: How to migrate existing backends to the new backend system --- diff --git a/docs/backend-system/building-plugins-and-modules/01-index.md b/docs/backend-system/building-plugins-and-modules/01-index.md index db78faaced..53bf4af8ef 100644 --- a/docs/backend-system/building-plugins-and-modules/01-index.md +++ b/docs/backend-system/building-plugins-and-modules/01-index.md @@ -2,7 +2,6 @@ id: index title: Building Backend Plugins and Modules sidebar_label: Overview -# prettier-ignore description: Building backend plugins and modules using the new backend system --- diff --git a/docs/backend-system/building-plugins-and-modules/02-testing.md b/docs/backend-system/building-plugins-and-modules/02-testing.md index f72d2de49b..6000c14a45 100644 --- a/docs/backend-system/building-plugins-and-modules/02-testing.md +++ b/docs/backend-system/building-plugins-and-modules/02-testing.md @@ -2,7 +2,6 @@ id: testing title: Testing Backend Plugins and Modules sidebar_label: Testing -# prettier-ignore description: Learn how to test your backend plugins and modules --- diff --git a/docs/backend-system/building-plugins-and-modules/08-migrating.md b/docs/backend-system/building-plugins-and-modules/08-migrating.md index 5da443328c..f48c5579f6 100644 --- a/docs/backend-system/building-plugins-and-modules/08-migrating.md +++ b/docs/backend-system/building-plugins-and-modules/08-migrating.md @@ -2,7 +2,6 @@ id: migrating title: Migrating your Backend Plugin to the New Backend System sidebar_label: Migration Guide -# prettier-ignore description: How to migrate existing backend plugins to the new backend system --- diff --git a/docs/backend-system/core-services/01-index.md b/docs/backend-system/core-services/01-index.md index 3be12b2e26..50ebea0e4f 100644 --- a/docs/backend-system/core-services/01-index.md +++ b/docs/backend-system/core-services/01-index.md @@ -2,7 +2,6 @@ id: index title: Core Backend Service APIs sidebar_label: Overview -# prettier-ignore description: Core backend service APIs --- diff --git a/docs/backend-system/core-services/actions-registry.md b/docs/backend-system/core-services/actions-registry.md new file mode 100644 index 0000000000..b1e8fe5f3d --- /dev/null +++ b/docs/backend-system/core-services/actions-registry.md @@ -0,0 +1,177 @@ +--- +id: actions-registry +title: Actions Registry (alpha) +sidebar_label: Actions Registry (alpha) +description: Documentation for the Actions Registry Service +--- + +## Overview + +The Actions Registry Service is a core service designed to provide a distributed registry for actions that can be executed within Backstage backend plugins. This service allows plugins to register reusable actions with well-defined schemas and execution logic, promoting consistency and reusability across the Backstage ecosystem. + +## Action Structure + +Each action registered with the service must conform to the `ActionsRegistryActionOptions` type, which includes: + +### Required Properties + +- **`name`:** A unique identifier for the action (string) +- **`title`:** A human-readable title for the action (string) +- **`description`:** A detailed description of what the action does (string) +- **`schema`:** Object containing input and output schema definitions + - **`input`:** Function that returns a Zod schema for validating input + - **`output`:** Function that returns a Zod schema for validating output +- **`action`:** The async function that executes the action logic + +### Optional Properties + +- **`attributes`:** Object containing behavioral flags: + - **`destructive`:** Boolean indicating if the action modifies or deletes data + - **`idempotent`:** Boolean indicating if running the action multiple times produces the same result + - **`readOnly`:** Boolean indicating if the action only reads data without modifications + +### Action Context + +When an action is executed, it receives a context object (`ActionsRegistryActionContext`) containing: + +- **`input`:** The validated input data matching the defined input schema +- **`logger`:** A LoggerService instance for logging within the action +- **`credentials`:** BackstageCredentials for authentication and authorization + +## Using the Service + +### Registering an Action + +Here's an example of how to register an action with the Actions Registry Service: + +```typescript +import { ActionsRegistryService } from '@backstage/backend-plugin-api'; + +export function registerMyActions(actionsRegistry: ActionsRegistryService) { + // Register a simple read-only action + actionsRegistry.register({ + name: 'fetch-user-info', + title: 'Fetch User Information', + description: 'Retrieves user information from the catalog', + schema: { + input: z => + z.object({ + userRef: z.string(), + includeGroups: z.boolean().optional(), + }), + output: z => + z.object({ + user: z.object({ + name: z.string(), + email: z.string(), + groups: z.array(z.string()).optional(), + }), + }), + }, + attributes: { + readOnly: true, + idempotent: true, + }, + action: async ({ input, logger, credentials }) => { + logger.info(`Fetching user info for ${input.userRef}`); + + // Perform the action logic here + const user = await fetchUserFromCatalog(input.userRef, credentials); + + return { + output: { + user: { + name: user.name, + email: user.email, + groups: input.includeGroups ? user.groups : undefined, + }, + }, + }; + }, + }); + + // Register a destructive action + actionsRegistry.register({ + name: 'delete-entity', + title: 'Delete Entity', + description: 'Removes an entity from the catalog', + schema: { + input: z => + z.object({ + entityRef: z.string(), + force: z.boolean().optional(), + }), + output: z => + z.object({ + deletedEntities: z.array(z.string()), + }), + }, + attributes: { + destructive: true, + idempotent: false, + }, + action: async ({ input, logger, credentials }) => { + logger.warn(`Deleting entity ${input.entityRef}`); + + // Perform the deletion logic here + const { deletedEntities } = await deleteEntityFromCatalog( + input.entityRef, + input.force, + credentials, + ); + + return { + output: deletedEntities, + }; + }, + }); +} +``` + +### Accessing the Service in a Plugin + +To use the Actions Registry Service in your plugin, access it through dependency injection: + +```typescript +import { + createBackendPlugin, + coreServices, +} from '@backstage/backend-plugin-api'; +import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha'; + +export const myPlugin = createBackendPlugin({ + pluginId: 'my-plugin', + register(env) { + env.registerInit({ + deps: { + actionsRegistry: actionsRegistryServiceRef, + logger: coreServices.logger, + }, + async init({ actionsRegistry, logger }) { + logger.info('Registering actions...'); + registerMyActions(actionsRegistry); + logger.info('Actions registered successfully'); + }, + }); + }, +}); +``` + +## Best Practices + +### Naming Conventions + +- **Use kebab-case:** Action names should be in kebab-case (e.g., `fetch-user-info`, `create-repository`) +- **Be Descriptive:** Choose names that clearly describe what the action does +- **Avoid Redundancy:** Don't include plugin names in action names since the plugin context is separate +- **Use Verbs:** Start action names with verbs that describe the operation (e.g., `fetch`, `create`, `delete`, `update`) + +## Action Attributes Reference + +| Attribute | Type | Default | Description | +| ------------- | ------- | ------- | ------------------------------------------------------------------- | +| `destructive` | boolean | `true` | Indicates the action modifies or deletes data. Use with caution. | +| `idempotent` | boolean | `false` | Indicates the action can be run multiple times with the same result | +| `readOnly` | boolean | `false` | Indicates the action only reads data without making modifications | + +These attributes help consumers of actions understand their behavior and implement appropriate safeguards, retries, or optimizations based on the action's characteristics. diff --git a/docs/backend-system/core-services/actions.md b/docs/backend-system/core-services/actions.md new file mode 100644 index 0000000000..ddb28b4da7 --- /dev/null +++ b/docs/backend-system/core-services/actions.md @@ -0,0 +1,131 @@ +--- +id: actions +title: Actions (alpha) +sidebar_label: Actions (alpha) +description: Documentation for the Actions Service +--- + +## Overview + +The Actions Service is a core service that provides a standardized interface for discovering and executing registered actions within Backstage backend plugins. This service acts as the consumer-facing API for actions that have been registered through the Actions Registry Service, allowing plugins to list available actions and invoke them with proper authentication and input validation. + +## How it Works + +The Actions Service implements the `ActionsService` interface, which provides two primary methods: + +- **`list()`:** Retrieves all available actions with their complete metadata +- **`invoke()`:** Executes a specific action by ID with provided input data + +The service works in conjunction with the [Actions Registry Service](./actions-registry.md), where actions are registered by plugins and then made available for discovery and execution through this service. + +## Action Identification + +Actions are identified using a unique ID that follows a specific format: + +- All action IDs are prefixed with the plugin ID that registered them, following the pattern `pluginId:actionName` +- An action named `fetch-user-info` registered by the `catalog` plugin would have the ID `catalog:fetch-user-info` +- When using the `actionsRegistryServiceMock`, the plugin ID prefix will be `test:` + +This naming convention ensures that action names are globally unique across all plugins and provides clear ownership identification. + +## Configuration + +The Actions Service can be configured to control which plugins' actions are available: + +```yaml +backend: + actions: + pluginSources: + - catalog +``` + +## Using the Service + +### Listing Available Actions + +Here's an example of how to list all available actions: + +```typescript +import { ActionsService } from '@backstage/backend-plugin-api'; + +export async function listAvailableActions( + actionsService: ActionsService, + credentials: BackstageCredentials, +) { + try { + const { actions } = await actionsService.list({ credentials }); + + console.log(`Found ${actions.length} available actions:`); + + actions.forEach(action => { + console.log(`- ${action.id}: ${action.title}`); + console.log(` Description: ${action.description}`); + console.log(` Attributes: ${JSON.stringify(action.attributes)}`); + + if (action.schema.input) { + console.log( + ` Input Schema: ${JSON.stringify(action.schema.input, null, 2)}`, + ); + } + }); + + return actions; + } catch (error) { + console.error('Failed to list actions:', error); + throw error; + } +} +``` + +### Invoking an Action + +Here's an example of how to execute a specific action: + +```typescript +import { ActionsService } from '@backstage/backend-plugin-api'; + +export async function executeAction( + actionsService: ActionsService, + actionId: string, + input: JsonObject, + credentials: BackstageCredentials, +) { + try { + const { output } = await actionsService.invoke({ + id: actionId, + input, + credentials, + }); + + console.log(`Action ${actionId} executed successfully`); + console.log('Output:', JSON.stringify(output, null, 2)); + + return output; + } catch (error) { + console.error(`Failed to execute action ${actionId}:`, error); + throw error; + } +} + +// Example usage +async function fetchUserInfo( + actionsService: ActionsService, + credentials: BackstageCredentials, +) { + const output = await executeAction( + actionsService, + 'catalog:fetch-user-info', // Note: Action ID includes plugin prefix + { + userRef: 'user:default/john.doe', + includeGroups: true, + }, + credentials, + ); + + return output; +} +``` + +## Best Practices + +For comprehensive guidance on action design, naming conventions, and schema design, see the [Actions Registry Best Practices](./actions-registry.md#best-practices) documentation. diff --git a/docs/backend-system/core-services/http-router.md b/docs/backend-system/core-services/http-router.md index d7658d9a67..5e065152a0 100644 --- a/docs/backend-system/core-services/http-router.md +++ b/docs/backend-system/core-services/http-router.md @@ -68,6 +68,60 @@ For those routes you will also have to specify `allowLimitedAccess: true` when using the [`auth`](./auth.md) and [`httpAuth`](./http-auth.md) services to access the incoming credentials. +## Rate limiting + +Rate limiting allows you to limit the amount of requests users can send to you backend. +This is useful for blocking various network attacks, such as DDOS. + +To enable rate limiting, add the following to your config: + +```yaml +backend: + rateLimit: true +``` + +You can additionally configure the rate limiting parameters, also by plugin: + +```yaml +backend: + rateLimit: + global: true # Enables or disables rate limit for all plugins + window: 6s # Time window for rate limiting for single client + incomingRequestLimit: 100 # Number of requests to accept from one client during time window + ipAllowList: ['127.0.0.1'] # IPs to bypass rate limiting + skipSuccesfulRequests: false # Rate limit successful requests + skipFailedRequests: false # Rate limit failed requests + plugin: + # Plugin specific rate limiting + catalog: + window: 3s + incomingRequestLimit: 50 +``` + +By default, the rate limiting is per instance and the request counts are stored into memory. +If you want to share this information across all your backstage instances, you have to configure +the rate limiting store: + +```yaml +backend: + rateLimit: + global: true + store: + type: redis + connection: redis://127.0.0.1:16379 +``` + +If your instance is working behind a proxy, you have to configure the backend to trust the proxy +for the rate limiting being able to distinguish clients. + +```yaml +backend: + trustProxy: true +``` + +For more information about the trust proxy configuration and available options, +please refer to [express documentation](https://expressjs.com/en/guide/behind-proxies.html). + ## Configuring the service For more advanced customization, there are several APIs from the `@backstage/backend-defaults/httpRouter` package that allow you to customize the implementation of the config service. The default implementation uses all of the middleware exported from `@backstage/backend-defaults/httpRouter`, including `createLifecycleMiddleware`, `createAuthIntegrationRouter`, `createCredentialsBarrier` and `createCookieAuthRefreshMiddleware`. You can use these to create your own `httpRouter` service implementation, for example - here's how you would add a custom health check route to all plugins: @@ -78,6 +132,7 @@ import { createCookieAuthRefreshMiddleware, createCredentialsBarrier, createAuthIntegrationRouter, + createRateLimitMiddleware, } from '@backstage/backend-defaults/httpRouter'; import { createServiceFactory } from '@backstage/backend-plugin-api'; @@ -105,6 +160,11 @@ backend.add( }) { const router = PromiseRouter(); + // Optional rate limiting middleware + router.use( + createRateLimitMiddleware({ pluginId: plugin.getId(), config }), + ); + rootHttpRouter.use(`/api/${plugin.getId()}`, router); const credentialsBarrier = createCredentialsBarrier({ diff --git a/docs/backend-system/core-services/root-health.md b/docs/backend-system/core-services/root-health.md index fa147499a6..ae279d46e8 100644 --- a/docs/backend-system/core-services/root-health.md +++ b/docs/backend-system/core-services/root-health.md @@ -12,7 +12,11 @@ The Root Health service provides some health check endpoints for the backend. By The following example shows how you can override the root health service implementation. ```ts -import { RootHealthService, coreServices } from '@backstage/backend-plugin-api'; +import { + RootHealthService, + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; const backend = createBackend(); diff --git a/docs/backend-system/core-services/root-http-router.md b/docs/backend-system/core-services/root-http-router.md index 7a268e27fc..1e76fb1871 100644 --- a/docs/backend-system/core-services/root-http-router.md +++ b/docs/backend-system/core-services/root-http-router.md @@ -40,6 +40,10 @@ createBackendPlugin({ }); ``` +## Rate limiting + +Please refer to the [HTTP Router documentation](./http-router.md#rate-limiting). + ## Configuring the service ### Via `app-config.yaml` @@ -60,7 +64,7 @@ backend: # - A string in the format of '1d', '2 seconds' etc. as supported by the `ms` library. # - A standard ISO formatted duration string, e.g. 'P2DT6H' or 'PT1M'. # - An object with individual units (in plural) as keys, e.g. `{ days: 2, hours: 6 }`. - serverShutdownTimeout: { seconds: 20 } + serverShutdownDelay: { seconds: 20 } ``` ### Via Code @@ -121,6 +125,11 @@ backend.add( app.use(middleware.cors()); app.use(middleware.compression()); + // Optional rate limiting middleware + app.use(middleware.rateLimit()); + // If you are using rate limiting behind a proxy, you should set the `trust proxy` setting to true + app.set('trust proxy', true); + app.use(healthRouter); // you can add you your own middleware in here @@ -161,3 +170,52 @@ backend.add( }), ); ``` + +## Defining Content Security Policy (CSP) Configuration + +Content Security Policy (CSP) is a crucial security feature that helps protect your Backstage instance from various attacks, particularly Cross-Site Scripting (XSS). Backstage provides a flexible way to configure CSP directives through your `app-config.yaml` file. + +### Basic Configuration + +CSP directives can be defined under the `backend.csp` section in your configuration: + +```yaml +backend: + csp: + default-src: ["'self'"] + script-src: ["'self'", "'unsafe-inline'", 'example.com'] + connect-src: ["'self'", 'http:', 'https:'] + img-src: ["'self'", 'data:', 'https://backstage.io'] + style-src: ["'self'", "'unsafe-inline'"] + frame-src: ['https://some-analytics-provider.com'] +``` + +### Available Directives + +You can configure any CSP directive supported by modern browsers. Common directives include: + +- `default-src`: The fallback for other CSP directives +- `script-src`: Controls which scripts can be executed +- `style-src`: Controls which styles can be applied +- `img-src`: Controls which images can be loaded +- `connect-src`: Controls which URLs can be loaded using fetch, WebSocket, etc. +- `frame-src`: Controls which URLs can be embedded in iframes +- `font-src`: Controls which fonts can be loaded +- `object-src`: Controls which URLs can be loaded as plugins +- `media-src`: Controls which media (audio, video) can be loaded +- `upgrade-insecure-requests`: Instructs browsers to upgrade HTTP to HTTPS + +### Special Values + +- Common special values within directive arrays: + - `self`: Allows content from the same origin + - `unsafe-inline`: Allows inline scripts/styles + - `unsafe-eval`: Allows dynamic code evaluation + - `none`: Blocks all content for that directive + - `data:`: Allows data: URIs (commonly used for images) + - `https:`: Allows any content over HTTPS + +For more information on available CSP options, refer to: + +- [MDN Content Security Policy documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) +- [Helmet Content Security Policy](https://helmetjs.github.io/#content-security-policy) diff --git a/docs/backend-system/index.md b/docs/backend-system/index.md index b8cf5048be..2eb7f158ef 100644 --- a/docs/backend-system/index.md +++ b/docs/backend-system/index.md @@ -2,12 +2,29 @@ id: index title: The Backend System sidebar_label: Introduction -# prettier-ignore -description: The Backend System +description: Introduction to Backstage's backend system and its documentation --- -## Status +## Overview -The new backend system is released and ready for production use, and many plugins and modules have already been migrated. We recommend all plugins and deployments to migrate to the new system. +The Backstage backend system provides a flexible foundation for building and extending Backstage backends. It uses a modular architecture where you can create and customize plugins, modules, and service implementations. It's focused both around building your own features as well as installing third-party plugins and modules available in the Backstage ecosystem. The system is designed to be scalable and maintainable, making it work well for organizations of all sizes. -You can find an example backend setup in [the `backend` package](https://github.com/backstage/backstage/tree/master/packages/backend). +## Documentation Sections + +This documentation is organized into several key areas, each focusing on different aspects of the backend system: + +### [Architecture](./architecture/01-index.md) + +The Architecture section explains the core building blocks and concepts of the Backstage backend system. It starts with backend instances, which serve as the main entry point for creating and wiring together backend features. You'll learn about plugins that provide the base features and operate independently as microservices, modules that extend plugins with additional capabilities through extension points, and services that provide shared functionality across plugins and modules. The section also covers feature loaders that enable programmatic selection and installation of features, as well as important naming patterns and conventions used throughout the backend system. + +### [Building Backends](./building-backends/01-index.md) + +This section covers how to set up and customize your own Backstage backend. You'll learn about the basic structure of a backend package, how to install plugins and modules, and ways to customize your installation through configuration and custom service implementations. The section also explains how to split your backend into multiple deployments for better scalability and security isolation. + +### [Building Plugins and Modules](./building-plugins-and-modules/01-index.md) + +The Building Plugins and Modules section shows you how to create and test backend plugins and modules. It covers creating new plugins using `yarn new`, implementing plugin functionality with services and extension points, and building modules that extend existing plugins. You'll learn how to test your plugins and modules using the test utilities in `@backstage/backend-test-utils`, including mocking services, testing HTTP endpoints, and working with databases. The section also explains how to enable users of your plugin to customize it through static configuration and extension points. + +### [Core Services](./core-services/01-index.md) + +The Core Services section documents the essential services that are available to all backend plugins. These include fundamental services like logging, configuration, and HTTP routing that plugins can depend on. Each service is documented with its interface, implementation details, and usage examples. diff --git a/docs/contribute/getting-involved.md b/docs/contribute/getting-involved.md index 4b8c4dfc6f..696b73b7d4 100644 --- a/docs/contribute/getting-involved.md +++ b/docs/contribute/getting-involved.md @@ -1,7 +1,6 @@ --- id: getting-involved title: Getting Involved -# prettier-ignore description: How can you help us build Backstage? We welcome contributions of all kinds, from documentation to code to design. --- diff --git a/docs/contribute/index.md b/docs/contribute/index.md index 627a2ff0d0..dbdf126b20 100644 --- a/docs/contribute/index.md +++ b/docs/contribute/index.md @@ -1,7 +1,6 @@ --- id: index title: Contributors -# prettier-ignore description: Documentation on how to get set up for doing development on the Backstage repository --- diff --git a/docs/contribute/project-structure.md b/docs/contribute/project-structure.md index 984cfe98d6..5b86b655b2 100644 --- a/docs/contribute/project-structure.md +++ b/docs/contribute/project-structure.md @@ -1,7 +1,6 @@ --- id: project-structure title: Backstage Project Structure -# prettier-ignore description: Introduction to files and folders in the Backstage Project repository --- @@ -157,10 +156,10 @@ are separated out into their own folder, see further down. - [`integration/`](https://github.com/backstage/backstage/tree/master/packages/integration) - Common functionalities of integrations like GitHub, GitLab, etc. -- [`storybook/`](https://github.com/backstage/backstage/tree/master/storybook) - - This folder contains only the Storybook config which helps visualize our - reusable React components. Stories are within the core package, and are - published in the [Backstage Storybook](https://backstage.io/storybook). +- [`.storybook/`](https://github.com/backstage/backstage/tree/master/.storybook) - + This folder contains the Storybook configuration which helps visualize our + reusable React components. Stories are scanned from packages and plugins across + the monorepo, and are published in the [Backstage Storybook](https://backstage.io/storybook). - [`techdocs-node/`](https://github.com/backstage/backstage/tree/master/plugins/techdocs-node) - Common node.js functionalities for TechDocs, to be shared between diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index 8f83acfc51..83f8cff6ea 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -5,30 +5,26 @@ sidebar_label: Docker description: How to build a Backstage Docker image for deployment --- -:::note Note -Before you start this section, it would be good to have a basic understanding of Docker and how it works. If you are new to Docker, you can start with the [Docker overview](https://docs.docker.com/get-started/overview/) guide. +## Summary + +This section describes how to build a Backstage App into a deployable Docker image. It is split into three sections, first covering the host build approach, which is recommended due to its speed and more efficient and often simpler caching. The second section covers a full multi-stage Docker build, and the last section covers how to deploy the frontend and backend as separate images. + +## Prerequisites + +This guide assumes your have a basic understanding of Docker and how it works. If you are new to Docker, you can start with the [Docker overview](https://docs.docker.com/get-started/overview/) guide. + +You'll also want to complete the following prerequisites: + +1. Created an app following the [Getting Started guide](../getting-started/index.md) +2. Setup an auth provider, the [Authentication guide](../getting-started/config/authentication.md) is a good starting point for this, the default [Guest auth provider](../auth/guest/provider.md) is not intended for use in containerized environments +3. A Postgres database setup that you are able to connect to, the [Database guide](../getting-started/config/database.md) can help you with this + +:::warning + +Moving forward without addressing these prerequisites is very likely to cause undesirable results, the most common being not having a proper auth provider setup as the default [Guest auth provider](../auth/guest/provider.md) is not intended for use in containerized environments. + ::: -This section describes how to build a Backstage App into a deployable Docker -image. It is split into three sections, first covering the host build approach, -which is recommended due to its speed and more efficient and often simpler -caching. The second section covers a full multi-stage Docker build, and the last -section covers how to deploy the frontend and backend as separate images. - -Something that goes for all of these docker deployment strategies is that they -are stateless, so for a production deployment you will want to set up and -connect to an external PostgreSQL instance where the backend plugins can store -their state, rather than using SQLite. - -This section assumes that an [app](https://backstage.io/docs/getting-started/) -has already been created with `@backstage/create-app`, in which the frontend is -bundled and served from the backend. This is done using the -`@backstage/plugin-app-backend` plugin, which also injects the frontend -configuration into the app. This means that you only need to build and deploy a -single container in a minimal setup of Backstage. If you wish to separate the -serving of the frontend out from the backend, see the -[separate frontend](#separate-frontend) topic below. - ## Host Build This section describes how to build a Docker image from a Backstage repo with diff --git a/docs/deployment/k8s.md b/docs/deployment/k8s.md index aae49b6069..3be791dc59 100644 --- a/docs/deployment/k8s.md +++ b/docs/deployment/k8s.md @@ -112,7 +112,7 @@ YmFja3N0YWdl Secrets are base64-encoded, but not encrypted. Be sure to enable [Encryption at Rest](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/) for the cluster. For storing secrets in Git, consider -[SealedSecrets or other solutions](https://learnk8s.io/kubernetes-secrets-in-git). +[SealedSecrets or other solutions](https://learnkube.com/kubernetes-secrets-in-git) ::: diff --git a/docs/dls/figma.md b/docs/dls/figma.md index 21a05d2818..738d607439 100644 --- a/docs/dls/figma.md +++ b/docs/dls/figma.md @@ -1,7 +1,6 @@ --- id: figma title: Figma -# prettier-ignore description: Documentation on using Figma to build your own plugins for Backstage --- diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index a22a7d94d9..830027f53b 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -2,7 +2,6 @@ id: configuration title: Configuring Kubernetes integration sidebar_label: Configuration -# prettier-ignore description: Configuring the Kubernetes integration for Backstage expose your entity's objects --- @@ -203,6 +202,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 @@ -677,6 +698,7 @@ rules: - '*' resources: - pods + - pods/log - configmaps - services - deployments diff --git a/docs/features/search/README.md b/docs/features/search/README.md index 93aa22ebe1..eb5d12080c 100644 --- a/docs/features/search/README.md +++ b/docs/features/search/README.md @@ -2,7 +2,6 @@ id: search-overview title: Search Documentation sidebar_label: Overview -# prettier-ignore description: Backstage Search lets you find the right information you are looking for in the Backstage ecosystem. --- 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/search/search-engines.md b/docs/features/search/search-engines.md index b61be1e0c8..1d66a6ae97 100644 --- a/docs/features/search/search-engines.md +++ b/docs/features/search/search-engines.md @@ -267,7 +267,7 @@ Fuzziness allows you to define the maximum Levenshtein distance, AUTO is the def ```yaml search: elasticsearch: - queryConfig: + queryOptions: fuzziness: AUTO prefixLength: 3; ``` diff --git a/docs/features/software-catalog/catalog-customization.md b/docs/features/software-catalog/catalog-customization.md index 1c36301605..1ef865039f 100644 --- a/docs/features/software-catalog/catalog-customization.md +++ b/docs/features/software-catalog/catalog-customization.md @@ -1,7 +1,6 @@ --- id: catalog-customization title: Catalog Customization -# prettier-ignore description: How to add custom filters or interface elements to the Backstage software catalog --- diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index 28ddfae282..776f8cd37f 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -2,7 +2,6 @@ id: descriptor-format title: Descriptor Format of Catalog Entities sidebar_label: YAML File Format -# prettier-ignore description: Documentation on Descriptor Format of Catalog Entities which describes the default data shape and semantics of catalog entities --- @@ -728,7 +727,6 @@ spec: name: Publish action: publish:github input: - allowedHosts: ['github.com'] description: 'This is {{ parameters.name }}' repoUrl: '{{ parameters.repoUrl }}' diff --git a/docs/features/software-catalog/extending-the-model--old.md b/docs/features/software-catalog/extending-the-model--old.md index 668d1d8311..00a6688352 100644 --- a/docs/features/software-catalog/extending-the-model--old.md +++ b/docs/features/software-catalog/extending-the-model--old.md @@ -1,7 +1,6 @@ --- id: extending-the-model--old title: Extending the model -# prettier-ignore description: Documentation on extending the catalog model --- diff --git a/docs/features/software-catalog/extending-the-model.md b/docs/features/software-catalog/extending-the-model.md index d289886a09..5d51a69e6b 100644 --- a/docs/features/software-catalog/extending-the-model.md +++ b/docs/features/software-catalog/extending-the-model.md @@ -1,7 +1,6 @@ --- id: extending-the-model title: Extending the model -# prettier-ignore description: Documentation on extending the catalog model --- diff --git a/docs/features/software-catalog/external-integrations.md b/docs/features/software-catalog/external-integrations.md index 8938fefdf6..0aef4565d9 100644 --- a/docs/features/software-catalog/external-integrations.md +++ b/docs/features/software-catalog/external-integrations.md @@ -1,7 +1,6 @@ --- id: external-integrations title: External integrations -# prettier-ignore description: Documentation on External integrations to integrate systems with Backstage --- diff --git a/docs/features/software-catalog/index.md b/docs/features/software-catalog/index.md index ac1d0ee8cd..1f374385a4 100644 --- a/docs/features/software-catalog/index.md +++ b/docs/features/software-catalog/index.md @@ -2,7 +2,6 @@ id: software-catalog-overview title: Backstage Software Catalog sidebar_label: Overview -# prettier-ignore description: The Backstage Software Catalog --- diff --git a/docs/features/software-catalog/life-of-an-entity.md b/docs/features/software-catalog/life-of-an-entity.md index 1489688720..9bb0604b82 100644 --- a/docs/features/software-catalog/life-of-an-entity.md +++ b/docs/features/software-catalog/life-of-an-entity.md @@ -2,7 +2,6 @@ id: life-of-an-entity title: The Life of an Entity sidebar_label: The Life of an Entity -# prettier-ignore description: The life cycle of entities, from being introduced into the catalog, through processing, to being removed again --- diff --git a/docs/features/software-catalog/well-known-annotations.md b/docs/features/software-catalog/well-known-annotations.md index 974fa80993..cdd93b50ac 100644 --- a/docs/features/software-catalog/well-known-annotations.md +++ b/docs/features/software-catalog/well-known-annotations.md @@ -2,7 +2,6 @@ id: well-known-annotations title: Well-known Annotations on Catalog Entities sidebar_label: Well-known Annotations -# prettier-ignore description: Documentation that lists a number of well known Annotations, that have defined semantics. They can be attached to catalog entities and consumed by plugins as needed. --- @@ -115,6 +114,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/software-catalog/well-known-relations.md b/docs/features/software-catalog/well-known-relations.md index 6d61847a15..740dbe0df2 100644 --- a/docs/features/software-catalog/well-known-relations.md +++ b/docs/features/software-catalog/well-known-relations.md @@ -2,7 +2,6 @@ id: well-known-relations title: Well-known Relations between Catalog Entities sidebar_label: Well-known Relations -# prettier-ignore description: Documentation that lists a number of well known Relations, that have defined semantics. They can be attached to catalog entities and consumed by plugins as needed. --- diff --git a/docs/features/software-catalog/well-known-statuses.md b/docs/features/software-catalog/well-known-statuses.md index d293ae5062..6829b1ccdf 100644 --- a/docs/features/software-catalog/well-known-statuses.md +++ b/docs/features/software-catalog/well-known-statuses.md @@ -2,7 +2,6 @@ id: well-known-statuses title: Well-known Status fields of Catalog Entities sidebar_label: Well-known Statuses -# prettier-ignore description: Lists a number of well known entity statuses, that have defined semantics. They can be attached to catalog entities and consumed by plugins as needed. --- diff --git a/docs/features/software-templates/adding-templates.md b/docs/features/software-templates/adding-templates.md index 674ee8788b..55c99aa694 100644 --- a/docs/features/software-templates/adding-templates.md +++ b/docs/features/software-templates/adding-templates.md @@ -49,7 +49,7 @@ spec: # here's the steps that are executed in series in the scaffolder backend steps: - - id: fetch-base + - id: fetchBase name: Fetch Base action: fetch:template input: @@ -57,7 +57,7 @@ spec: values: name: ${{ parameters.name }} - - id: fetch-docs + - id: fetchDocs name: Fetch Docs action: fetch:plain input: @@ -68,7 +68,6 @@ spec: name: Publish action: publish:github input: - allowedHosts: ['github.com'] description: This is ${{ parameters.name }} repoUrl: ${{ parameters.repoUrl }} defaultBranch: 'main' diff --git a/docs/features/software-templates/authorizing-scaffolder-template-details.md b/docs/features/software-templates/authorizing-scaffolder-template-details.md index d4798b2540..7ff2cd9377 100644 --- a/docs/features/software-templates/authorizing-scaffolder-template-details.md +++ b/docs/features/software-templates/authorizing-scaffolder-template-details.md @@ -73,7 +73,7 @@ class ExamplePermissionPolicy implements PermissionPolicy { isPermission(request.permission, templateParameterReadPermission) || isPermission(request.permission, templateStepReadPermission) ) { - if (user?.info.userEntityRef === 'user:default/spiderman') + if (user?.info.userEntityRef === 'user:default/bob') return createScaffolderTemplateConditionalDecision(request.permission, { not: scaffolderTemplateConditions.hasTag({ tag: 'secret' }), }); @@ -87,7 +87,7 @@ class ExamplePermissionPolicy implements PermissionPolicy { } ``` -In this example, the user `spiderman` is not authorized to read parameters or steps marked with the `secret` tag. +In this example, the user `bob` is not authorized to read parameters or steps marked with the `secret` tag. By combining this feature with restricting the ingestion of templates in the Catalog as recommended in our threat model, you can create a solid system to restrict certain actions. @@ -113,7 +113,7 @@ class ExamplePermissionPolicy implements PermissionPolicy { ): Promise { /* highlight-add-start */ if (isPermission(request.permission, actionExecutePermission)) { - if (user?.info.userEntityRef === 'user:default/spiderman') { + if (user?.info.userEntityRef === 'user:default/alice') { return createScaffolderActionConditionalDecision(request.permission, { not: scaffolderActionConditions.hasActionId({ actionId: 'debug:log', @@ -130,10 +130,10 @@ class ExamplePermissionPolicy implements PermissionPolicy { } ``` -With this permission policy, the user `spiderman` won't be able to execute the `debug:log` action. +With this permission policy, the user `alice` won't be able to execute the `debug:log` action. You can also restrict the input provided to the action by combining multiple rules. -In the example below, `spiderman` won't be able to execute `debug:log` when passing `{ "message": "not-this!" }` as action input: +In the example below, `alice` won't be able to execute `debug:log` when passing `{ "message": "not-this!" }` as action input: ```ts title="packages/backend/src/plugins/permission.ts" /* highlight-add-start */ @@ -151,7 +151,7 @@ class ExamplePermissionPolicy implements PermissionPolicy { ): Promise { /* highlight-add-start */ if (isPermission(request.permission, actionExecutePermission)) { - if (user?.info.userEntityRef === 'user:default/spiderman') { + if (user?.info.userEntityRef === 'user:default/alice') { return createScaffolderActionConditionalDecision(request.permission, { not: { allOf: [ @@ -176,7 +176,9 @@ class ExamplePermissionPolicy implements PermissionPolicy { ### Authorizing scaffolder tasks -The scaffolder plugin also exposes permissions that can restrict access to tasks, task logs, task creation, and task cancellation. This can be useful if you want to control who has access to these areas of the scaffolder. +The scaffolder plugin also exposes permissions that can restrict access to tasks, task logs, task creation, and task cancellation. This can be useful if you want to control who has access to these areas of the scaffolder. You can configure policies to restrict access to tasks to only their creators, while also granting specific users the permission to view all tasks. + +The following is a simple example of how to do this: ```ts title="packages/src/backend/plugins/permissions.ts" /* highlight-add-start */ @@ -185,6 +187,20 @@ import { taskCreatePermission, taskReadPermission, } from '@backstage/plugin-scaffolder-common/alpha'; +import { + AuthorizeResult, + isPermission, + PolicyDecision, +} from '@backstage/plugin-permission-common'; +import { + PermissionPolicy, + PolicyQuery, + PolicyQueryUser, +} from '@backstage/plugin-permission-node'; +import { + createScaffolderTaskConditionalDecision, + scaffolderTaskConditions, +} from '@backstage/plugin-scaffolder-backend/alpha'; /* highlight-add-end */ class ExamplePermissionPolicy implements PermissionPolicy { @@ -193,26 +209,58 @@ class ExamplePermissionPolicy implements PermissionPolicy { user?: PolicyQueryUser, ): Promise { /* highlight-add-start */ - if (isPermission(request.permission, taskCreatePermission)) { - if (user?.info.userEntityRef === 'user:default/spiderman') { + if (isPermission(request.permission, taskReadPermission)) { + // Allow alice to read any task + if (user?.info.userEntityRef === 'user:default/alice') { return { result: AuthorizeResult.ALLOW, }; } + + // Allow users to read tasks they created + return createScaffolderTaskConditionalDecision( + request.permission, + scaffolderTaskConditions.isTaskOwner({ + createdBy: user?.info.userEntityRef ? [user?.info.userEntityRef] : [], + }), + ); + } + + if (isPermission(request.permission, taskCreatePermission)) { + const userArray = ['user:default/bob', 'user:default/alice']; + const allowed = userArray.some( + allowedUser => user?.info.userEntityRef === allowedUser, + ); + if (allowed) { + return { + result: AuthorizeResult.ALLOW, + }; + } + return { + result: AuthorizeResult.DENY, + }; } if (isPermission(request.permission, taskCancelPermission)) { - if (user?.info.userEntityRef === 'user:default/spiderman') { - return { - result: AuthorizeResult.ALLOW, - }; - } - } - if (isPermission(request.permission, taskReadPermission)) { - if (user?.info.userEntityRef === 'user:default/spiderman') { + // Allow bob to cancel only his tasks + if (user?.info.userEntityRef === 'user:default/bob') { + return createScaffolderTaskConditionalDecision( + request.permission, + scaffolderTaskConditions.isTaskOwner({ + createdBy: user?.info.userEntityRef + ? [user?.info.userEntityRef] + : [], + }), + ); + } + // Allow alice to cancel any task + if (user?.info.userEntityRef === 'user:default/alice') { return { result: AuthorizeResult.ALLOW, }; } + return { + result: AuthorizeResult.DENY, + }; } /* highlight-add-end */ @@ -223,13 +271,21 @@ class ExamplePermissionPolicy implements PermissionPolicy { } ``` -In the provided example permission policy, we only grant the `spiderman` user permissions to perform/access the following actions/resources: +In the provided example permission policy, we only grant the user `bob` permissions to perform/access the following actions/resources: -- Read all scaffolder tasks and their associated events/logs. -- Cancel any ongoing scaffolder tasks. +- Read scaffolder tasks and their associated events/logs for tasks created by `bob`. +- Cancel ongoing scaffolder tasks created by `bob`. - Trigger software templates, which effectively creates new scaffolder tasks. -Any other user would be denied access to these actions/resources. +On the other hand, the user `alice` is granted: + +- Read access to all scaffolder tasks and their associated events/logs. +- The ability to cancel any ongoing scaffolder task. +- The ability to trigger software templates. + +All other users are granted permissions to perform/access the following actions/resources: + +- Read scaffolder tasks and their associated events/logs for tasks created by the user. Although the rules exported by the scaffolder are simple, combining them can help you achieve more complex use cases. diff --git a/docs/features/software-templates/builtin-actions.md b/docs/features/software-templates/builtin-actions.md index d732bd8a27..22d8ef6454 100644 --- a/docs/features/software-templates/builtin-actions.md +++ b/docs/features/software-templates/builtin-actions.md @@ -78,7 +78,7 @@ allow most templates built for `fetch:cookiecutter` to work without any changes. ```yaml title="template.yaml" steps: - - id: fetch-base + - id: fetchBase name: Fetch Base # highlight-remove-next-line action: fetch:cookiecutter diff --git a/docs/features/software-templates/experimental.md b/docs/features/software-templates/experimental.md index 929fe088cf..ff40120d8c 100644 --- a/docs/features/software-templates/experimental.md +++ b/docs/features/software-templates/experimental.md @@ -1,7 +1,6 @@ --- id: experimental title: Experimental Features -# prettier-ignore description: Information on Experimental Features that are currently available in the Scaffolder --- @@ -101,7 +100,7 @@ metadata: name: my-template spec: EXPERIMENTAL_formDecorators: - - id: my-decorator + - id: myDecorator input: test: something funky @@ -116,7 +115,7 @@ You can create a decorator using the simple helper method `createScaffolderFormD ```ts export const mockDecorator = createScaffolderFormDecorator({ // give the decorator a name - id: 'mock-decorator', + id: 'mockDecorator', // define the schema for the input that can be provided in `template.yaml` schema: { diff --git a/docs/features/software-templates/index.md b/docs/features/software-templates/index.md index 10550f7c01..58da85c6eb 100644 --- a/docs/features/software-templates/index.md +++ b/docs/features/software-templates/index.md @@ -2,7 +2,6 @@ id: software-templates-index title: Backstage Software Templates sidebar_label: Overview -# prettier-ignore description: The Software Templates part of Backstage is a tool that can help you create Components inside Backstage --- @@ -11,6 +10,16 @@ Components inside Backstage. By default, it has the ability to load skeletons of code, template in some variables, and then publish the template to some locations like GitHub or GitLab. +:::warning Important + +When creating custom scaffolder actions, **use camelCase for action IDs** instead of kebab-case. Action IDs with dashes (like `fetch-component-id`) will cause template expressions like `${{ steps.fetch-component-id.output.componentId }}` to return `NaN` because the dashes are evaluated as subtraction operators in JavaScript expressions. + +:::note + +See the [Writing Custom Actions guide](./writing-custom-actions.md#naming-conventions) and [Template Migration guide](./migrating-from-v1beta2-to-v1beta3.md#watch-out-for-dash-case) for more details. + +::: + ## Prerequisites :::note Note diff --git a/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3.md b/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3.md index bd8d4bdb72..91fc34f978 100644 --- a/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3.md +++ b/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3.md @@ -1,7 +1,6 @@ --- id: migrating-from-v1beta2-to-v1beta3 title: Migrating to v1beta3 templates -# prettier-ignore description: How to migrate your existing templates to beta3 syntax --- @@ -80,7 +79,6 @@ writing nice `yaml` files that just work. spec: steps: input: - allowedHosts: ['github.com'] # highlight-remove-next-line description: 'This is {{ parameters.name }}' # highlight-add-next-line diff --git a/docs/features/software-templates/migrating-to-rjsf-v5.md b/docs/features/software-templates/migrating-to-rjsf-v5.md index 8357d7567b..d35eb166f0 100644 --- a/docs/features/software-templates/migrating-to-rjsf-v5.md +++ b/docs/features/software-templates/migrating-to-rjsf-v5.md @@ -1,7 +1,6 @@ --- id: migrating-to-rjsf-v5 title: 'Migrating to react-jsonschema-form@v5' -# prettier-ignore description: Docs on migrating to `react-jsonschema-form`@v5 and the new designs --- diff --git a/docs/features/software-templates/template-extensions.md b/docs/features/software-templates/templating-extensions.md similarity index 93% rename from docs/features/software-templates/template-extensions.md rename to docs/features/software-templates/templating-extensions.md index a43f590e9f..2010eee665 100644 --- a/docs/features/software-templates/template-extensions.md +++ b/docs/features/software-templates/templating-extensions.md @@ -1,21 +1,21 @@ --- -id: template-extensions -title: Template Extensions -description: Template extensions system +id: templating-extensions +title: Templating Extensions +description: Templating extensions system --- Backstage templating is powered by [Nunjucks][]. The basics: -# Template Filters +# Templating Filters The [filter][] is a critical mechanism for the rendering of Nunjucks templates, providing a means of transforming values in a familiar [piped][] fashion. -Template filters are functions that help you transform data, extract specific -information, and perform various operations in Scaffolder Templates. +Templating filters are functions that help you transform data, extract specific +information, and perform various operations in Scaffolder templates. ## Built-in -Backstage provides out of the box the following set of "built-in" template +Backstage provides out of the box the following set of "built-in" templating filters (to create your own custom filters, look to the section [Custom Filter](#custom-filter) hereafter): ### parseRepoUrl @@ -103,7 +103,7 @@ The `projectSlug` filter generates a project slug from a repository URL. - **Input**: `github.com?repo=backstage&owner=backstage` - **Output**: `backstage/backstage` -# Template Globals +# Templating Globals In addition to its powerful filtering functionality, the Nunjucks engine allows access from the template expression context to specified globally-accessible @@ -112,12 +112,12 @@ plugin, which we shall soon see in action. # Customizing the templating environment -Custom plugins make it possible to install your own template extensions, which +Custom plugins make it possible to install your own templating extensions, which may be any combination of filters, global functions and global values. With the new backend you would use a scaffolder plugin module for this; later we will demonstrate the analogous approach with the old backend. -## Streamlining Template Extension Module Creation with the Backstage CLI +## Streamlining Templating Extension Module Creation with the Backstage CLI The creation of a "template environment customization" module in Backstage can be accelerated using the Backstage CLI. @@ -145,7 +145,7 @@ and the CLI will generate the required files and directory structure. ## Writing your Module Once the CLI has generated the essential structure for your new scaffolder -module, it's time to implement our template extensions. Here we'll demonstrate +module, it's time to implement our templating extensions. Here we'll demonstrate how to create each of the supported extension types. `src/module.ts` is where the magic happens. First we prepare to utilize the @@ -213,7 +213,7 @@ async init({ }, ``` -This demonstrates the bare minimum: a TypeScript `Record` of named template +This demonstrates the bare minimum: a TypeScript `Record` of named templating filter implementations to register. However, by adopting an alternate structure we can document our filter with additional metadata; to utilize this capability we begin by adding a new import: @@ -405,7 +405,7 @@ Then modify: #### Schema Declaring a global function schema is quite like the schema declaration for a -template filter: +templating filter: ```ts createTemplateGlobal({ @@ -415,7 +415,7 @@ template filter: }), ``` -#### Template Global Function Example Documentation +#### Global Function Example Documentation Again, this works in the same way as filter examples: @@ -441,7 +441,7 @@ Again, this works in the same way as filter examples: ### Custom Global Value -Alternatively, your template may need access to a simple JSON value, which can +Alternatively, you may need to provide templates access to a simple JSON value, which can be registered in this manner: ```ts @@ -468,7 +468,7 @@ async init({ templating.addTemplateGlobals([ ..., createTemplateGlobalValue({ - id: 'preferredMetasyntacticVariable', + id: 'preferredMetasyntacticIdentifier', value: 'foo', description: 'This description is as contrived as the global value it documents', @@ -477,15 +477,15 @@ async init({ }, ``` -## Register Template Extensions with the Legacy Backend System +## Register Templating Extensions with the Legacy Backend System -Users of the original Backstage backend can register template extensions by +Users of the original Backstage backend can register templating extensions by specifying options to the scaffolder backend plugin's `createRouter` function (customarily called in `packages/backend/src/plugins/scaffolder.ts`): - `additionalTemplateFilters` - either of: - object mapping filter name to implementation function, or - - array of documented template filters as returned by the + - array of documented templating filters as returned by the utility function `createTemplateFilter` - `additionalTemplateGlobals` - either of: - object mapping global name to value or function, or diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index 9a502d7195..1a64406b04 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -12,7 +12,7 @@ by writing custom actions which can be used alongside our When adding custom actions, the actions array will **replace the built-in actions too**. Meaning, you will no longer be able to use them. -If you want to continue using the builtin actions, include them in the actions +If you want to continue using the builtin actions, include them in the `actions` array when registering your custom actions, as seen below. ::: @@ -52,19 +52,20 @@ its generated unit test. We will replace the existing placeholder code with our import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import fs from 'fs-extra'; -import { z } from 'zod'; +import { type z } from 'zod'; export const createNewFileAction = () => { return createTemplateAction({ id: 'acme:file:create', description: 'Create an Acme file.', schema: { - input: z.object({ - contents: z.string().describe('The contents of the file'), - filename: z - .string() - .describe('The filename of the file that will be created'), - }), + input: { + contents: z => z.string({ description: 'The contents of the file' }), + filename: z => + z.string({ + description: 'The filename of the file that will be created', + }), + }, }, async handler(ctx) { @@ -95,53 +96,11 @@ The `createTemplateAction` takes an object which specifies the following: function using `ctx.output` - `handler` - the actual code which is run as part of the action, with a context -You can also choose to define your custom action using JSON schema instead of `zod`: - -```ts title="With JSON Schema" -import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import { writeFile } from 'fs'; - -export const createNewFileAction = () => { - return createTemplateAction<{ contents: string; filename: string }>({ - id: 'acme:file:create', - description: 'Create an Acme file.', - schema: { - input: { - required: ['contents', 'filename'], - type: 'object', - properties: { - contents: { - type: 'string', - title: 'Contents', - description: 'The contents of the file', - }, - filename: { - type: 'string', - title: 'Filename', - description: 'The filename of the file that will be created', - }, - }, - }, - }, - async handler(ctx) { - const { signal } = ctx; - await writeFile( - resolveSafeChildPath(ctx.workspacePath, ctx.input.filename), - ctx.input.contents, - { signal }, - _ => {}, - ); - }, - }); -}; -``` - ### Naming Conventions Try to keep names consistent for both your own custom actions, and any actions contributed to open source. We've found that a separation of `:` and using a verb as the last part of the name works well. -We follow `provider:entity:verb` or as close to this as possible for our built in actions. For example, +We follow `provider:entity:verb` or as close to this as possible for our built-in actions. For example, `github:actions:create` or `github:repo:create`. Also feel free to use your company name to namespace them if you prefer too, for example `acme:file:create` like above. @@ -151,11 +110,15 @@ and writing of template entity definitions. ### Adding a TemplateExample -A TemplateExample is a predefined structure that can be used to create custom actions in your software templates. It -serves as a blueprint for users to understand how to use a specific action and its fields as well as to ensure -consistency and standardization across different custom actions. +A TemplateExample is a way to document different ways that your custom action can be used. Once added, it will be visible +in your Backstage instance under the [/create/actions](https://demo.backstage.io/create/actions) path. You can have multiple +examples for one action that can demonstrate different combinations of inputs and how to use them. -#### Define a TemplateExample and add to your Custom Action +#### Define TemplateExamples + +Below is a sample TemplateExample that is used for `publish:github`. The source code is available +on [GitHub](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/src/actions/github.examples.ts) +and preview on [demo.backstage.io/create/actions](https://demo.backstage.io/create/actions#publish-github) ```ts title="With JSON Schema" import { TemplateExample } from '@backstage/plugin-scaffolder-node'; @@ -163,15 +126,33 @@ import yaml from 'yaml'; export const examples: TemplateExample[] = [ { - description: 'Template Example for Creating an Acme file', + description: 'Initializes a GitHub repository with a description.', example: yaml.stringify({ steps: [ { - action: 'acme:file:create', - name: 'Create an Acme file.', + id: 'publish', + action: 'publish:github', + name: 'Publish to GitHub', input: { - contents: 'file contents...', - filename: 'ACME.properties', + repoUrl: 'github.com?repo=repo&owner=owner', + description: 'Initialize a git repository', + }, + }, + ], + }), + }, + { + description: + 'Initializes a GitHub repository with public repo visibility, if not set defaults to private', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:github', + name: 'Publish to GitHub', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'public', }, }, ], @@ -180,24 +161,28 @@ export const examples: TemplateExample[] = [ ]; ``` -Add the example to `createTemplateAction` by including the `examples` property: +#### Register TemplateExample with your custom action + +It is also crucial +to [register](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/src/actions/github.ts#L126) +the `TemplateExample` when calling `createTemplateAction` by including the `examples` +property. ```ts return createTemplateAction({ - id: 'acme:file:create', - description: 'Create an Acme file', - schema: { - input: { - contents: d => d.string().describe('The contents of the file'), - filename: d => - d.string().describe('The filename of the file that will be created'), - }, - }, + id: 'publish:github', + description: + 'Initializes a git repository of contents in workspace and publishes it to GitHub.', examples, // ...rest of the action configuration }); ``` +#### Test TemplateAction examples + +It is also possible to test your example TemplateActions. You can see a sample test +on [GitHub](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts) + ### The context object When the action `handler` is called, we provide you a `context` as the only @@ -208,13 +193,12 @@ argument. It looks like the following: implement [idempotency of the actions](https://github.com/backstage/backstage/tree/master/beps/0004-scaffolder-task-idempotency) by not re-running the same function again if it was executed successfully on the previous run. -- `ctx.logger` - a Winston logger for additional logging inside your action -- `ctx.logStream` - a stream version of the logger if needed +- `ctx.logger` - a [LoggerService](../../backend-system/core-services/logger.md) instance for additional logging inside your action - `ctx.workspacePath` - a string of the working directory of the template run - `ctx.input` - an object which should match the `zod` or JSON schema provided in the `schema.input` part of the action definition - `ctx.output` - a function which you can call to set outputs that match the - JSON schema or `zod` in `schema.output` for ex. `ctx.output('downloadUrl', myDownloadUrl)` + `zod` schema in `schema.output` for ex. `ctx.output('downloadUrl', myDownloadUrl)` - `createTemporaryDirectory` a function to call to give you a temporary directory somewhere on the runner, so you can store some files there rather than polluting the `workspacePath` @@ -223,7 +207,7 @@ argument. It looks like the following: ## Registering Custom Actions -To register your new custom action in the Backend System you will need to create a backend module. Here is a very +To register your new custom action in the Backend System, you will need to create a backend module. Here is a very simplified example of how to do that: ```ts title="packages/backend/src/index.ts" @@ -301,8 +285,8 @@ const res = await ctx.checkpoint?.({ }); ``` -You have to define the unique key in scope of the scaffolder task for your checkpoint. During the execution task engine -will check if the checkpoint with such key was already executed or not, if yes, and the run was successful, the callback +You have to define the unique key in the scope of the scaffolder task for your checkpoint. During the execution task engine +will check if the checkpoint with such a key was already executed or not, if yes, and the run was successful, the callback will be skipped and instead the stored value will be returned. Whenever you change the return type of the checkpoint, we encourage you to change the ID. diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index f02b2722f6..32f17f945f 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -59,7 +59,7 @@ spec: # here's the steps that are executed in series in the scaffolder backend steps: - - id: fetch-base + - id: fetchBase name: Fetch Base action: fetch:template input: @@ -68,7 +68,7 @@ spec: name: ${{ parameters.name }} owner: ${{ parameters.owner }} - - id: fetch-docs + - id: fetchDocs name: Fetch Docs action: fetch:plain input: @@ -79,7 +79,6 @@ spec: name: Publish action: publish:github input: - allowedHosts: ['github.com'] description: This is ${{ parameters.name }} repoUrl: ${{ parameters.repoUrl }} defaultBranch: 'main' @@ -88,7 +87,7 @@ spec: name: Register action: catalog:register input: - repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} + repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }} catalogInfoPath: '/catalog-info.yaml' # some outputs which are saved along with the job for use in the frontend @@ -274,6 +273,58 @@ spec: password: ${{ secrets.password }} ``` +You can also consume secrets within `each` step of the template. + +```yaml +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: v1beta3-demo + title: Test Action template + description: scaffolder v1beta3 template demo +spec: + owner: backstage/techdocs-core + type: service + + parameters: + - title: Authentication + description: Provide authentication for the resource + required: + - service1 + - token1 + - service2 + - token2 + properties: + service1: + type: string + token1: + type: string + ui:field: Secret + service2: + type: string + token2: + type: string + ui:field: Secret + + steps: + - id: setupAuthentication + action: auth:create + each: + [ + { + name: '${{ parameters.service1 }}', + token: '${{ secrets.token1 }}', + }, + { + name: '${{ parameters.service2 }}', + token: '${{ secrets.token2 }}', + }, + ] + input: + name: ${{ each.value.name }} + token: ${{ each.value.token }} +``` + ### Custom step layouts If you find that the default layout of the form used in a particular step does not meet your needs then you can supply your own [custom step layout](./writing-custom-step-layouts.md). @@ -315,7 +366,7 @@ your first set of parameter fields would be shown. The same goes for the nested spec. Make sure to use the key `backstage:featureFlag` in your templates if you want to use this functionality. -Feature Flags cannot be used in `spec.steps[].if`(the conditional on whether to execute an step/action). But you can use feature flags to display parameters that allow for skipping steps. +Feature Flags cannot be used in `spec.steps[].if`(the conditional on whether to execute a step/action). But you can use feature flags to display parameters that allow for skipping steps. ```yaml spec: @@ -453,7 +504,6 @@ spec: name: Publish action: publish:github input: - allowedHosts: ['github.com'] description: This is ${{ parameters.name }} repoUrl: ${{ parameters.repoUrl }} # here's where the secret can be used @@ -572,7 +622,7 @@ The `steps` is an array of the things that you want to happen part of this template. These follow the same standard format: ```yaml -- id: fetch-base # A unique id for the step +- id: fetchBase # A unique id for the step name: Fetch Base # A title displayed in the frontend if: ${{ parameters.name }} # Optional condition, skip the step if not truthy each: ${{ parameters.iterable }} # Optional iterable, run the same step multiple times @@ -583,6 +633,14 @@ template. These follow the same standard format: name: ${{ parameters.name }} ``` +:::warning Action ID Naming + +When using custom actions, **use camelCase for action IDs** to avoid issues with template expressions. Action IDs with dashes will cause expressions like `${{ steps.my-action.output.value }}` to return `NaN` instead of the expected value. + +Use `myAction` instead of `my-action`, or access outputs with bracket notation: `${{ steps['my-action'].output.value }}`. + +::: + By default we ship some [built in actions](./builtin-actions.md) that you can take a look at, or you can [create your own custom actions](./writing-custom-actions.md). @@ -674,7 +732,7 @@ spec: default: false ... steps: - - id: fetch-base + - id: fetchBase name: Fetch Base action: fetch:template input: @@ -711,9 +769,9 @@ powerful [Nunjucks templating engine](https://mozilla.github.io/nunjucks/). To learn more about basic Nunjucks templating please see [templating documentation](https://mozilla.github.io/nunjucks/templating.html). -Information about Backstage's built-in Nunjucks extensions, as well as how to +Information about Backstage's built-in templating extensions, as well as how to create your own customizations, may be found at -[Template Extensions](./template-extensions.md). +[Templating Extensions](./templating-extensions.md). ## Template Editor @@ -749,7 +807,7 @@ Have in mind that changes in this form will not be saved on the template and is ### Custom Field Explorer -The custom filed explorer allows you to select any custom field loaded on the backstage instance and test different values and configurations. +The custom field explorer allows you to select any custom field loaded on the backstage instance and test different values and configurations. ## Presentation diff --git a/docs/features/techdocs/README.md b/docs/features/techdocs/README.md index beec6d1fad..0306a9746b 100644 --- a/docs/features/techdocs/README.md +++ b/docs/features/techdocs/README.md @@ -2,7 +2,6 @@ id: techdocs-overview title: TechDocs Documentation sidebar_label: Overview -# prettier-ignore description: TechDocs is Spotify’s homegrown docs-like-code solution built directly into Backstage --- diff --git a/docs/features/techdocs/cli.md b/docs/features/techdocs/cli.md index 517b2a3547..a52730d335 100644 --- a/docs/features/techdocs/cli.md +++ b/docs/features/techdocs/cli.md @@ -1,7 +1,6 @@ --- id: cli title: TechDocs CLI -# prettier-ignore description: TechDocs CLI - a utility command line interface for managing TechDocs sites in Backstage. --- diff --git a/docs/features/techdocs/concepts.md b/docs/features/techdocs/concepts.md index 2944197dfb..f427f0cd2b 100644 --- a/docs/features/techdocs/concepts.md +++ b/docs/features/techdocs/concepts.md @@ -1,7 +1,6 @@ --- id: concepts title: Concepts -# prettier-ignore description: Documentation on concepts that are introduced with Spotify's docs-like-code solution in Backstage --- diff --git a/docs/features/techdocs/configuration.md b/docs/features/techdocs/configuration.md index 05f184e90f..e21e452b7c 100644 --- a/docs/features/techdocs/configuration.md +++ b/docs/features/techdocs/configuration.md @@ -1,7 +1,6 @@ --- id: configuration title: TechDocs Configuration Options -# prettier-ignore description: Reference documentation for configuring TechDocs using app-config.yaml --- diff --git a/docs/features/techdocs/configuring-ci-cd.md b/docs/features/techdocs/configuring-ci-cd.md index 20a73798a4..d818c129a4 100644 --- a/docs/features/techdocs/configuring-ci-cd.md +++ b/docs/features/techdocs/configuring-ci-cd.md @@ -1,7 +1,6 @@ --- id: configuring-ci-cd title: Configuring CI/CD to generate and publish TechDocs sites -# prettier-ignore description: Configuring CI/CD to generate and publish TechDocs sites to cloud storage --- diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 8a95c86533..755d88b163 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -606,6 +606,25 @@ techdocs: This way, custom element like `` will be allowed in the result HTML. +## How to allow additional URI protocols in TechDocs + +TechDocs uses the [DOMPurify](https://github.com/cure53/DOMPurify) library to +sanitize HTML and prevent XSS attacks. + +It's possible to allow additional URI protocols based on a list of protocols. To do +this, add the allowed protocols in the `techdocs.sanitizer.additionalAllowedURIProtocols` +and `additionalAllowedURIProtocols` configuration of your `app-config.yaml`. + +For example: + +```yaml +techdocs: + sanitizer: + additionalAllowedURIProtocols: ["vscode"], +``` + +This way, links like `
    VSCode Settings` will be allowed in the result HTML + ## How to render PlantUML diagram in TechDocs PlantUML allows you to create diagrams from plain text language. Each diagram description begins with the keyword - (@startXYZ and @endXYZ, depending on the kind of diagram). For UML Diagrams, Keywords @startuml & @enduml should be used. Further details for all types of diagrams can be found at [PlantUML Language Reference Guide](https://plantuml.com/guide). @@ -942,6 +961,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/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index bdafb3532c..42024ffbb2 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -355,31 +355,53 @@ techdocs: **3a. (Recommended) Authentication using environment variable** -If you do not prefer (3a) and optionally like to use a service account, you can -set the config `techdocs.publisher.azureBlobStorage.credentials.accountName` in -your `app-config.yaml` to the your account name. +The Azure Blob Storage client in Backstage supports all credential types +provided by +[DefaultAzureCredential](https://azuresdkdocs.z19.web.core.windows.net/javascript/azure-identity/4.10.1/classes/DefaultAzureCredential.html). +This means you can authenticate using environment variables for a service +principal, managed identity, and other methods in the default credential +chain. -The storage blob client will automatically use the environment variable -`AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET` to authenticate with -Azure Blob Storage. -[Steps to create the service where the variables can be retrieved from](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal). +For deployment on Kubernetes, you can use +[Azure Workload Identity Federation](https://learn.microsoft.com/en-us/azure/active-directory/workload-identities/workload-identity-federation-overview) +to grant your Backstage workload access to the storage account without +managing secrets. +If running in Azure Virtual Machines or Azure Kubernetes Service (AKS) with managed +identity, no additional configuration apart from the `accountName` and +`containerName` may be needed. +For other scenarios, you can use a +[service principal](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal) +by setting: -https://docs.microsoft.com/en-us/azure/storage/common/storage-auth-aad for more -details. +- `AZURE_CLIENT_ID` +- `AZURE_TENANT_ID` +- `AZURE_CLIENT_SECRET` + +Example configuration in `app-config.yaml`: ```yaml techdocs: publisher: type: 'azureBlobStorage' azureBlobStorage: - containerName: 'name-of-techdocs-storage-bucket' + containerName: 'name-of-techdocs-storage-container' credentials: accountName: ${TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_NAME} ``` +> **Note:** The account or credentials used must have the +> `Storage Blob Data Owner` role on the container to read, write, and +> delete objects as needed. If you use an external publisher, the +> `Storage Blob Data Reader` role is sufficient. + +For more details, see the +[Azure Identity documentation](https://azuresdkdocs.z19.web.core.windows.net/javascript/azure-identity/4.10.1/classes/DefaultAzureCredential.html) +and +[Workload Identity Federation for Kubernetes](https://learn.microsoft.com/en-us/azure/active-directory/workload-identities/workload-identity-federation-overview). + **3b. Authentication using app-config.yaml** -If you do not prefer (3a) and optionally like to use a service account, you can +If you do not prefer (3a) and optionally like to use key-based access, you can follow these steps. To get credentials, access the Azure Portal and go to "Settings > Access Keys", @@ -400,7 +422,9 @@ techdocs: In either case, the account or credentials used to access your container and all TechDocs objects underneath it should have the `Storage Blog Data Owner` role -applied, in order to read, write, and delete objects as needed. +applied, in order to read, write, and delete objects as needed, unless you use +an external publisher, in this case the `Storage Blob Data Reader` role is +sufficient. **4. That's it!** diff --git a/docs/frontend-system/architecture/00-index.md b/docs/frontend-system/architecture/00-index.md index 20a12ba1e8..8307877f16 100644 --- a/docs/frontend-system/architecture/00-index.md +++ b/docs/frontend-system/architecture/00-index.md @@ -2,7 +2,6 @@ id: index title: Frontend System Architecture sidebar_label: Overview -# prettier-ignore description: The structure and architecture of the new Frontend System --- diff --git a/docs/frontend-system/architecture/10-app.md b/docs/frontend-system/architecture/10-app.md index d1c3820f09..d1ab51d5be 100644 --- a/docs/frontend-system/architecture/10-app.md +++ b/docs/frontend-system/architecture/10-app.md @@ -2,7 +2,6 @@ id: app title: App Instances sidebar_label: App -# prettier-ignore description: App instances --- @@ -30,7 +29,7 @@ const root = app.createRoot(); // Just like any other React we need a root element. No server side rendering is used. const rootEl = document.getElementById('root')!; -ReactDOM.createRoot(rootEl).render(app); +ReactDOM.createRoot(rootEl).render(root); ``` We call `createApp` to create a new app instance, which is responsible for wiring together all of the features that we provide to the app. It also provides a set of built-in [Extensions](./20-extensions.md) that help build out the foundations of the app, as well as defaults for many other systems such as [Utility API](./33-utility-apis.md) implementations, components, icons, themes, and how to load configuration. No real work is done at the point of creating the app though, it's all deferred to the rendering of the element returned from `app.createRoot()`. @@ -49,31 +48,123 @@ App feature discovery lets you automatically discover and install features provi Because feature discovery needs to interact with the compilation process, it is only available when using the `@backstage/cli` to build your app. It is hooked into the WebPack compilation process by scanning your app package for compatible dependencies, which are then made part of the app compilation bundle. -Since the `@backstage/cli` is a more stable component than the new frontend system, feature discovery is currently marked as an experimental feature of the CLI and needs to be enabled manually. To enable it, add the following configuration to your `app-config.yaml`: +To enable frontend feature discovery, add the following configuration to your `app-config.yaml`: ```yaml app: - experimental: - packages: all + packages: all ``` This will cause all dependencies in your app package to be installed automatically. If this is not desired, you can use include or exclude filters to narrow down the set of packages: ```yaml app: - experimental: - packages: - # Only the following packages will be included - include: - - '@backstage/plugin-catalog' - - '@backstage/plugin-scaffolder' + packages: + # Only the following packages will be included + include: + - '@backstage/plugin-catalog' + - '@backstage/plugin-scaffolder' --- app: - experimental: - packages: - # All but the following package will be included - exclude: - - '@backstage/plugin-catalog' + packages: + # All but the following package will be included + exclude: + - '@backstage/plugin-catalog' ``` 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..d88fbfb596 100644 --- a/docs/frontend-system/architecture/15-plugins.md +++ b/docs/frontend-system/architecture/15-plugins.md @@ -2,7 +2,6 @@ id: plugins title: Frontend Plugins sidebar_label: Plugins -# prettier-ignore description: Frontend plugins --- @@ -22,7 +21,7 @@ Frontend plugin instances are created with the `createFrontendPlugin` function, // This creates a new extension, see "Extension Blueprints" documentation for more details const myPage = PageBlueprint.make({ params: { - defaultPath: '/my-page', + path: '/my-page', loader: () => import('./MyPage').then(m => ), }, }); @@ -53,6 +52,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. @@ -79,7 +107,7 @@ export default plugin.withOverrides({ // Override the catalog index page with a completely custom implementation PageBlueprint.make({ params: { - defaultPath: '/catalog', + path: '/catalog', routeRef: plugin.routes.catalogIndex, loader: () => import('./CustomCatalogIndexPage').then(m => ), }, diff --git a/docs/frontend-system/architecture/20-extensions.md b/docs/frontend-system/architecture/20-extensions.md index eae0405bda..590d0a7bb9 100644 --- a/docs/frontend-system/architecture/20-extensions.md +++ b/docs/frontend-system/architecture/20-extensions.md @@ -2,7 +2,6 @@ id: extensions title: Frontend Extensions sidebar_label: Extensions -# prettier-ignore description: Frontend extensions --- diff --git a/docs/frontend-system/architecture/23-extension-blueprints.md b/docs/frontend-system/architecture/23-extension-blueprints.md index fb10749845..55cbf45606 100644 --- a/docs/frontend-system/architecture/23-extension-blueprints.md +++ b/docs/frontend-system/architecture/23-extension-blueprints.md @@ -2,7 +2,6 @@ id: extension-blueprints title: Frontend Extension Blueprints sidebar_label: Extensions Blueprints -# prettier-ignore description: Frontend extensions --- @@ -19,7 +18,7 @@ The following is a simple example of how one might use the blueprint `make` meth ```tsx const myPageExtension = PageBlueprint.make({ params: { - defaultPath: '/my-page', + path: '/my-page', loader: () => import('./components/MyPage').then(m => ), }, }); @@ -27,7 +26,7 @@ const myPageExtension = PageBlueprint.make({ The returned `myPageExtension` is an extension which is ready to be used in a plugin. It is the same type of object as is returned by the lower level `createExtension` function. -## Creating an extension from a blueprint with overrides +### Creating an extension from a blueprint with overrides Every extension blueprint also provides a `makeWithOverrides` method. It is useful in cases where you want to provide additional integration points for an extension created with a blueprint. You might for example want to define additional inputs or configuration schema, or use the existing configuration to dynamically compute the parameters passed to the blueprint. @@ -35,22 +34,34 @@ The following is an example of how one might use the blueprint `makeWithOverride ```tsx const myPageExtension = PageBlueprint.makeWithOverrides({ + // This defines additional configuration options for the extension. config: { schema: { layout: z => z.enum(['grid', 'rows']).default('grid'), }, }, - // The original blueprint factory is provided as the first argument - factory(originalFactory, { config }) { + // This defines additional inputs for the extension. + inputs: { + content: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + optional: true, + }), + }, + // The original blueprint factory is provided as the first argument. + // By convention the name is `originalFactory`, but you can also pick a different name. + factory(originalFactory, { config, inputs }) { // Call and forward the result from the original factory, providing // the blueprint parameters as the first argument. return originalFactory({ - defaultPath: '/my-page', + path: '/my-page', loader: () => import('./components/MyPage').then(m => ( // We can now access values from the factory context when providing - // the blueprint parameters, such as config values. - + // the blueprint parameters, such as config values and inputs. + )), }); }, @@ -59,7 +70,40 @@ const myPageExtension = PageBlueprint.makeWithOverrides({ When using `makeWithOverrides`, we no longer pass the blueprint parameters directly. Instead, we provide a `factory` function that receives the original blueprint factory as the first argument, and the extension factory context as the second. We can then call the original blueprint factory with the blueprint parameters and forward the result as the return value of out factory. Notice that when passing the blueprint parameters using this pattern we have access to a lot more information than when using the `make` method, at the cost of being more complex. -Apart from the addition of the blueprint parameters of the first argument to the original factory function, the `makeWithOverrides` method works the same way as [extension overrides](./25-extension-overrides.md). All the same options and rules apply, including the ability to define additional inputs, override outputs, and so on. We therefore defer to the [extension overrides](./25-extension-overrides.md) documentation for more information on how to use the `makeWithOverrides` method. +Apart from the addition of the blueprint parameters of the first argument to the original factory function, the `makeWithOverrides` method works the same way as [extension overrides](./25-extension-overrides.md). All the same options and rules apply, including the ability to define additional inputs, override outputs, and so on. For more details and examples on how this works, please refer to the [extension overrides](./25-extension-overrides.md) documentation. The patterns in that section also apply to the creation of extensions with the `makeWithOverrides` method. + +### Creating an extension from a blueprint with advanced parameter types + +Some blueprints may be defined with something known as "advanced parameter types". This is a feature that enables type inference and transform of the blueprint parameters, and the way that you pass the parameters look a little bit different. Rather than passing the parameters directly, they are instead passed as a callback function of the form `defineParams => defineParams()`. + +An example of a blueprint that uses advanced parameter types is the `ApiBlueprint` blueprint. Using it to create a simple implementation for the `AlertApi` might look like this: + +```ts +const alertApiBlueprint = ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: alertApiRef, + deps: {}, + factory: () => new MyAlertApi(), + }), +}); +``` + +This also works with `makeWithOverrides`, where the define callback is passed as the first argument to the original factory: + +```ts +const alertApiBlueprint = ApiBlueprint.makeWithOverrides({ + factory(originalFactory, { config }) { + return originalFactory(defineParams => + defineParams({ + api: alertApiRef, + deps: {}, + factory: () => new MyAlertApi(config), + }), + ); + }, +}); +``` ## Creating an extension blueprint @@ -69,7 +113,7 @@ The following is an example of how one might create a new extension blueprint: ```tsx export interface MyWidgetBlueprintParams { - defaultTitle: string; + title: string; element: JSX.Element; } @@ -87,7 +131,7 @@ export const MyWidgetBlueprint = createExtensionBlueprint({ // Note that while this is a valid pattern, you might often want to // return separate pieces of data instead, more on that below. coreExtensionData.reactElement( - + {params.element} , ), @@ -100,6 +144,41 @@ This is of course a quite bare-bones example blueprint, but still a very real ex Most of the options provided to `createExtensionBlueprint` can be overridden when using `makeWithOverrides` to create an extension from the blueprint. These overrides work the same way as [extension overrides](./25-extension-overrides.md), and we defer to that documentation for more information on how overrides work. +### Creating an extension blueprint with advanced parameter types + +In some cases you may want to use inferred type parameters in the definition of the blueprint parameters. For this you need to use something known as "advanced parameter types". This is a feature that enables type inference and transform of the blueprint parameters, and the way you define the parameter type is a bit different. Rather than defining the type of the parameters as part of the factory function, you instead provide a separate `defineParams` options. This is a function that takes the parameters as a single argument, and must then return the parameters wrapped with the `createExtensionBlueprintParams` function. + +The following is an example of how one might define a blueprint where the parameters make use of inferred types: + +```ts +export interface MyWidgetBlueprintParams { + defaultOptions: T; + elementFactory(options: T): JSX.Element; +} + +export const MyWidgetBlueprint = createExtensionBlueprint({ + kind: 'my-widget', + attachTo: { id: 'page:my-plugin', input: 'widgets' }, + output: [coreExtensionData.reactElement], + defineParams(params: MyWidgetBlueprintParams) { + return createExtensionBlueprintParams(params); + }, + // Note that we no longer define the parameters type here, they are inferred from the defineParams function + factory(params) { + return [ + coreExtensionData.reactElement( + , + ), + ]; + }, +}); +``` + +If you happen to ask yourself, "why can't I just define type parameters on the factory function instead?", this is a limitation in the TypeScript type system. We could technically support that in the blueprint definition, but there would be no way for that logic to be carried forward to the blueprint `.make` and `.makeWithOverrides` methods. + ### Blueprint-specific extension data references In some cases you may want to define and provide [extension data reference](./20-extensions.md#extension-data-references) that are specific to your blueprint. In the above example we might want to forward the `title` as data for example, rather than encapsulating it into the `MyWidgetContainer` component. This gives the parent extension more flexibility in the rendering for our example widget extensions. @@ -108,7 +187,7 @@ To do that, we create a new extension data reference for our widget title. This ```tsx export interface MyWidgetBlueprintParams { - defaultTitle: string; + title: string; element: JSX.Element; } @@ -127,7 +206,7 @@ export const MyWidgetBlueprint = createExtensionBlueprint({ output: [widgetTitleRef, coreExtensionData.reactElement], factory(params: MyWidgetBlueprintParams, { config }) { return [ - widgetTitleRef(config.title ?? params.defaultTitle), + widgetTitleRef(config.title ?? params.title), coreExtensionData.reactElement(params.element), ]; }, diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md index faaf446f41..b2584ab689 100644 --- a/docs/frontend-system/architecture/25-extension-overrides.md +++ b/docs/frontend-system/architecture/25-extension-overrides.md @@ -2,7 +2,6 @@ id: extension-overrides title: Frontend Extension Overrides sidebar_label: Extension Overrides -# prettier-ignore description: Frontend extension overrides --- @@ -90,7 +89,7 @@ const exampleExtension = PageBlueprint.make({ params: { loader: () => import('./components/ExamplePage').then(m => ), - defaultPath: '/example', + path: '/example', }, }); ``` @@ -319,7 +318,7 @@ import { const customSearchPage = PageBlueprint.make({ params: { - defaultPath: '/search', + path: '/search', loader: () => import('./CustomSearchPage').then(m => ), }, diff --git a/docs/frontend-system/architecture/30-references.md b/docs/frontend-system/architecture/30-references.md index 24a9e5df2a..618f2868bb 100644 --- a/docs/frontend-system/architecture/30-references.md +++ b/docs/frontend-system/architecture/30-references.md @@ -2,7 +2,6 @@ id: references title: Value References sidebar_label: Value References -# prettier-ignore description: Value References --- diff --git a/docs/frontend-system/architecture/33-utility-apis.md b/docs/frontend-system/architecture/33-utility-apis.md index fd069319de..ce67956281 100644 --- a/docs/frontend-system/architecture/33-utility-apis.md +++ b/docs/frontend-system/architecture/33-utility-apis.md @@ -2,7 +2,6 @@ id: utility-apis title: Utility APIs sidebar_label: Utility APIs -# prettier-ignore description: Utility APIs --- diff --git a/docs/frontend-system/architecture/36-routes.md b/docs/frontend-system/architecture/36-routes.md index 7b6376b2eb..9ba03285a4 100644 --- a/docs/frontend-system/architecture/36-routes.md +++ b/docs/frontend-system/architecture/36-routes.md @@ -2,7 +2,6 @@ id: routes title: Frontend Routes sidebar_label: Routes -# prettier-ignore description: Frontend routes --- @@ -48,7 +47,7 @@ import { indexRouteRef } from './routes'; const catalogIndexPage = createPageExtension({ // The `name` option is omitted because this is an index page - defaultPath: '/entities', + path: '/entities', // highlight-next-line routeRef: indexRouteRef, loader: () => import('./components').then(m => ), @@ -198,7 +197,7 @@ import { import { indexRouteRef, createComponentExternalRouteRef } from './routes'; const catalogIndexPage = createPageExtension({ - defaultPath: '/entities', + path: '/entities', routeRef: indexRouteRef, loader: () => import('./components').then(m => ), }); @@ -405,7 +404,7 @@ import { import { indexRouteRef, detailsSubRouteRef } from './routes'; const catalogIndexPage = createPageExtension({ - defaultPath: '/entities', + path: '/entities', routeRef: indexRouteRef, loader: () => import('./components').then(m => ), }); @@ -420,3 +419,42 @@ export default createFrontendPlugin({ extensions: [catalogIndexPage], }); ``` + +## Route Aliases - Overriding Routed Extensions in Modules + +It is possible to [override extensions of a plugin using a module](./25-extension-overrides.md#creating-a-frontend-module). In some cases the extension you're overriding may require a route reference. You could import import the plugin instance and access the it via the `routes` property, but this creates a direct dependency on the plugin and risks leading to package duplication issues that would also break the route reference. + +Instead of accessing the route reference directly, you can create a new route reference that acts as an alias for the original one from the plugin. For example, you can override the catalog index page with a custom one like this: + +```tsx +const indexRouteRef = createRouteRef({ aliasFor: 'catalog.catalogIndex' }); + +export default createFrontendModule({ + pluginId: 'catalog', + extensions: [ + PageBlueprint.make({ + params: { + defaultPath: '/catalog', + routeRef: indexRouteRef, + loader: () => + import('./CustomCatalogIndexPage').then(m => ( + + )), + }, + }), + ], +}); +``` + +Aliases are limited to the plugin that they are defined in. These aliases can also be imported and used as usual with for example `useRouteRef`, but they must always be registered in the app via an extension for this to work. For example, the following will not work: + +```tsx +function MyInvalidComponent() { + // This is NOT valid + const link = useRouteRef( + createRouteRef({ aliasFor: 'catalog.catalogIndex' }), + ); + + // ... +} +``` diff --git a/docs/frontend-system/architecture/50-naming-patterns.md b/docs/frontend-system/architecture/50-naming-patterns.md index 55068d8ea1..14634b4355 100644 --- a/docs/frontend-system/architecture/50-naming-patterns.md +++ b/docs/frontend-system/architecture/50-naming-patterns.md @@ -2,7 +2,6 @@ id: naming-patterns title: Frontend System Naming Patterns sidebar_label: Naming Patterns -# prettier-ignore description: Naming patterns in the frontend system --- diff --git a/docs/frontend-system/architecture/60-migrations.md b/docs/frontend-system/architecture/60-migrations.md index 3a33e663f4..413b3b07f4 100644 --- a/docs/frontend-system/architecture/60-migrations.md +++ b/docs/frontend-system/architecture/60-migrations.md @@ -2,7 +2,6 @@ id: migrations title: Frontend System Changelog sidebar_label: Changelog -# prettier-ignore description: Changelog documentation for different versions of the frontend system core APIs. --- diff --git a/docs/frontend-system/building-apps/01-index.md b/docs/frontend-system/building-apps/01-index.md index 5b5298a2c5..0a65ddf122 100644 --- a/docs/frontend-system/building-apps/01-index.md +++ b/docs/frontend-system/building-apps/01-index.md @@ -2,7 +2,6 @@ id: index title: Building Frontend Apps sidebar_label: Overview -# prettier-ignore description: Building frontend apps using the new frontend system --- diff --git a/docs/frontend-system/building-apps/02-configuring-extensions.md b/docs/frontend-system/building-apps/02-configuring-extensions.md index 4052ef47f0..11da9810a4 100644 --- a/docs/frontend-system/building-apps/02-configuring-extensions.md +++ b/docs/frontend-system/building-apps/02-configuring-extensions.md @@ -2,7 +2,6 @@ id: configuring-extensions title: Configuring Extensions in the App sidebar_label: Configuring Extensions -# prettier-ignore description: Documentation for how to configure extensions in a Backstage app --- diff --git a/docs/frontend-system/building-apps/03-built-in-extensions.md b/docs/frontend-system/building-apps/03-built-in-extensions.md index 5f74100df0..44316bd898 100644 --- a/docs/frontend-system/building-apps/03-built-in-extensions.md +++ b/docs/frontend-system/building-apps/03-built-in-extensions.md @@ -2,7 +2,6 @@ id: built-in-extensions title: App Built-in Extensions sidebar_label: Built-in extensions -# prettier-ignore description: Configuring or overriding built-in extensions --- @@ -38,14 +37,14 @@ This extension is the first extension attached to the extension tree. It is resp #### Inputs -| Name | Description | Type | Optional | Default | Extension creator | -| ------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -| root | The app root element. | [coreExtensionData.reactElement](https://backstage.io/docs/reference/frontend-plugin-api.coreextensiondata) | false | The [`App/Root`](#app-root) extension output. | No creator available, configure or override the [`App/Root`](#app-root) extension. | -| apis | The app apis factories. | [createApiExtension.factoryDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createapiextension.factorydataref) | false | See [default apis](#default-apis-extensions). | [createApiExtension](https://backstage.io/docs/reference/frontend-plugin-api.createapiextension) | -| themes | The app themes list. | [createThemeExtension.themeDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createthemeextension.themedataref) | false | See [default themes](#default-theme-extensions). | [createThemeExtension](https://backstage.io/docs/reference/frontend-plugin-api.createthemeextension) | -| components | The app components list. | [createComponentExtension.componentDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createcomponentextension.componentdataref) | false | See [default components](#default-components-extensions). | [createComponentExtension](https://backstage.io/docs/reference/frontend-plugin-api.createcomponentextension) | -| translations | The app translations list. | [createTranslationExtension.translationDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createtranslationextension.translationdataref) | false | - | [createTranslationExtension](https://backstage.io/docs/reference/frontend-plugin-api.createtranslationextension) | -| icons | The app icons list. | [IconBundleBlueprint.dataRefs.icons](https://backstage.io/docs/reference/frontend-plugin-api.iconbundleblueprint.dataRefs.icons) | true | - | [IconBundleBlueprint](https://backstage.io/docs/reference/frontend-plugin-api.iconbundleblueprint) | +| Name | Description | Type | Optional | Default | Extension creator | +| ------------ | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| root | The app root element. | [coreExtensionData.reactElement](https://backstage.io/docs/reference/frontend-plugin-api.coreextensiondata) | false | The [`App/Root`](#app-root) extension output. | No creator available, configure or override the [`App/Root`](#app-root) extension. | +| apis | The app apis factories. | [createApiExtension.factoryDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createapiextension.factorydataref) | false | See [default apis](#default-apis-extensions). | [createApiExtension](https://backstage.io/docs/reference/frontend-plugin-api.createapiextension) | +| themes | The app themes list. | [createThemeExtension.themeDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createthemeextension.themedataref) | false | See [default themes](#default-theme-extensions). | [createThemeExtension](https://backstage.io/docs/reference/frontend-plugin-api.createthemeextension) | +| components | The app components list. | [SwappableComponentBlueprint.dataRefs.component](https://backstage.io/docs/reference/frontend-plugin-api.swappablecomponentblueprint.dataRefs.component) | false | See [default components](#default-components-extensions). | [SwappableComponentBlueprint](https://backstage.io/docs/reference/frontend-plugin-api.swappablecomponentblueprint) | +| translations | The app translations list. | [createTranslationExtension.translationDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createtranslationextension.translationdataref) | false | - | [createTranslationExtension](https://backstage.io/docs/reference/frontend-plugin-api.createtranslationextension) | +| icons | The app icons list. | [IconBundleBlueprint.dataRefs.icons](https://backstage.io/docs/reference/frontend-plugin-api.iconbundleblueprint.dataRefs.icons) | true | - | [IconBundleBlueprint](https://backstage.io/docs/reference/frontend-plugin-api.iconbundleblueprint) | #### Default theme extensions @@ -60,11 +59,11 @@ Extensions that provides default theme inputs for the `App` extension. Extensions that provides default components inputs for the `App` extension. -| kind | namespace | name | id | -| :--------: | :-------: | :-----------------------------------: | :----------------------------------------------------: | -| components | app | core.components.progress | `components:app/core.components.progress` | -| components | app | core.components.notFoundErrorPage | `components:app/core.components.notFoundErrorPage` | -| components | app | core.components.errorBoundaryFallback | `components:app/core.components.errorBoundaryFallback` | +| kind | namespace | name | id | +| :--------: | :-------: | :-----------------------: | :----------------------------------------: | +| components | app | core-progress | `components:app/core-progress` | +| components | app | core-not-found-error-page | `components:app/core-not-found-error-page` | +| components | app | core-error-display | `components:app/core-error-display` | #### Default apis extensions @@ -162,10 +161,10 @@ Extension responsible for rendering the logo and items in the app's sidebar. #### Inputs -| Name | Description | Type | Optional | Default | Extension creator | -| ----- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- | -------------------------------------------------------------------------------------------------------- | -| logos | A nav logos object. | [createNavLogoExtension.logoElementsDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createnavlogoextension.logoelementsdataref) | true | - | [createNavLogoExtension](https://backstage.io/docs/reference/frontend-plugin-api.createnavlogoextension) | -| items | Nav items target objects. | [createNavItemExtension.targetDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension.targetdataref) | true | - | [createNavItemExtension](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension) | +| Name | Description | Type | Optional | Default | Extension creator | +| ------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- | -------------------------------------------------------------------------------------------------------- | +| content | Overrides the default content of the navbar. | [NavContentBlueprint.dataRefs.component](https://backstage.io/docs/reference/frontend-plugin-api.navcontentblueprint) | true | - | [NavContentBlueprint](https://backstage.io/docs/reference/frontend-plugin-api.navcontentblueprint) | +| items | Nav items target objects. | [createNavItemExtension.targetDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension.targetdataref) | true | - | [createNavItemExtension](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension) | ### App routes diff --git a/docs/frontend-system/building-apps/06-plugin-conversion.md b/docs/frontend-system/building-apps/06-plugin-conversion.md index 2fab6cf828..a5d9ba6a5d 100644 --- a/docs/frontend-system/building-apps/06-plugin-conversion.md +++ b/docs/frontend-system/building-apps/06-plugin-conversion.md @@ -2,14 +2,14 @@ id: plugin-conversion title: Converting 3rd-party plugins from the old system sidebar_label: Converting 3rd-party Plugins -# prettier-ignore description: Documentation for how to convert 3rd-party plugins to support the new frontend system. --- If you are using or want to use a 3rd-party plugin that does not yet support the new frontend system in your app, you can often use conversion utilities from `@backstage/core-compat-api` in order wrap the plugin to make it possible to install in your app. -> [!CAUTION] -> The purpose of these utilities is to wrap 3rd-party plugins. Do not use them for your own plugins where you can add support for the new frontend system directly. +:::caution +The purpose of these utilities is to wrap 3rd-party plugins. Do not use them for your own plugins where you can add support for the new frontend system directly. +::: ## Converting a legacy plugin @@ -42,7 +42,7 @@ The conversion functions such as `convertLegacyPageExtension` will attempt to in ```ts const convertedIndexPage = convertLegacyPageExtension(TechDocsIndexPage, { name: 'index', - defaultPath: '/docs', + path: '/docs', }); ``` @@ -72,10 +72,10 @@ const convertedTechdocsPlugin = convertLegacyPlugin(techdocsPlugin, { extensions: [ convertLegacyPageExtension(TechDocsIndexPage, { name: 'index', - defaultPath: '/docs', + path: '/docs', }), convertLegacyPageExtension(TechDocsReaderPage, { - defaultPath: '/docs/:namespace/:kind/:name/*', + path: '/docs/:namespace/:kind/:name/*', }), convertLegacyEntityContentExtension(EntityTechdocsContent), ], diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index 861be2e39c..e796f64728 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -2,7 +2,6 @@ id: migrating title: Migrating Apps sidebar_label: Migration Guide -# prettier-ignore description: How to migrate existing apps to the new frontend system --- @@ -12,7 +11,13 @@ This section describes how to migrate an existing Backstage app package to use t ## Switching out `createApp` -The first step in migrating an app is to switch out the `createApp` function for the new one from `@backstage/frontend-api-app`: +To start we'll need to add the new `@backstage/frontend-defaults` package: + +```bash +yarn --cwd packages/app add @backstage/frontend-defaults +``` + +The next step in migrating an app is to switch out the `createApp` function for the new one from `@backstage/frontend-defaults`: ```tsx title="in packages/app/src/App.tsx" // highlight-remove-next-line @@ -61,7 +66,7 @@ import { const legacyFeatures = convertLegacyApp( <> - + {routes} @@ -70,7 +75,7 @@ const legacyFeatures = convertLegacyApp( ); const optionsModule = convertLegacyAppOptions({ - /* other options */ + /* other options such as apis, plugins, components, and themes */ }); const app = createApp({ @@ -98,6 +103,44 @@ ReactDOM.createRoot(document.getElementById('root')!).render(); ReactDOM.createRoot(document.getElementById('root')!).render(app); ``` +You'll also need to make similar changes to your `App.test.tsx` file as well: + +```tsx +import { render, waitFor } from '@testing-library/react'; +// highlight-remove-next-line +import App from './App'; +// highlight-add-next-line +import app from './App'; + +describe('App', () => { + it('should render', async () => { + process.env = { + NODE_ENV: 'test', + APP_CONFIG: [ + { + data: { + app: { title: 'Test' }, + backend: { baseUrl: 'http://localhost:7007' }, + }, + context: 'test', + }, + ] as any, + }; + + // highlight-remove-next-line + const rendered = render(); + // highlight-add-next-line + const rendered = render(app); + + await waitFor(() => { + expect(rendered.baseElement).toBeInTheDocument(); + }); + }); +}); +``` + +Then you'll want to follow the section on [migrating `bindRoutes`](#bindroutes). + At this point the contents of your app should be past the initial migration stage, and we can move on to migrating any remaining options that you may have passed to `createApp`. ## Migrating `createApp` Options @@ -106,7 +149,17 @@ Many of the `createApp` options have been migrated to use extensions instead. Ea For example, assuming you have a `lightTheme` extension that you want to add to your app, you can use the following: +First we add the `@backstage/frontend-plugin-api` package + +```bash +yarn --cwd packages/app add @backstage/frontend-plugin-api +``` + +Then we can use it like this: + ```ts +import { createFrontendModule } from '@backstage/frontend-plugin-api'; + const app = createApp({ features: [ // highlight-add-start @@ -142,21 +195,20 @@ const app = createApp({ Can be converted to the following extension: ```ts +import { ApiBlueprint } from '@backstage/frontend-plugin-api'; + const scmIntegrationsApi = ApiBlueprint.make({ name: 'scm-integrations', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: scmIntegrationsApiRef, deps: { configApi: configApiRef }, factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), }), - }, }); ``` -### `icons` - -Icons are currently installed through the usual options to `createApp`, but will be switched to use extensions in the future. +You would then add `scmIntegrationsApi` as an `extension` like you did with `lightTheme` in the [Migrating `createApp` Options](#migrating-createapp-options) section. ### `plugins` @@ -192,7 +244,7 @@ Plugins don't even have to be imported manually after installing their package i ```yaml title="in app-config.yaml" app: # Enabling plugin and override features discovery - experimental: 'all' + packages: all # ✨ ``` ### `featureFlags` @@ -218,6 +270,8 @@ createApp({ Can be converted to the following plugin configuration: ```tsx +import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; + createFrontendPlugin({ pluginId: 'tech-radar', // ... @@ -226,6 +280,8 @@ createFrontendPlugin({ }); ``` +This would get added to the `features` array as part of your `createApp` options. + ### `components` Many app components are now installed as extensions instead using `createComponentExtension`. See the section on [configuring app components](./01-index.md#configure-your-app) for more information. @@ -257,6 +313,8 @@ const app = createApp({ Can be converted to the following extension: ```tsx +import { SignInPageBlueprint } from '@backstage/frontend-plugin-api'; + const signInPage = SignInPageBlueprint.make({ params: { loader: async () => props => @@ -275,6 +333,8 @@ const signInPage = SignInPageBlueprint.make({ }); ``` +You would then add `signInPage` as an `extension` like you did with `lightTheme` in the [Migrating `createApp` Options](#migrating-createapp-options) section. + ### `themes` Themes are now installed as extensions, created using `ThemeBlueprint`. @@ -285,7 +345,7 @@ For example, the following theme configuration: const app = createApp({ themes: [ { - id: 'light', + id: 'custom-light', title: 'Light', variant: 'light', Provider: ({ children }) => ( @@ -301,27 +361,33 @@ const app = createApp({ Can be converted to the following extension: ```tsx -const lightTheme = ThemeBlueprint.make({ - name: 'light', +import { ThemeBlueprint } from '@backstage/frontend-plugin-api'; + +const customLightThemeExtension = ThemeBlueprint.make({ + name: 'custom-light', params: { theme: { - id: 'light', + id: 'custom-light', title: 'Light Theme', variant: 'light', icon: , Provider: ({ children }) => ( - + ), }, }, }); ``` +You would then add `customLightThemeExtension` as an `extension` like you did with `lightTheme` in the [Migrating `createApp` Options](#migrating-createapp-options) section. + ### `configLoader` The config loader API has been slightly changed. Rather than returning a promise for an array of `AppConfig` objects, it should now return the `ConfigApi` directly. ```ts +import { ConfigReader } from '@backstage/core-app-api'; + const app = createApp({ async configLoader() { const appConfigs = await loadAppConfigs(); @@ -403,7 +469,13 @@ createApp({ Can be converted to the following extension: ```tsx -TranslationBlueprint.make({ +import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha'; +import { + createTranslationMessages, + TranslationBlueprint, +} from '@backstage/frontend-plugin-api'; + +const catalogTranslations = TranslationBlueprint.make({ name: 'catalog-overrides', params: { resource: createTranslationMessages({ @@ -414,6 +486,8 @@ TranslationBlueprint.make({ }); ``` +You would then add `catalogTranslations` as an `extension` like you did with `lightTheme` in the [Migrating `createApp` Options](#migrating-createapp-options) section. + ## Gradual Migration After updating all `createApp` options as well as using `convertLegacyApp` to use your existing app structure, you should be able to start up the app and see that it still works. If that is not the case, make sure you read any error messages that you may see in the app as they can provide hints on what you need to fix. If you are still stuck, you can check if anyone else ran into the same issue in our [GitHub issues](https://github.com/backstage/backstage/issues), or ask for help in our [community Discord](https://discord.gg/backstage-687207715902193673). @@ -425,7 +499,7 @@ First off we'll want to trim away any top-level elements in the app so that only ```tsx title="in packages/app/src/App.tsx" const legacyFeatures = convertLegacyApp( <> - + {routes} diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index 33ec65d9d1..91e274ccab 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -2,7 +2,6 @@ id: index title: Building Frontend Plugins sidebar_label: Overview -# prettier-ignore description: Building frontend plugins using the new frontend system --- @@ -76,7 +75,7 @@ const examplePage = PageBlueprint.make({ routeRef: rootRouteRef, // This is the default path of this page, but integrators are free to override it - defaultPath: '/example', + path: '/example', // Page extensions are always dynamically loaded using React.lazy(). // All of the functionality of this page is implemented in the @@ -155,19 +154,18 @@ export function ExamplePage() { ``` ```tsx title="in src/plugin.ts - Registering a factory for our API" -import { createApiFactory, ApiBlueprint } from '@backstage/frontend-plugin-api'; +import { ApiBlueprint } from '@backstage/frontend-plugin-api'; import { exampleApiRef, DefaultExampleApi } from './api'; // highlight-add-start const exampleApi = ApiBlueprint.make({ name: 'example', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: exampleApiRef, deps: {}, factory: () => new DefaultExampleApi(), }), - }, }); // highlight-add-end @@ -200,8 +198,8 @@ import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; // route reference if you want to be able to generate a URL that links to the content. const exampleEntityContent = EntityContentBlueprint.make({ params: { - defaultPath: 'example', - defaultTitle: 'Example', + path: 'example', + title: 'Example', loader: () => import('./components/ExampleEntityContent').then(m => ( diff --git a/docs/frontend-system/building-plugins/02-testing.md b/docs/frontend-system/building-plugins/02-testing.md index 87c22b839b..f37c7cb106 100644 --- a/docs/frontend-system/building-plugins/02-testing.md +++ b/docs/frontend-system/building-plugins/02-testing.md @@ -2,7 +2,6 @@ id: testing title: Frontend System Testing Plugins sidebar_label: Testing -# prettier-ignore description: Testing plugins in the frontend system --- diff --git a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md index b437a44552..330da0b97e 100644 --- a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md +++ b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md @@ -2,7 +2,6 @@ id: common-extension-blueprints title: Common Extension Blueprints sidebar_label: Common Extension Blueprints -# prettier-ignore description: Extension blueprints provided by the frontend system and core features --- @@ -18,10 +17,6 @@ These are the [extension blueprints](../architecture/23-extension-blueprints.md) An API extension is used to add or override [Utility API factories](../utility-apis/01-index.md) in the app. They are commonly used by plugins for both internal and shared APIs. There are also many built-in Api extensions provided by the framework that you are able to override. -### Component - [Reference](../../reference/frontend-plugin-api.createcomponentextension.md) - -Components extensions are used to override the component associated with a component reference throughout the app. This uses an extension creator function rather than a blueprint, but will likely be migrated to a blueprint in the future. - ### NavItem - [Reference](../../reference/frontend-plugin-api.navitemblueprint.md) Navigation item extensions are used to provide menu items that link to different parts of the app. By default nav items are attached to the app nav extension, which by default is rendered as the left sidebar in the app. @@ -34,6 +29,10 @@ Page extensions provide content for a particular route in the app. By default pa Sign-in page extension have a single purpose - to implement a custom sign-in page. They are always attached to the app root extension and are rendered before the rest of the app until the user is signed in. +### SwappableComponent - [Reference](../../reference/frontend-plugin-api.swappablecomponentblueprint.md) + +Swappable Components are extensions that are used to replace the implementations of components in the app and plugins. + ### Theme - [Reference](../../reference/frontend-plugin-api.themeblueprint.md) Theme extensions provide custom themes for the app. They are always attached to the app extension and you can have any number of themes extensions installed in an app at once, letting the user choose which theme to use. diff --git a/docs/frontend-system/building-plugins/04-built-in-data-refs.md b/docs/frontend-system/building-plugins/04-built-in-data-refs.md index a26f6549b7..00392ed467 100644 --- a/docs/frontend-system/building-plugins/04-built-in-data-refs.md +++ b/docs/frontend-system/building-plugins/04-built-in-data-refs.md @@ -2,7 +2,6 @@ id: built-in-data-refs title: Built-in data refs sidebar_label: Built-in data refs -# prettier-ignore description: Configuring or overriding built-in extension data references --- diff --git a/docs/frontend-system/building-plugins/05-migrating.md b/docs/frontend-system/building-plugins/05-migrating.md index ba292aa5d5..2d954cb526 100644 --- a/docs/frontend-system/building-plugins/05-migrating.md +++ b/docs/frontend-system/building-plugins/05-migrating.md @@ -2,7 +2,6 @@ id: migrating title: Migrating Plugins sidebar_label: Migration Guide -# prettier-ignore description: How to migrate an existing frontend plugin to the new frontend system --- @@ -120,7 +119,7 @@ const fooPage = PageBlueprint.make({ params: { // This is the path that was previously defined in the app code. // It's labelled as the default one because it can be changed via configuration. - defaultPath: '/foo', + path: '/foo', // You can reuse the existing routeRef by wrapping it with convertLegacyRouteRef. routeRef: convertLegacyRouteRef(rootRouteRef), // these inputs usually match the props required by the component. @@ -206,22 +205,17 @@ The major changes we'll make are The end result, after simplifying imports and cleaning up a bit, might look like this: ```tsx title="in @internal/plugin-example" -import { - storageApiRef, - createApiFactory, - ApiBlueprint, -} from '@backstage/frontend-plugin-api'; +import { storageApiRef, ApiBlueprint } from '@backstage/frontend-plugin-api'; import { workApiRef } from '@internal/plugin-example-react'; import { WorkImpl } from './WorkImpl'; const exampleWorkApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: workApiRef, deps: { storageApi: storageApiRef }, factory: ({ storageApi }) => new WorkImpl({ storageApi }), }), - }, }); ``` diff --git a/docs/frontend-system/building-plugins/06-swappable-components.md b/docs/frontend-system/building-plugins/06-swappable-components.md new file mode 100644 index 0000000000..a26396bbff --- /dev/null +++ b/docs/frontend-system/building-plugins/06-swappable-components.md @@ -0,0 +1,114 @@ +--- +id: swappable-components +title: Swappable components +sidebar_label: Swappable components +description: Configuring or overriding Swappable Components +--- + +# Swappable components + +Swappable components are a feature of the frontend system that allow you to replace the implementations of components that are used in your Backstage app. +These Swappable Components are defined using `createSwappableComponent` and then can be exported from a plugins `-react` package in order to be used in both other plugins, and to be rebound to a new implementation by the Backstage Integrator. + +## Creating a Swappable Component + +In order to create a Swappable Component, you need to use the `createSwappableComponent` function from the `@backstage/frontend-plugin-api` package. You can supply a default implementation for the component, as well as a way to separate both the props of the external component and in the implementation of the component. + +```tsx title="in @internal/plugin-example-react" +import { createSwappableComponent } from '@backstage/frontend-plugin-api'; + +export const ExampleSwappableComponent = createSwappableComponent({ + name: 'example', + + // This is a loader for loading the default implementation of the component when there's no overriden + // implementation created with `SwappableComponentBlueprint`. + // It can be sync like below, but is can also be async like `loader: () => import('./DefaultImplementation').then(m => m.DefaultImplementation)`. + loader: () => (props: { name: string }) => +
    Your name is {props.name}
    , + + // This is an optional function that can be used to transform the props of the external component. + // If it's not provided, the props of the external component will be passed through unchanged. + // Typically this wouldn't be used and you don't need to define this initially, + // this is a nice API to evolve the Swappable Component internal API without breaking usages of it in existing plugins. + transformProps: (props: { lastName: string; firstName: string }) => ({ + name: `${props.firstName} ${props.lastName}`, + }), +}); +``` + +## Using a Swappable Component + +Using a Swappable Component is just like using any other React Component. The return of `createSwappableComponent` can be rendered directly in your plugin, or any plugins that will be consuming your `-react` package. + +```tsx title="in @internal/plugin-example" +import { ExampleSwappableComponent } from '@internal/plugin-example-react'; + +; +``` + +## Overriding a Swappable Component + +In order to override a Swappable Component, you need to create a `SwappableComponentBlueprint` and install it with the `app` plugin. +There are two different ways to add extensions to the `app` plugin, both are documented below in an example of overriding the `Progress` Swappable Component. + +```tsx title="in packages/app/src/App.tsx" +import { + Progress, + SwappableComponentBlueprint, + createFrontendModule, +} from '@backstage/frontend-plugin-api'; +import { MyCustomProgress } from './CustomProgress'; +import { createApp } from '@backstage/frontend-defaults'; +import appPlugin from '@backstage/plugin-app'; + +const app = createApp({ + features: [ + // Using a module to provide the extenion to the app + createFrontendModule({ + pluginId: 'app', + extensions: [ + SwappableComponentBlueprint.make({ + name: 'core-progress', + params: defineParams => + defineParams({ + component: Progress, + loader: () => MyCustomProgress, + }), + }), + ], + }), + // Core components that already ship with the app plugin can be overriden using getExtension() + appPlugin.withOverrides({ + extensions: [ + appPlugin.getExtension('component:app/core-progress').override({ + params: defineParams => + defineParams({ + component: Progress, + loader: () => MyCustomProgress, + }), + }), + ], + }), + ], +}); +``` + +## Default Swappable Components + +Currently there are only three different built-in Swappable Components that you can replace the implementations of, and these live in `@backstage/frontend-plugin-api`. They are as follows: + +- ` +- ` +- ` + +You can see more about these components at their [definition](https://github.com/backstage/backstage/blob/master/packages/frontend-plugin-api/src/components/DefaultSwappableComponents.tsx), and their default implementations are shipped inside the [`app-plugin`](https://github.com/backstage/backstage/blob/master/plugins/app/src/extensions/components.tsx). + +## Implementations in tests + +By default, if no `loader` is passed through to `createSwappableComponent` then there's a default fallback component which will be rendered, which is mainly helpful in tests. The default implementation for a Swappable Component without a `loader` is as follows: + +```tsx +props =>
    ; +``` + +Which means that you can use the `getByTestId` in tests to assert that these components have been rendered on the page. diff --git a/docs/frontend-system/index.md b/docs/frontend-system/index.md index 201718f6d3..c9a8059086 100644 --- a/docs/frontend-system/index.md +++ b/docs/frontend-system/index.md @@ -2,7 +2,6 @@ id: index title: The Frontend System sidebar_label: Introduction -# prettier-ignore description: The Frontend System --- diff --git a/docs/frontend-system/utility-apis/01-index.md b/docs/frontend-system/utility-apis/01-index.md index 07f7dcc2a8..71e1eea740 100644 --- a/docs/frontend-system/utility-apis/01-index.md +++ b/docs/frontend-system/utility-apis/01-index.md @@ -2,7 +2,6 @@ id: index title: Utility APIs sidebar_label: Overview -# prettier-ignore description: Working with Utility APIs in the New Frontend System --- diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md index bf53911d24..c98c4e03f5 100644 --- a/docs/frontend-system/utility-apis/02-creating.md +++ b/docs/frontend-system/utility-apis/02-creating.md @@ -2,7 +2,6 @@ id: creating title: Creating Utility APIs sidebar_label: Creating APIs -# prettier-ignore description: Creating new utility APIs in your plugins and app --- @@ -46,7 +45,6 @@ The plugin itself now wants to provide this API and its default implementation, ```tsx title="in @internal/plugin-example" import { ApiBlueprint, - createApiFactory, createFrontendPlugin, storageApiRef, StorageApi, @@ -64,15 +62,14 @@ class WorkImpl implements WorkApi { const workApi = ApiBlueprint.make({ name: 'work', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: workApiRef, deps: { storageApi: storageApiRef }, factory: ({ storageApi }) => { return new WorkImpl({ storageApi }); }, }), - }, }); /** diff --git a/docs/frontend-system/utility-apis/03-consuming.md b/docs/frontend-system/utility-apis/03-consuming.md index bdf3b8dcdb..aa3c840d79 100644 --- a/docs/frontend-system/utility-apis/03-consuming.md +++ b/docs/frontend-system/utility-apis/03-consuming.md @@ -2,7 +2,6 @@ id: consuming title: Consuming Utility APIs sidebar_label: Consuming APIs -# prettier-ignore description: Consuming utility APIs --- @@ -47,14 +46,13 @@ Your utility APIs can depend on other utility APIs in their factories. You do th import { configApiRef, ApiBlueprint, - createApiFactory, discoveryApiRef, } from '@backstage/frontend-plugin-api'; import { MyApiImpl } from './MyApiImpl'; const myApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: myApiRef, deps: { configApi: configApiRef, @@ -64,7 +62,6 @@ const myApi = ApiBlueprint.make({ return new MyApiImpl({ configApi, discoveryApi }); }, }), - }, }); ``` diff --git a/docs/frontend-system/utility-apis/04-configuring.md b/docs/frontend-system/utility-apis/04-configuring.md index 4847fc6eaf..1b28bf1351 100644 --- a/docs/frontend-system/utility-apis/04-configuring.md +++ b/docs/frontend-system/utility-apis/04-configuring.md @@ -2,7 +2,6 @@ id: configuring title: Configuring Utility APIs sidebar_label: Configuring -# prettier-ignore description: Configuring, extending, and overriding utility APIs --- diff --git a/docs/getting-started/app-custom-theme.md b/docs/getting-started/app-custom-theme.md index c8c4ee8e5a..38f2fd4c98 100644 --- a/docs/getting-started/app-custom-theme.md +++ b/docs/getting-started/app-custom-theme.md @@ -435,7 +435,7 @@ Supply your custom icon in `packages/app/src/App.tsx` ```tsx title="packages/app/src/App.tsx" /* highlight-add-next-line */ -import { ExampleIcon } from './assets/customIcons' +import { ExampleIcon } from './assets/icons/CustomIcons' const app = createApp({ diff --git a/docs/getting-started/config/authentication.md b/docs/getting-started/config/authentication.md index cb0a329ae1..e25f357ff8 100644 --- a/docs/getting-started/config/authentication.md +++ b/docs/getting-started/config/authentication.md @@ -128,7 +128,7 @@ backend.add(import('@backstage/plugin-auth-backend-module-github-provider')); /* highlight-add-end */ ``` -Restart Backstage from the terminal, by stopping it with `Ctrl+C`, and starting it with `yarn dev`. You should be welcomed by a login prompt! If you try to login at this point you will get a "Failed to sign-in, unable to resolve user identity" message, read on as we'll fix that next. +Restart Backstage from the terminal, by stopping it with `Ctrl+C`, and starting it with `yarn start`. You should be welcomed by a login prompt! If you try to login at this point you will get a "Failed to sign-in, unable to resolve user identity" message, read on as we'll fix that next. :::note Note @@ -157,7 +157,7 @@ For the sake of this guide we'll simply step you though adding a User to the `or 3. Now make sure to replace the text "YOUR GITHUB USERNAME" with your actual GitHub User name. -Let's restart Backstage from the terminal once more, by stopping it with `Ctrl+C`, and starting it with `yarn dev`. You should now be able to log into Backstage and see items in your Catalog. +Let's restart Backstage from the terminal once more, by stopping it with `Ctrl+C`, and starting it with `yarn start`. You should now be able to log into Backstage and see items in your Catalog. To learn more about Authentication in Backstage, here are some docs you could read: @@ -199,7 +199,7 @@ integrations: :::note Note -If you've updated the configuration for your integration, it's likely that the backend will need a restart to apply these changes. To do this, stop the running instance in your terminal with `Control-C`, then start it again with `yarn dev`. Once the backend has restarted, retry the operation. +If you've updated the configuration for your integration, it's likely that the backend will need a restart to apply these changes. To do this, stop the running instance in your terminal with `Control-C`, then start it again with `yarn start`. Once the backend has restarted, retry the operation. ::: diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md index 782e6f9826..010917f6c8 100644 --- a/docs/getting-started/configure-app-with-plugins.md +++ b/docs/getting-started/configure-app-with-plugins.md @@ -82,11 +82,20 @@ import ExtensionIcon from '@material-ui/icons/Extension'; You can also use your own SVGs directly as icon components. Just make sure they are sized according to the Material UI's -[SvgIcon](https://material-ui.com/api/svg-icon/) default of 24x24px, and set the -extension to `.icon.svg`. For example: +[SvgIcon](https://material-ui.com/api/svg-icon/) default of 24x24px, and wrap +the SVG elements in a `SvgIcon` component like this: ```tsx -import InternalToolIcon from './internal-tool.icon.svg'; +import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon'; + +export const ExampleIcon = (props: SvgIconProps) => ( + + + + + + +); ``` On mobile devices the `Sidebar` is displayed at the bottom of the screen. For diff --git a/docs/getting-started/keeping-backstage-updated.md b/docs/getting-started/keeping-backstage-updated.md index a44cb3c8bc..dea0cc2939 100644 --- a/docs/getting-started/keeping-backstage-updated.md +++ b/docs/getting-started/keeping-backstage-updated.md @@ -21,7 +21,7 @@ starting point that's meant to be evolved. The Backstage CLI has a command to bump all `@backstage` packages and dependencies you're using to the latest versions: -[versions:bump](https://backstage.io/docs/tooling/cli/03-commands#versionsbump). +[versions:bump](../tooling/cli/03-commands.md#versionsbump). ```bash yarn backstage-cli versions:bump diff --git a/docs/integrations/aws-codecommit/locations.md b/docs/integrations/aws-codecommit/locations.md index 35187b1b89..28e77769b2 100644 --- a/docs/integrations/aws-codecommit/locations.md +++ b/docs/integrations/aws-codecommit/locations.md @@ -2,7 +2,6 @@ id: locations sidebar_label: Locations title: Amazon Web Services CodeCommit Locations -# prettier-ignore description: Setting up an integration with Amazon Web Services CodeCommit --- diff --git a/docs/integrations/aws-s3/discovery.md b/docs/integrations/aws-s3/discovery.md index fa1eb017fc..fcba5ca04f 100644 --- a/docs/integrations/aws-s3/discovery.md +++ b/docs/integrations/aws-s3/discovery.md @@ -2,7 +2,6 @@ id: discovery title: AWS S3 Discovery sidebar_label: Discovery -# prettier-ignore description: Automatically discovering catalog entities from an AWS S3 Bucket --- diff --git a/docs/integrations/aws-s3/locations.md b/docs/integrations/aws-s3/locations.md index 9bac28f906..3ed6af62fe 100644 --- a/docs/integrations/aws-s3/locations.md +++ b/docs/integrations/aws-s3/locations.md @@ -2,7 +2,6 @@ id: locations sidebar_label: Locations title: Amazon Web Services S3 Locations -# prettier-ignore description: Setting up an integration with Amazon Web Services S3 --- diff --git a/docs/integrations/azure-blobStorage/discovery.md b/docs/integrations/azure-blobStorage/discovery.md index 32612daf75..bed97441f9 100644 --- a/docs/integrations/azure-blobStorage/discovery.md +++ b/docs/integrations/azure-blobStorage/discovery.md @@ -2,7 +2,6 @@ id: discovery title: Azure Blob Storage Discovery sidebar_label: Discovery -# prettier-ignore description: Automatically discovering catalog entities from an Azure Blob Storage account --- diff --git a/docs/integrations/azure-blobStorage/locations.md b/docs/integrations/azure-blobStorage/locations.md index 38d8555c2e..abe2a238ba 100644 --- a/docs/integrations/azure-blobStorage/locations.md +++ b/docs/integrations/azure-blobStorage/locations.md @@ -2,7 +2,6 @@ id: locations sidebar_label: Locations title: Azure Blob Storage account Locations -# prettier-ignore description: Setting up an integration with Azure Blob Storage account --- diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md index b749f8e9ed..a4584ef338 100644 --- a/docs/integrations/azure/discovery.md +++ b/docs/integrations/azure/discovery.md @@ -2,7 +2,6 @@ id: discovery title: Azure DevOps Discovery sidebar_label: Discovery -# prettier-ignore description: Automatically discovering catalog entities from repositories in an Azure DevOps organization --- diff --git a/docs/integrations/azure/locations.md b/docs/integrations/azure/locations.md index ddc95b65eb..3ea180f5bd 100644 --- a/docs/integrations/azure/locations.md +++ b/docs/integrations/azure/locations.md @@ -2,7 +2,6 @@ id: locations title: Azure DevOps Locations sidebar_label: Locations -# prettier-ignore description: Integrating source code stored in Azure DevOps into the Backstage catalog --- @@ -13,7 +12,17 @@ or registered with the [catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import) plugin. -Using a service principal: +## Authentication + +The Azure integration supports several methods to authenticate against Azure DevOps. The following sections describe how to configure the integration for each authentication method. + +It is also possible to configure separate authentication methods for different Azure DevOps organizations. This is useful if you have multiple organizations and want to use (or have to) different credentials for each organization. + +### Using a service principal with a client secret + +A service principal is an Entra ID identity that can be used to authenticate against Azure DevOps. The service principal is created in Entra ID and has a client ID and client secret (akin to a username and password). + +The following configuration shows how to use a service principal to authenticate against Azure DevOps: ```yaml integrations: @@ -25,7 +34,29 @@ integrations: tenantId: ${AZURE_TENANT_ID} ``` -Using a managed identity: +See the Azure DevOps documentation on how to grant access to the [service principal](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity). + +#### Using a system-assigned managed identity + +A system-assigned [managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview) is an Entra ID identity that is tied to a specific Azure resource and managed by Azure. In contrast to a user-assigned managed identity, a system-assigned managed identity shares the lifecycle of the resource to which it is assigned and Azure guarantees that the identity can only be used by the specific resource. + +The following configuration shows how to use a system-assigned managed identity to authenticate against Azure DevOps: + +```yaml +integrations: + azure: + - host: dev.azure.com + credentials: + - clientId: system-assigned +``` + +See the Azure DevOps documentation on how to grant access to the [managed identity](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity). + +#### Using a user-assigned managed identity + +A user-assigned [managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview) is an Entra ID identity that is created as a standalone resource by the user and assigned to one or more Azure resources. This allows you to use the same managed identity across multiple resources. + +The following configuration shows how to use a user-assigned managed identity to authenticate against Azure DevOps: ```yaml integrations: @@ -35,7 +66,13 @@ integrations: - clientId: ${AZURE_CLIENT_ID} ``` -Using a personal access token (PAT): +See the Azure DevOps documentation on how to grant access to the [managed identity](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity). + +### Using a personal access token (PAT) + +A personal access token (PAT) is a token you generate with a specific scope and expiration date. It allows Backstage to authenticate against Azure DevOps on your behalf. + +The following configuration shows how to use a personal access token to authenticate against Azure DevOps: ```yaml integrations: @@ -45,6 +82,55 @@ integrations: - personalAccessToken: ${PERSONAL_ACCESS_TOKEN} ``` +See the Azure DevOps documentation on how to create a [personal access token](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate) + +### Using a service principal with a managed identity to generate the client assertion + +Using a managed identity to generate a client assertion is an advanced scenario. It requires you to setup a federated credential for the app registration in Azure Entra ID. + +It is most useful when you want to [authenticate against an Azure DevOps organization in a different tenant](#authenticate-against-an-azure-devops-organization-in-a-different-tenant) than the managed identity itself. Otherwise [a regular managed identity](#using-a-system-assigned-managed-identity) is probably a more suitable choice. + +#### Add a federated credential + +To be able to use a managed identity to generate a client assertion, you need to create a federated credential in Azure Entra ID. Follow these steps: + +1. Create an app registration in Entra ID (or use an existing one). +2. Navigate to the "Certificates & secrets" tab for your app registration. +3. Add a new federated credential using the "Customer managed keys" scenario. +4. Select the managed identity you want to use to generate the client assertion. +5. Enter the name and description. +6. Click "Add". + +You can now add the required configuration to the Azure DevOps integration in Backstage. The `${APP_REGISTRATION_CLIENT_ID}` is the client ID of the app registration in Entra ID where you added the federated credential. + +#### Using a system-assigned managed identity to generate the client assertion + +This is the most secure option because Azure guarantees that the identity can only be used by the specific resource, whereas a user-assigned managed identity can be assigned to any resource in the same tenant. + +```yaml +integrations: + azure: + - host: dev.azure.com + credentials: + - clientId: ${APP_REGISTRATION_CLIENT_ID} + managedIdentityClientId: system-assigned + tenantId: ${AZURE_TENANT_ID} +``` + +#### Using a user-assigned managed identity to generate the client assertion + +```yaml +integrations: + azure: + - host: dev.azure.com + credentials: + - clientId: ${APP_REGISTRATION_CLIENT_ID} + managedIdentityClientId: ${MANAGED_IDENTITY_CLIENT_ID} + tenantId: ${AZURE_TENANT_ID} +``` + +### Authenticating against multiple Azure DevOps organizations + You can use specific credentials for different Azure DevOps organizations by specifying the `organizations` field on the credential: ```yaml @@ -68,27 +154,76 @@ integrations: If you do not specify the `organizations` field the credential will be used for all organizations for which no other credential is configured. +### Authenticate against an Azure DevOps organization in a different tenant + +If you need to authenticate against an Azure DevOps organization in a different tenant than the service principal, you have to either: + +- [Create a multi-tenant application in Entra ID](https://learn.microsoft.com/en-us/entra/identity-platform/single-and-multi-tenant-apps). +- [Convert the existing application to a multi-tenant application](https://learn.microsoft.com/en-gb/entra/identity-platform/howto-convert-app-to-be-multi-tenant#update-registration-to-be-multitenant). + :::note Note -An Azure DevOps provider is added automatically at startup for -convenience, so you only need to list it if you want to supply a -[personalAccessToken](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate), -a [service principal](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity), -or a [managed identity](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity) +Make sure that your application requests at least one Graph API permission. This is required to be able to install the application in another tenant. The least privileged permission you can request is the [`email` permission](https://learn.microsoft.com/en-us/graph/permissions-reference#email) with type `Delegated`. This allows the application to read the e-mail address of the signed-in user, but without the [`openid` permission](https://learn.microsoft.com/en-us/graph/permissions-reference#openid) users cannot actually sign in. ::: +After you have done that, an admin from the other tenant has to install your application by providing admin consent for the requested permissions. This can be done by visiting the following URL: + +```plaintext +https://login.microsoftonline.com//oauth2/authorize?client_id=&response_type=code&redirect_uri= +``` + +The `` is the tenant ID of the other tenant, `` is the client ID of the application (in your tenant), and `` is the redirect URI configured for the application. The redirect URI must be a valid URI in the application registration, but you can use any valid URI for this purpose, for example `https://backstage.io`. + +After the admin has consented to the application, an Enterprise Application, also called a Service Principal, will be created in the other tenant with the same client ID as the app registration in the original tenant. You can now grant the service principal access to the Azure DevOps organization in the other tenant. To authenticate against the Azure DevOps organization in the other tenant, you can use the same service principal as before, but with the tenant ID of the other tenant: + +```yaml +integrations: + azure: + - host: dev.azure.com + credentials: + - clientId: ${APP_REGISTRATION_CLIENT_ID} + managedIdentityClientId: system-assigned + tenantId: ${OTHER_TENANT_ID} +``` + +Where `${APP_REGISTRATION_CLIENT_ID}` is the client ID of the multi-tenant app registration in you created in your own tenant, and `${OTHER_TENANT_ID}` is the tenant ID of the other tenant where you . + +:::note Note + +The example above uses a [system-assigned managed identity to generate the client assertion](#using-a-system-assigned-managed-identity-to-generate-the-client-assertion). You can also use a [user-assigned managed identity to generate the client assertion](#using-a-user-assigned-managed-identity-to-generate-the-client-assertion) or a client secret to authenticate for the application. + +However a system-assigned managed identity is the most secure option because: + +- Azure guarantees that the identity can only be used by the specific resource, whereas a user-assigned managed identity can be used by any resource. +- There is no need to manage the underlying secrets, Azure takes care of that for you. + +::: + +## Configuration schema + The configuration is a structure with these elements: -- `credentials`: (optional): A service principal, managed identity, or personal access token +- `credentials`: (optional): must be one of the following: + - A service principal using a client secret + - A service principal using a managed identity client assertion + - A managed identity + - A personal access token -The `credentials` element is a structure with these elements: +The `credentials` element is an array where each entry is a structure with exactly these of elements: -- `organizations`: (optional): A list of organizations for which this credential should be used. If not specified the credential will be used for all organizations for which no other credential is configured. -- `clientId`: The client ID of the service principal or managed identity (required for service principal and managed identities) -- `clientSecret`: The client secret of the service principal (required for service principal) -- `tenantId`: The tenant ID of the service principal (required for service principal) -- `personalAccessToken`: The personal access token (required for personal access token) +- For a service principal with client secret: + - `clientId`: The client ID of the service principal + - `clientSecret`: The client secret of the service principal + - `tenantId`: The tenant ID of the service principal +- For a service principal with managed identity client assertion: + - `clientId`: The client ID of the service principal + - `managedIdentityClientId`: the client ID of the managed identity used to generate the client assertion token. Use `system-assigned` for system-assigned managed identities or the client ID of a user-assigned managed identity. + - `tenantId`: The tenant ID of the service principal +- For managed identity: + - `clientId`: the client ID of the managed identity used to generate the client assertion token. +- For personal access token: + - `personalAccessToken`: The personal access token :::note Note @@ -96,5 +231,6 @@ The `credentials` element is a structure with these elements: - You can only use a service principal or managed identity for Microsoft Entra ID (formerly Azure Active Directory) backed Azure DevOps organizations - You can only specify one credential per host without any organizations specified - The personal access token should just be provided as the raw token generated by Azure DevOps using the format `raw_token` with no base64 encoding. Formatting and base64'ing is handled by dependent libraries handling the Azure DevOps API +- The managed identity used to generate the client assertion must be in the same Entra ID tenant as the app registration. ::: diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md index 58c6c9f889..efcd8ae612 100644 --- a/docs/integrations/azure/org.md +++ b/docs/integrations/azure/org.md @@ -2,7 +2,6 @@ id: org title: Microsoft Entra Tenant Data sidebar_label: Org Data -# prettier-ignore description: Importing users and groups from Microsoft Entra ID into Backstage --- diff --git a/docs/integrations/bitbucketCloud/discovery.md b/docs/integrations/bitbucketCloud/discovery.md index 9560c5e06b..e0fab954ac 100644 --- a/docs/integrations/bitbucketCloud/discovery.md +++ b/docs/integrations/bitbucketCloud/discovery.md @@ -2,7 +2,6 @@ id: discovery title: Bitbucket Cloud Discovery sidebar_label: Discovery -# prettier-ignore description: Automatically discovering catalog entities from repositories in Bitbucket Cloud --- diff --git a/docs/integrations/bitbucketCloud/locations.md b/docs/integrations/bitbucketCloud/locations.md index 9b08a79c63..f0a032353c 100644 --- a/docs/integrations/bitbucketCloud/locations.md +++ b/docs/integrations/bitbucketCloud/locations.md @@ -2,7 +2,6 @@ id: locations title: Bitbucket Cloud Locations sidebar_label: Locations -# prettier-ignore description: Integrating source code stored in Bitbucket Cloud into the Backstage catalog --- diff --git a/docs/integrations/bitbucketServer/discovery.md b/docs/integrations/bitbucketServer/discovery.md index 36282b1f48..350bc33aed 100644 --- a/docs/integrations/bitbucketServer/discovery.md +++ b/docs/integrations/bitbucketServer/discovery.md @@ -2,7 +2,6 @@ id: discovery title: Bitbucket Server Discovery sidebar_label: Discovery -# prettier-ignore description: Automatically discovering catalog entities from repositories in Bitbucket Server --- @@ -67,6 +66,7 @@ catalog: projectKey: '^apis-.*$' # optional; RegExp repoSlug: '^service-.*$' # optional; RegExp skipArchivedRepos: true # optional; boolean + validateLocationsExist: false # optional; boolean schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } @@ -87,6 +87,10 @@ catalog: Regular expression used to filter results based on the repo slug. - **`skipArchivedRepos`** _(optional)_: Boolean flag to filter out archived repositories. +- **`validateLocationsExist`** _(optional)_: + Defaults to `false`. + Whether to validate locations that exist before emitting them. + This option avoids generating locations for catalog info files that do not exist in the source repository. - **`schedule`**: - **`frequency`**: How often you want the task to run. The system does its best to avoid overlapping invocations. diff --git a/docs/integrations/bitbucketServer/locations.md b/docs/integrations/bitbucketServer/locations.md index 7ff19b8b27..0c8b0e16b4 100644 --- a/docs/integrations/bitbucketServer/locations.md +++ b/docs/integrations/bitbucketServer/locations.md @@ -2,7 +2,6 @@ id: locations title: Bitbucket Server Locations sidebar_label: Locations -# prettier-ignore description: Integrating source code stored in Bitbucket Server into the Backstage catalog --- diff --git a/docs/integrations/datadog-rum/installation.md b/docs/integrations/datadog-rum/installation.md index 6659bec182..5b4fe8bd40 100644 --- a/docs/integrations/datadog-rum/installation.md +++ b/docs/integrations/datadog-rum/installation.md @@ -2,7 +2,6 @@ id: installation title: Datadog RUM Installation sidebar_label: Installation -# prettier-ignore description: Adding Datadog Real User Monitoring (RUM) to Your App --- diff --git a/docs/integrations/gerrit/discovery.md b/docs/integrations/gerrit/discovery.md index dbbc7ba7a4..06fb144e50 100644 --- a/docs/integrations/gerrit/discovery.md +++ b/docs/integrations/gerrit/discovery.md @@ -2,7 +2,6 @@ id: discovery title: Gerrit Discovery sidebar_label: Discovery -# prettier-ignore description: Automatically discovering catalog entities from Gerrit repositories --- diff --git a/docs/integrations/gitea/discovery.md b/docs/integrations/gitea/discovery.md new file mode 100644 index 0000000000..ce8ad9d360 --- /dev/null +++ b/docs/integrations/gitea/discovery.md @@ -0,0 +1,58 @@ +--- +id: discovery +title: Gitea Discovery +sidebar_label: Discovery +description: Automatically discovering catalog entities from Gitea repositories +--- + +The Gitea integration has a special entity provider for discovering catalog entities +from Gitea repositories. The provider uses the "List Projects" API in Gitea to get +a list of repositories and will automatically ingest all `catalog-info.yaml` files +stored in the root of the matching projects. + +## Installation + +As this provider is not one of the default providers, you will first need to install +the Gitea provider plugin: + +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-gitea +``` + +Then update your backend by adding the following line: + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-catalog-backend-module-gitea')); +/* highlight-add-end */ +``` + +## Configuration + +To use the discovery provider, you'll need a Gitea integration +[set up](locations.md). Then you can add any number of providers. + +```yaml +# app-config.yaml +catalog: + providers: + gitea: + yourProviderId: # identifies your dataset / provider independent of config changes + organization: 'your-company' # string + host: gitea-your-company.com + branch: 'main' # Optional, defaults to 'main' + catalogPath: 'catalog-info.yaml' # Optional, defaults to catalog-info.yaml + schedule: + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } +``` + +The provider configuration consists of the following parts: + +- **`organization`**: Name of your organization account/workspace. If you want to add multiple organizations, you need to add one provider config each. +- **`host`**: the host of the Gitea integration to use. +- **`branch`** _(optional)_: the branch where we will look for catalog entities (defaults to 'main'). +- **`catalogPath`**: path relative to the root of the repository where the Backstage manifests are stored. diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index 354569cdb6..c56aed054b 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -2,7 +2,6 @@ id: discovery title: GitHub Discovery sidebar_label: Discovery -# prettier-ignore description: Automatically discovering catalog entities from repositories in a GitHub organization --- @@ -38,32 +37,149 @@ backend.add(import('@backstage/plugin-catalog-backend-module-github')); ## Events Support -The catalog module for GitHub comes with events support enabled. -This will make it subscribe to its relevant topics (`github.push`, -`github.repository`) and expects these events to be published -via the `EventsService`. +The catalog module for GitHub comes with events support enabled. This will make it subscribe to its relevant topics (`github.push`, `github.repository`) and expects these events to be published via the `EventsService`. -Additionally, you should install the -[event router by `events-backend-module-github`](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github/README.md) -which will route received events from the generic topic `github` to more specific ones -based on the event type (e.g., `github.push`). +### Prerequisites -In order to receive Webhook events by GitHub, you have to decide how you want them -to be ingested into Backstage and published to its `EventsService`. -You can decide between the following options (extensible): +There are two Prerequisites to use the builtin events support: -- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) -- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) +1. Creating a webhook in GitHub +2. Installing and configuring `@backstage/plugin-events-backend-module-github` + +#### Configure Webhooks in GitHub You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks). -The webhook(s) will need to be configured to react to `push` and -`repository` events. +The webhook(s) will need to be configured to react to `push` and `repository` events. -Certain actions like `transferred` by the `repository` event type -will not be supported when you use repository webhooks. -Please check the GitHubs documentation for these event types and -its actions. +:::note + +To receive the `repository.transferred` event, the new owner account must have the GitHub App installed, and the App must be subscribed to `repository` events. This event is only sent to the account where the ownership is transferred. + +::: + +When creating the webhook in GitHub the "Payload URL" will looks something along these lines: `https:///api/events/http/github` and the "Content Type" should be `application/json`. + +The GitHub Webhooks UI will send a trial event to validate it can connect when you save your new Webhook. It is possible to retry this trial event if it fails and you want to send it again. Additionally there is a Recent Deliveries tab you can use to validate that the events are being fired should you need to do any later troubleshooting. + +#### Install and Configure GitHub Events Module + +In order to use the built-in events support you'll need to install and configure `@backstage/plugin-events-backend-module-github`. This module will route received events from the generic topic `github` to more specific ones based on the event type (e.g., `github.push`). These more specific events are what the builtin events support is expecting. + +First we need to add the package: + +```bash title="from your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-events-backend-module-github +``` + +Then we need to add it to your backend: + +```ts title="in packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-events-backend')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-events-backend-module-github')); +/* highlight-add-end */ +``` + +Finally you will want to configure it: + +```yaml title="app-config.yaml +events: + modules: + github: + webhookSecret: ${GITHUB_WEBHOOK_SECRET} +``` + +Though this last step is technically optional, you'll want to include it to be sure the events being received are from GitHub and not from an external bad actor. + +The value of `${GITHUB_WEBHOOK_SECRET}` in this example would be the same that you used when creating the webhook on GitHub. + +### Events Setup using HTTP endpoint + +Using the HTTP endpoint for events just requires adding some additional configuration to your `app-config.yaml` as it is a built in feature of the Events backend, here's what that would look like: + +```yaml title="app-config.yaml +events: + http: + topics: + - github +``` + +This will then expose an endpoint like this: + +### Events Setup using AWS SQS module + +Alternatively to using the HTTP endpoint you can use the AWS SQS module, here's how. + +First we need to add the package: + +```bash title="from your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugins-events-backend-module-aws-sqs +``` + +Then we need to add it to your backend: + +```ts title="in packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-events-backend')); +backend.add(import('@backstage/plugin-events-backend-module-github')); +/* highlight-add-start */ +backend.add(import('@backstage/plugins-events-backend-module-aws-sqs')); +/* highlight-add-end */ +``` + +Finally you will want to configure it: + +```yaml title="app-config.yaml +events: + modules: + awsSqs: + awsSqsConsumingEventPublisher: + topics: + github: + queue: + url: 'https://sqs.us-east-2.amazonaws.com/123456789012/MyQueue' + region: us-east-2 +``` + +The [AWS SQS module `README`](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-aws-sqs/README.md#configuration) has more details on the configuration options, the example above includes on the required options. + +### Events Setup using Google Pub/Sub module + +Alternatively to using the HTTP endpoint you can use the Google Pub/Sub module, here's how. + +First we need to add the package: + +```bash title="from your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-events-backend-module-google-pubsub +``` + +Then we need to add it to your backend: + +```ts title="in packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-events-backend')); +backend.add(import('@backstage/plugin-events-backend-module-github')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-events-backend-module-google-pubsub')); +/* highlight-add-end */ +``` + +Finally you will want to configure it: + +```yaml title="app-config.yaml +events: + modules: + googlePubSub: + googlePubSubConsumingEventPublisher: + subscriptions: + # A unique key for your subscription, to be used in logging and metrics + mySubscription: + # The fully qualified name of the subscription + subscriptionName: 'projects/my-google-project/subscriptions/github-enterprise-events' + # The event system topic to transfer to. This can also be just a plain string + targetTopic: 'github.{{ event.attributes.x-github-event }}' +``` + +The [Google Pub/Sub module `README`](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-google-pubsub/README.md#configuration) has more details on the configuration options, the example above includes on the required options. ## Configuration @@ -148,7 +264,7 @@ If you do so, `default` will be used as provider ID. - **`catalogPath`** _(optional)_: Default: `/catalog-info.yaml`. Path where to look for `catalog-info.yaml` files. - You can use wildcards - `*` or `**` - to search the path and/or the filename. + You can use wildcards - `*`, `**` or a glob pattern supported by [`minimatch`](https://github.com/isaacs/minimatch) - to search the path and/or the filename. Wildcards cannot be used if the `validateLocationsExist` option is set to `true`. - **`filters`** _(optional)_: - **`branch`** _(optional)_: @@ -168,6 +284,8 @@ If you do so, `default` will be used as provider ID. If configured, all repositories _except_ those with one (or more) topics(s) present in the exclusion filter will be ingested. - **`visibility`** _(optional)_: An array of strings used to filter results based on their visibility. Available options are `private`, `internal`, `public`. If configured (non empty), only repositories with visibility present in the filter will be ingested + - **`allowArchived`** _(optional)_: + Whether to include archived repositories. Defaults to `false`. - **`host`** _(optional)_: The hostname of your GitHub Enterprise instance. It must match a host defined in [integrations.github](locations.md). - **`organization`**: diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 3d0e0a4221..473d408b0b 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -2,7 +2,6 @@ id: org title: GitHub Organizational Data sidebar_label: Org Data -# prettier-ignore description: Importing users and groups from a GitHub organization into Backstage --- @@ -169,6 +168,7 @@ const backend = createBackend(); backend.add(import('@backstage/plugin-catalog-backend')); +backend.add(import('@backstage/plugin-catalog-backend-module-github-org')); backend.add(githubOrgModule); backend.start(); diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index de35fbcc96..e0ededd1fa 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -2,7 +2,6 @@ id: discovery title: GitLab Discovery sidebar_label: Discovery -# prettier-ignore description: Automatically discovering catalog entities from repositories in GitLab --- diff --git a/docs/integrations/google-cloud-storage/locations.md b/docs/integrations/google-cloud-storage/locations.md index 1f0334ff17..99bba9feb7 100644 --- a/docs/integrations/google-cloud-storage/locations.md +++ b/docs/integrations/google-cloud-storage/locations.md @@ -2,7 +2,6 @@ id: locations sidebar_label: Locations title: Google Cloud Storage Locations -# prettier-ignore description: Setting up an integration with Google Cloud Storage --- diff --git a/docs/integrations/index.md b/docs/integrations/index.md index b090d6d77e..00463a810f 100644 --- a/docs/integrations/index.md +++ b/docs/integrations/index.md @@ -2,8 +2,7 @@ id: index title: Integrations sidebar_label: Overview -# prettier-ignore -description: Configuring Backstage to read or publish data with external providers using integrations +description: Configuring Backstage to read or publish data with external providers using integrations --- Integrations allow Backstage to read or publish data using external providers diff --git a/docs/integrations/ldap/org--old.md b/docs/integrations/ldap/org--old.md index 448e9bbed1..03862d8625 100644 --- a/docs/integrations/ldap/org--old.md +++ b/docs/integrations/ldap/org--old.md @@ -2,7 +2,6 @@ id: org--old title: LDAP Organizational Data sidebar_label: Org Data -# prettier-ignore description: Setting up ingestion of organizational data from LDAP --- diff --git a/docs/integrations/ldap/org.md b/docs/integrations/ldap/org.md index 9913cf0729..8adc81184a 100644 --- a/docs/integrations/ldap/org.md +++ b/docs/integrations/ldap/org.md @@ -2,7 +2,6 @@ id: org title: LDAP Organizational Data sidebar_label: Org Data -# prettier-ignore description: Setting up ingestion of organizational data from LDAP --- diff --git a/docs/notifications/index.md b/docs/notifications/index.md index 2af8879ac0..c31d2716e4 100644 --- a/docs/notifications/index.md +++ b/docs/notifications/index.md @@ -158,6 +158,21 @@ You can customize the origin names shown in the UI by passing an object where th Each notification processor will receive its own row in the settings page, where the user can enable or disable notifications from that processor. +### Automatic notification cleanup + +Notifications are deleted automatically after a certain period of time to prevent the database from growing indefinitely +and to keep the user interface clean. The default retention period is set to 1 year, meaning that notifications older +than that will be deleted automatically. + +The retention period can be configured by setting the `notifications.retention` in the `app-config.yaml` file. + +```yaml +notifications: + retention: 1y +``` + +If the retention is set to false, notifications will not be automatically deleted. + ## Additional info An example of a backend plugin sending notifications can be found in the [`@backstage/plugin-scaffolder-backend-module-notifications` package](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-notifications). 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/overview/adopting.md b/docs/overview/adopting.md index a6053d0f85..e924cfbeb0 100644 --- a/docs/overview/adopting.md +++ b/docs/overview/adopting.md @@ -1,7 +1,6 @@ --- id: adopting title: Strategies for adopting -# prettier-ignore description: Documentation on some general best practices that have been key to Backstage's success inside Spotify --- diff --git a/docs/overview/architecture-overview.md b/docs/overview/architecture-overview.md index d1be2bb2fc..035be95769 100644 --- a/docs/overview/architecture-overview.md +++ b/docs/overview/architecture-overview.md @@ -6,20 +6,11 @@ description: Documentation on Architecture overview ## Terminology -Backstage is constructed out of three parts. We separate Backstage in this way -because we see three groups of contributors that work with Backstage in three -different ways. +Backstage is organized into three main components, each catering to different groups of contributors who interact with Backstage in distinct ways. -- Core - Base functionality built by core developers in the open source project. -- App - The app is an instance of a Backstage app that is deployed and tweaked. - The app ties together core functionality with additional plugins. The app is - built and maintained by app developers, usually a productivity team within a - company. -- Plugins - Additional functionality to make your Backstage app useful for your - company. Plugins can be specific to a company or open sourced and reusable. At - Spotify we have over 100 plugins built by over 50 different teams. It has been - very powerful to get contributions from various infrastructure teams added - into a single unified developer experience. +- Core - This includes the base functionality developed by core developers within the open-source project. +- App - The app represents a deployed instance of a Backstage application, customized and maintained by app developers, typically a productivity team within an organization. It integrates core functionalities with additional plugins. +- Plugins - These provide additional functionalities to enhance the usefulness of your Backstage app. Plugins can be company-specific or open-sourced and reusable. At Spotify, we have over 100 plugins created by more than 50 different teams, significantly enriching the unified developer experience by incorporating contributions from various infrastructure teams. ## Overview @@ -276,14 +267,14 @@ require a database to work with. The Backstage backend and its built-in plugins are based on the [Knex](http://knexjs.org/) library, and set up a separate logical database per plugin. This gives great isolation and lets them perform migrations and evolve -separate from each other. +separately from each other. -The Knex library supports a multitude of databases, but Backstage is at the time -of writing tested primarily against two of them: SQLite, which is mainly used as +The Knex library supports a multitude of databases, but Backstage at this time +of writing is tested primarily against two of them: SQLite, which is mainly used as an in-memory mock/test database, and PostgreSQL, which is the preferred production database. Other databases such as the MySQL variants are reported to work but -[aren't tested as fully](https://github.com/backstage/backstage/issues/2460) +[aren't fully tested](https://github.com/backstage/backstage/issues/2460) yet. ## Cache diff --git a/docs/overview/background.md b/docs/overview/background.md index 5e59967b21..147fe8ce7b 100644 --- a/docs/overview/background.md +++ b/docs/overview/background.md @@ -1,7 +1,6 @@ --- id: background title: The Spotify Story -# prettier-ignore description: Backstage was born out of necessity at Spotify. We found that as we grew, our infrastructure was becoming more fragmented, our engineers less productive. --- diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index a61455a313..d9a03d9668 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -17,7 +17,7 @@ The initiatives listed below are planned for release within the next half-year, ### New Frontend System - Ready for Adoption -The [new fronted system](../frontend-system/index.md) still needs more work, and +The [new frontend system](../frontend-system/index.md) still needs more work, and the next milestone is to improve it to the point where there is enough confidence in the design to start encouraging adoption in the community. You can follow along with this work in the [meta issue](https://github.com/backstage/backstage/issues/19545). diff --git a/docs/overview/vision.md b/docs/overview/vision.md index f6d6af90dd..aab5eaa9b8 100644 --- a/docs/overview/vision.md +++ b/docs/overview/vision.md @@ -1,7 +1,6 @@ --- id: vision title: Vision -# prettier-ignore description: Goal is to provide engineers with the best developer experience in the world --- diff --git a/docs/overview/what-is-backstage.md b/docs/overview/what-is-backstage.md index ef452d5d15..fa1c75de44 100644 --- a/docs/overview/what-is-backstage.md +++ b/docs/overview/what-is-backstage.md @@ -1,7 +1,6 @@ --- id: what-is-backstage title: What is Backstage? -# prettier-ignore description: Backstage is an open source framework for building developer portals. Powered by a centralized software catalog, Backstage restores order to your microservices and infrastructure --- diff --git a/docs/permissions/frontend-integration.md b/docs/permissions/frontend-integration.md index 1e83f8ad7d..d5329eca96 100644 --- a/docs/permissions/frontend-integration.md +++ b/docs/permissions/frontend-integration.md @@ -4,6 +4,10 @@ title: Frontend Integration description: How to place your Backstage frontend components behind authorization --- +:::info +Starting with a [fresh Backstage installation](https://backstage.io/docs/getting-started/), the code example below is already implemented, so there are no actual code changes required. +::: + Now that we understand how to [author a permission policy](./writing-a-policy.md), let's consider cases where we'll need to supplement our policy with authorization checks on the frontend. In most cases, actual functionality that live within various plugins will already have been placed behind authorization by the plugin authors. The permission backend will use your permission policy to return an authorization result, and the plugin frontend will correspondingly show/hide/disable the relevant UI component. @@ -13,25 +17,20 @@ However, there are some cases where the integrator needs to supplement the polic If your Backstage permission policy may return a `DENY` for users requesting the `catalogEntityCreatePermission`, it may make sense, for example, to remove access to the `/catalog-import` page entirely: ```tsx title="packages/app/src/App.tsx" -/* highlight-add-start */ import { RequirePermission } from '@backstage/plugin-permission-react'; -import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common'; -/* highlight-add-end */ +import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; const routes = ( - {/* highlight-remove-next-line */} - } /> - {/* highlight-add-start */} } /> - {/* highlight-add-end */} {/* ... */} ); diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md index 6ebbdf77e4..afa56307ce 100644 --- a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md +++ b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md @@ -125,7 +125,10 @@ $ yarn workspace @internal/plugin-todo-list-backend add zod Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append the following code: ```typescript title="plugins/todo-list-backend/src/service/rules.ts" -import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; +import { + createPermissionResourceRef, + createPermissionRule, +} from '@backstage/plugin-permission-node'; import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; import { z } from 'zod'; import { Todo, TodoFilter } from './todos'; @@ -135,13 +138,13 @@ export const todoListPermissionResourceRef = createPermissionResourceRef< TodoFilter >().with({ pluginId: 'todolist', - type: TODO_LIST_RESOURCE_TYPE, + resourceType: TODO_LIST_RESOURCE_TYPE, }); export const isOwner = createPermissionRule({ name: 'IS_OWNER', description: 'Should allow only if the todo belongs to the user', - resourceType: todoListPermissionResourceRef, + resourceRef: todoListPermissionResourceRef, paramsSchema: z.object({ userId: z.string().describe('User ID to match on the resource'), }), diff --git a/docs/permissions/writing-a-policy.md b/docs/permissions/writing-a-policy.md index a479684287..576afcac21 100644 --- a/docs/permissions/writing-a-policy.md +++ b/docs/permissions/writing-a-policy.md @@ -92,6 +92,13 @@ import { catalogEntityDeletePermission, } from '@backstage/plugin-catalog-common/alpha'; /* highlight-add-end */ +import { + PermissionPolicy, + PolicyQuery, + /* highlight-add-next-line */ + PolicyQueryUser, +} from '@backstage/plugin-permission-node'; + class CustomPermissionPolicy implements PermissionPolicy { /* highlight-remove-next-line */ @@ -143,8 +150,8 @@ import { PolicyDecision, /* highlight-remove-next-line */ isPermission, - isResourcePermission, /* highlight-add-next-line */ + isResourcePermission, } from '@backstage/plugin-permission-common'; import { catalogConditions, @@ -155,6 +162,11 @@ import { catalogEntityDeletePermission, } from '@backstage/plugin-catalog-common/alpha'; /* highlight-remove-end */ +import { + PermissionPolicy, + PolicyQuery, + PolicyQueryUser, +} from '@backstage/plugin-permission-node'; class CustomPermissionPolicy implements PermissionPolicy { async handle( diff --git a/docs/plugins/analytics.md b/docs/plugins/analytics.md index 0a6bac81a0..f383e8881c 100644 --- a/docs/plugins/analytics.md +++ b/docs/plugins/analytics.md @@ -101,6 +101,25 @@ export const apis: AnyApiFactory[] = [ }, }), ]; + +// Or, when building for the new frontend system: +import { AnalyticsImplementationBlueprint } from '@backstage/frontend-plugin-api'; + +export const acmeAnalyticsImplementation = + AnalyticsImplementationBlueprint.make({ + name: 'acme', + params: define => + define({ + deps: {}, + factory() { + return { + captureEvent: event => { + window._AcmeAnalyticsQ.push(event); + }, + }; + }, + }), + }); ``` In reality, you would likely want to encapsulate instantiation logic and pull @@ -140,6 +159,19 @@ export const apis: AnyApiFactory[] = [ factory: ({ configApi }) => AcmeAnalytics.fromConfig(configApi), }), ]; + +// Or, when building for the new frontend system: +import { AnalyticsImplementationBlueprint } from '@backstage/frontend-plugin-api'; + +export const acmeAnalyticsImplementation = + AnalyticsImplementationBlueprint.make({ + name: 'acme', + params: define => + define({ + deps: { configApi: configApiRef }, + factory: ({ configApi }) => AcmeAnalytics.fromConfig(configApi), + }), + }); ``` If you are integrating with an analytics service (as opposed to an internal diff --git a/docs/plugins/call-existing-api.md b/docs/plugins/call-existing-api.md index 065427b9ee..35a6d8d599 100644 --- a/docs/plugins/call-existing-api.md +++ b/docs/plugins/call-existing-api.md @@ -1,7 +1,6 @@ --- id: call-existing-api title: Call Existing API -# prettier-ignore description: Describes the various options that Backstage frontend plugins have, in communicating with service APIs that already exist --- @@ -20,11 +19,18 @@ such as `axios`. Example: -```ts -// Inside your component -fetch('https://api.frobsco.com/v1/list') - .then(response => response.json()) - .then(payload => setFrobs(payload as Frob[])); +```ts title="plugins/my-awesome-plugin/src/components/AwesomeUsersTable.tsx" +import useAsync from 'react-use/esm/useAsync'; + +function AwesomeUsersTable() { + const { value, loading, error } = useAsync(async () => { + const response = await fetch('https://api.frobsco.com/v1/list'); + return response.json(); + }, []); + + + ... +} ``` Internally at Spotify, this has not been a very common choice. Third party APIs @@ -76,12 +82,26 @@ proxy: '/frobs': http://api.frobsco.com/v1 ``` -```ts -// Inside your component -const backendUrl = config.getString('backend.baseUrl'); -fetch(`${backendUrl}/api/proxy/frobs/list`) - .then(response => response.json()) - .then(payload => setFrobs(payload as Frob[])); +```tsx title="plugins/frobs-aggregator/src/components/FrobsAggregator.tsx" +import { + useApi, + discoveryApiRef, + fetchApiRef, +} from '@backstage/core-plugin-api'; +import useAsync from 'react-use/esm/useAsync'; + +function FrobsAggregator() { + const fetchApi = useApi(fetchApiRef); + const discoveryApi = useApi(discoveryApiRef); + + const { value, loading, error } = useAsync(async () => { + const baseUrl = await discoveryApi.getBaseUrl('proxy'); + const response = await fetchApi.fetch(`${baseUrl}/frobs`); + return response.json(); + }, [fetchApi, discoveryApi]); + + // ... +} ``` The proxy is powered by the `http-proxy-middleware` package. See @@ -112,28 +132,55 @@ system. The above mentioned proxy is actually one such plugin. If you were in need of a more involved integration than just direct access to the FrobsCo API, or if you needed to hold state, you may want to make such a plugin. -Example: +For example, assuming you have created a new backend plugin called +`frobs-aggregator`, you can add a new route like this: -```ts -// Inside your component -const backendUrl = config.getString('backend.baseUrl'); -fetch(`${backendUrl}/frobs-aggregator/summary`) - .then(response => response.json()) - .then(payload => setSummary(payload as FrobSummary)); +```tsx title="plugins/frobs-aggregator-backend/src/router.ts" +import Router from 'express-promise-router'; + +export async function createRouter() { + const router = Router(); + router.use(express.json()); + + /* highlight-add-start */ + router.get('/summary', async (req, res) => { + const agg = await Promise.all([ + fetch('https://api.frobsco.com/v1/list'), + fetch('http://flerps.partnercompany.com:8080/flerp-batch'), + database.currentThunk(), + ]).then(async ([frobs, flerps, thunk]) => { + return computeAggregate(await frobs.json(), await flerps.json(), thunk); + }); + res.status(200).json(agg); + }); + /* highlight-add-end */ +} ``` -```ts -// Inside a new frobs-aggregator backend plugin -router.use('/summary', async (req, res) => { - const agg = await Promise.all([ - fetch('https://api.frobsco.com/v1/list'), - fetch('http://flerps.partnercompany.com:8080/flerp-batch'), - database.currentThunk(), - ]).then(async ([frobs, flerps, thunk]) => { - return computeAggregate(await frobs.json(), await flerps.json(), thunk); - }); - res.status(200).json(agg); -}); +Then you can fetch the data from your frontend plugin like this: + +```tsx title="plugins/frobs-aggregator/src/components/FrobsAggregator.tsx" +import { + useApi, + discoveryApiRef, + fetchApiRef, +} from '@backstage/core-plugin-api'; +import useAsync from 'react-use/esm/useAsync'; + +function FrobsAggregator() { + const fetchApi = useApi(fetchApiRef); + const discoveryApi = useApi(discoveryApiRef); + + const { value, loading, error } = useAsync(async () => { + // highlight-next-line + const baseUrl = await discoveryApi.getBaseUrl('frobs-aggregator'); + // highlight-next-line + const response = await fetchApi.fetch(`${baseUrl}/summary`); + return response.json(); + }, [fetchApi, discoveryApi]); + + // ... +} ``` For a more detailed example, see @@ -166,8 +213,3 @@ There is a balance to strike regarding when to make an entirely separate backend for a purpose, and when to make a Backstage backend plugin that adapts something that already exists. General advice is not easy to give, but contact us on Discord if you have any questions, and we may be able to offer guidance. - -## Extending the GraphQL Model - -The extensible GraphQL backend layer is not built yet. This section will be -expanded when that happens. Stay tuned! diff --git a/docs/plugins/composability.md b/docs/plugins/composability.md index a0e0e2d5e6..d6c9abc98f 100644 --- a/docs/plugins/composability.md +++ b/docs/plugins/composability.md @@ -1,7 +1,6 @@ --- id: composability title: Composability System -# prettier-ignore description: Documentation for the Backstage plugin composability APIs. --- diff --git a/docs/plugins/observability.md b/docs/plugins/observability.md index c294001606..7803d90bd5 100644 --- a/docs/plugins/observability.md +++ b/docs/plugins/observability.md @@ -1,7 +1,6 @@ --- id: observability title: Observability -# prettier-ignore description: Adding Observability to Your Plugin --- diff --git a/docs/plugins/proxying.md b/docs/plugins/proxying.md index b32ed4fe12..5182ee7171 100644 --- a/docs/plugins/proxying.md +++ b/docs/plugins/proxying.md @@ -4,6 +4,8 @@ title: Proxying description: Documentation on Proxying --- +This page describes how to configure and use the built-in HTTP proxy functionality in your Backstage backend. + ## Overview The Backstage backend comes packaged with a basic HTTP proxy, that can aid in diff --git a/docs/references/glossary.md b/docs/references/glossary.md index 906260e710..c955d9a5ae 100644 --- a/docs/references/glossary.md +++ b/docs/references/glossary.md @@ -1,39 +1,48 @@ --- id: glossary title: Glossary -# prettier-ignore description: List of terms, abbreviations, and phrases used in Backstage, together with their explanations. --- ## Access Token -A [token](#token) that gives access to perform actions on behalf of a user. It will commonly have a short expiry time, and be limited to a set of [scopes](#scope). Part of the [OAuth](#oauth) protocol, see [their docs](https://oauth.net/2/access-tokens/) for more information. +A [token](#token) that represents the authorization to access resources on behalf of the end-user in a way that hides the user's actual identity. It will commonly have a short expiry time, and be limited to a set of [scopes](#scope). Part of the [OAuth](#oauth) protocol, see [their docs](https://oauth.net/2/access-tokens/) for details. ## Administrator -Someone responsible for installing and maintaining a Backstage [app](#app) for an organization. A [user role](#user-role). +Someone responsible for installing, configuring, and maintaining a Backstage [app](#app) for an organization. A [user role](#user-role). -## API (catalog plugin) +## API -An [entity](#entity) representing a schema that two [components](#component) use to communicate. See [the catalog docs](https://backstage.io/docs/features/software-catalog/system-model) for more information. +A software interface consisting of defined rules, protocols, and tools, that allows two applications or [components](#component-catalog-plugin) to communicate and exchange data or functionality. + +APIs are key abstractions that allow large software ecosystems to be scaled efficiently. Within the Backstage model, APIs are first-class citizens, and are the primary method for discovering existing functionality across the ecosystem. See [Backstage System Model](https://backstage.io/docs/features/software-catalog/system-model/#api). ## App -An installed instance of Backstage. An app can be local, intended for a single development group or individual developer, or organizational, for use by an entire enterprise. +An installed instance of Backstage. An app can be local, intended for a single developer or development group, or organizational, for use by an entire enterprise. -## Authorization Code +## Authentication -A type of [OAuth flow](#oauth) used by confidential and public clients to get an [access token](#access-token). See [the OAuth docs](https://oauth.net/2/grant-types/authorization-code/) for more details. +The process of verifying the identity of a user, system, or entity attempting to access a resource, system, or network. It answers the question: "Are you who you claim to be?" See [this Wikipedia article](https://en.wikipedia.org/wiki/Authentication) for more details. + +## Authorization + +The process of determining which operations an authenticated user or system is allowed to perform within a system, application, or resource. It comes after authentication, and answers the question: "What are you allowed to access or do?" It involves assigning specific privileges, access rights, or roles to the user. See [this Wikipedia article](https://en.wikipedia.org/wiki/Authorization) for more details. + +## Authorization Code Grant + +See [Code Grant](#code-grant). ## Backstage -1. An open source framework for creating and deploying [developer portals](#developer-portal), originally created at Spotify. Backstage is an incubation-stage open source project of the [Cloud Native Computing Foundation](#cloud-native-computing-foundation). +1. An open source framework for creating and deploying [developer portals](#developer-portal), originally created at Spotify. Backstage is an incubation-stage open source project of the [Cloud Native Computing Foundation](#cloud-native-computing-foundation-aka-cncf). 2. [The Backstage Framework](#backstage-framework). ## Backstage Framework -The actual framework that Backstage [plugins](#plugin) sit on. This spans both the frontend and the backend, and includes core functionality such as declarative integration, config reading, database management, and many more. +The actual framework that Backstage [plugins](#plugin) sit on. The framework spans the frontend and backend, and includes core functionality such as declarative integration, config reading, database management, and much more. ## Bundle @@ -43,47 +52,49 @@ The actual framework that Backstage [plugins](#plugin) sit on. This spans both t ## Catalog -1. The core Backstage plugin that handle ingestion and display of your organizations software products. - -2. An organization's portfolio of software products managed in Backstage. +See [Software Catalog](#software-catalog). ## Cloud Native Computing -A set of technologies that "empower organizations to build and run scalable applications in modern, dynamic environments such as public, private, and hybrid clouds. Containers, service meshes, microservices, immutable infrastructure, and declarative APIs exemplify this approach." ([CNCF Cloud Native Definition v1.0](https://github.com/cncf/toc/blob/main/DEFINITION.md)). +A set of technologies that "empower organizations to build and run scalable applications in modern, dynamic environments such as public, private, and hybrid clouds. Containers, service meshes, microservices, immutable infrastructure, and declarative APIs exemplify this approach." ([CNCF Cloud Native Definition v1.1](https://github.com/cncf/toc/blob/main/DEFINITION.md)). -## Cloud Native Computing Foundation +## Cloud Native Computing Foundation (aka CNCF) -A foundation dedicated to the promotion and advancement of [Cloud Native Computing](#Cloud-Native-Computing). The mission of the Cloud Native Computing Foundation (CNCF) is "to make cloud native computing ubiquitous" ([CNCF Charter](https://github.com/cncf/foundation/blob/main/charter.md)). +A foundation dedicated to the promotion and advancement of [Cloud Native Computing](#cloud-native-computing). The mission of the Cloud Native Computing Foundation (CNCF) is "to make cloud native computing ubiquitous" ([CNCF Charter](https://github.com/cncf/foundation/blob/main/charter.md)). CNCF is part of the [Linux Foundation](https://www.linuxfoundation.org/). -## CNCF - -Cloud Native Computing Foundation. - ## Code Grant -[OAuth](#oauth) flow where the client receives an [authorization code](#code) that is passed to the backend to be exchanged for an [access token](#access-token) and possibly a [refresh token](#refresh-token). +In the context of [OAuth 2.0](https://oauth.net/2/), refers to the process where an application receives an [authorization code](#authorization-code-grant) after a user grants it permission to access their resources. This code is then exchanged for an [access token](#access-token) and possibly a [refresh token](#refresh-token), which the application uses to access the user's data on their behalf. It's a secure way for applications to access protected resources without directly handling the user's credentials. See the [OAuth docs](https://oauth.net/2/grant-types/authorization-code/) for details. ## Collator (search plugin) -A transformer that takes streams of [documents](#documents) and outputs searchable texts. They're usually responsible for the data transformation and definition and collection process for specific [documents](#documents). +A specialized component of Backstage that's responsible for ordering or indexing data according to a specific set of rules. In Backstage search, collators are used to define what can be searched. Specifically, they're readable object streams of documents that contain a minimum set of fields (including document title, location, and text), but can contain any other fields as defined by the collator itself. A single collator is responsible for defining and collecting documents of a specific type. + +Backstage includes "default" collators for Catalog and TechDocs that you can use out-of-the-box to start searching across Backstage quickly. More collators are available from the Backstage community. Learn more at [Collators](https://backstage.io/docs/features/search/collators). ## Component (catalog plugin) -A software product that is managed in the Backstage [Software Catalog](#software-catalog). A component can be a service, website, library, data pipeline, or any other piece of software managed as a single project. See [the catalog docs](https://backstage.io/docs/features/software-catalog/system-model) for more information. +1. A modular, independent, reusable software-based unit that encapsulates specific functionality. It has well-defined interfaces, explicitly specified dependencies, and is designed to be integrated with other components to build larger software systems. + +2. A software product that Backstage manages in the [Software Catalog](#software-catalog). A component can be a service, website, library, data pipeline, or any other software artifact that's managed as a single entity. + +A Backstage component can implement [API](#api)s for other components to consume. In turn, it might consume APIs implemented by other components, or directly depend on components or resources that are attached to it at runtime. + +See the [Backstage System Model](https://backstage.io/docs/features/software-catalog/system-model/#component-catalog-plugin). ## Condition (permission plugin) -A mapping from a given entity to criteria a user must fulfill to perform an action on that entity. Examples include `isOwner`, `hasRole`, etc. +A criterion, evaluated on an entity, that a user must meet to be granted permission to perform an action on that entity within a permission plugin. Examples might include `isOwner` or `hasRole`. ## Conditional Decision (permission plugin) -A type of [decision](#policy-decision-permission-plugin) that allows for per-user evaluation of [conditions](#condition-permission-plugin) against a [resource](#resource-permission-plugin). See [Conditional Decisions](../permissions/concepts.md#conditional-decisions) +A [decision](#policy-decision-permission-plugin) mechanism, common in permission plugins, that evaluates real-time conditions (predicates) on a per-user basis to determine access or actions against a [resource](#resource-permission-plugin). This enables highly granular, context-aware control. See [Conditional Decisions](https://backstage.io/docs/permissions/concepts/#conditional-decisions). ## Contributor -A volunteer who helps to improve an OSS product such as Backstage. This volunteer effort includes coding, testing, technical writing, user support, and other work. A [user role](#user-role). +A volunteer who helps to improve an open source product such as Backstage. This volunteer effort includes coding, testing, technical writing, user support, and other work. A [user role](#user-role). ## Declarative Integration @@ -91,75 +102,107 @@ A new paradigm for Backstage frontend plugins, allowing definition in config fil ## Decorator (search plugin) -A transform stream that allows you to add additional information to [documents](#document-search-plugin). +A transform stream that operates during the indexing process, positioned between a [Collator](#collator-search-plugin) (read stream) and an Indexer (write stream). Document Decorators are used to modify documents by adding, removing, or filtering metadata, or even injecting new documents, as they are being prepared for the search index. + +To illustrate, while the [Software Catalog](#software-catalog) understands software entities, it might not track their usage or quality. A decorator can add this extra metadata, which can then be used to bias search results or enhance the search experience within your Backstage instance. ## Deployment Artifacts -An executable or package file with all of the necessary information required to deploy the application at runtime. Deployment artifacts can be hosted on [package registries](#package-registry). +An executable or [package](#package) file with all of the necessary information required to deploy at runtime. Deployment artifacts can be hosted on [package registries](#package-registry). ## Developer -1. Someone who writes code and develops software. +1. A professional who designs, builds, tests, deploys, and maintains software applications and systems. Developers essentially turn ideas or requirements into functional digital products. -2. A [user role](#user-role) defined as someone who uses a Backstage [app](#app). Might or might not actually be a software developer. +2. A [user role](#user-role) defined as someone who creates, modifies, or uses a Backstage [app](#app). Might or might not actually be a software developer. ## Developer Portal -A centralized system comprising a user interface and database used to facilitate and document all the software projects within an organization. Backstage is both a developer portal and (by virtue of being based on plugins) a framework for creating developer portals. +1. A centralized, self-service interface providing developers with all the necessary resources, tools, documentation, and information to effectively build, integrate, deploy, and manage software products within an organization. + +2. Backstage is a specific example of a developer portal, designed as a centralized system with a user interface and database to streamline development and maintenance of an organization's software projects. It features a robust [Software Catalog](#software-catalog) that centralizes and organizes access to the organization's services, websites, mobile features, libraries, and other software components. Backstage also includes [Software Templates](#software-templates-aka-scaffolder) that simplify the creation of new projects and components. + +Backstage is both a developer portal and a plugin-based framework for creating new custom developer portals. ## Document (search plugin) -An abstract concept representing something that can be found by searching for it. A document can represent a software entity, a TechDocs page, etc. Documents are made up of metadata fields, at a minimum -- a title, text, and location (as in a URL). +1. A piece of information or data that is recorded on some medium for the purpose of retention and conveyance. + +2. An abstract representation of information or data that can be discovered and retrieved. For search purposes, a document might represent a software entity, a TechDocs page, or any other type of data that is indexed. In Backstage, a document is structured with metadata fields that must include at least a title, a body (containing its core text content), and a location (such as a URL pointing to its source). ## Domain -An area that relates systems or entities to a business unit. See [the catalog docs](https://backstage.io/docs/features/software-catalog/system-model) for more information. +1. A collection of systems that share terminology, domain models, metrics, KPIs, business purpose, or documentation; that is, it forms a bounded context. + +2. Typically the unique, human-readable address that is used to identify websites, email servers, and other resources on the internet, such as `google.com` or `example.store`. More narrowly, it can refer to the Top-Level Domain (TLD), which is the part of a web address after the last dot, such as `.com` or `.org`; the country code, such as `.us` or `.uk`; or the sponsored TLD such as `.gov` or `.edu`. + +3. An area that relates systems or entities to a business unit. See [Domain](https://backstage.io/docs/features/software-catalog/system-model/#domain) in the [System Model](https://backstage.io/docs/features/software-catalog/system-model/). ## Entity -What is cataloged in the Backstage Software Catalog. An entity is identified by a unique combination of [kind](#Kind), [namespace](#Namespace), and name. See [the catalog docs](https://backstage.io/docs/features/software-catalog/system-model) for more information. +1. Something that exists as a separate and distinct unit. Its existence can be real or abstract, physical or conceptual, persistent or ephemeral. + +2. What is cataloged in the Backstage [Software Catalog](#software-catalog). An entity is identified by a unique combination of [kind](#kind), [namespace](#namespace-catalog-plugin), and name. See [The Life of an Entity](https://backstage.io/docs/features/software-catalog/life-of-an-entity) for related key concepts and how it's handled. ## Evaluator -Someone who assesses whether Backstage is a suitable solution for their organization. The only [user role](#user-role) with a pre-deployment [use case](#use-case). +Someone who assesses whether Backstage is a suitable solution for their organization and needs. The only [user role](#user-role) with a pre-deployment [use case](#use-case). ## ID Token -A [JWT](#jwt) used to prove a user's identity, containing for example the user's email. Part of [OpenID Connect](#openid-connect). +A security token used in authentication processes, primarily defined by the [OpenID Connect (OIDC) standard](#openid-connect-aka-oidc). Its main purpose is to prove that a user has been successfully authenticated by an identity provider. See [JSON Web Token](#json-web-token-aka-jwt). ## Index (search plugin) -An index is a collection of [documents](#documents) of a given type. +A collection of [documents](#document-search-plugin) that a search engine uses to quickly locate relevant information within your developer portal. It's essentially a lookup table that allows you to quickly find documents without having to scan the entire dataset. ## Indexer (search plugin) -A write stream of [documents](#documents). +In Backstage's Search Platform, the term _indexer_ isn't a component or concept that developers typically interact with. Instead, it refers to the overall process or the _write stream_ within the Search backend that takes processed documents and adds them to the chosen search engine's index. + +To understand _indexer_ in Backstage, it's helpful to understand the flow of data into the Search system: + +1. A [Collator](#collator-search-plugin) reads raw data from a specific source and transforms it into a stream of [documents](#document-search-plugin), where each document is formatted in a way the search platform understands (e.g., having title, text, location fields, and potentially other metadata). + +2. As the stream of documents flows from the collator, a [decorator](#decorator-search-plugin) can intercept them. The decorator optionally adds, removes, or modifies fields within the documents to enrich them with context that the original collator might not have (such as adding ownership information from the Catalog to TechDocs documents). + +3. After optional decoration, the stream of finalized search documents is then written to the search engine's index. This is what the term _indexer_ implicitly refers to. It's the functionality that takes these structured documents and inserts them into the chosen search engine (like Lunr, PostgreSQL, or Elasticsearch) so they become searchable. + +The `plugin-search-backend-node` package in Backstage is responsible for orchestrating this entire indexing process. It manages the collators, decorators, the connection to the specific search engine, and the scheduling of when these indexing tasks run. ## Integrator -Someone who develops one or more plugins that enable Backstage to interoperate with another software system. A [user role](#user-role). +1. Someone who develops one or more plugins that enable Backstage to interoperate with another software system. A [user role](#user-role). -## JWT +2. May refer to someone who develops software that integrates with Backstage. -JSON Web Token. +## JSON Web Token (aka JWT) -A popular JSON based token format that is commonly encrypted and/or signed, see [the Wikipedia article](https://en.wikipedia.org/wiki/JSON_Web_Token) for more details. +A popular, compact, and self-contained open standard for securely transmitting information between parties as a JSON object. It is commonly used to verify a user's identity ([authentication](#authentication)) and permissions ([authorization](#authorization)). Each JWT consists of a header, payload, and digital signature separated by dots. JWTs are often encrypted, and are always signed for integrity. + +This standard is a key component of [OpenID Connect](#openid-connect-aka-oidc). For more details, see [this Wikipedia article](https://en.wikipedia.org/wiki/JSON_Web_Token). ## Kind -Classification of an [entity](#Entity) in the Backstage Software Catalog, for example _service_, _database_, and _team_. +Classification of an [entity](#entity) in the Backstage Software Catalog, for example _service_, _database_, or _team_. An element of the [kind|namespace|name triplet](#kind-namespace-name-triplet) that is an important concept for uniqueness. -## Kubernetes (CNCF Project) +## Kind|namespace|name triplet -An open-source system for automating deployment, scaling, and management of containerized applications. +The primary reference for [Software Catalog](#software-catalog) entities. It is human-readable and should be unique across your Backstage instance. ## Kubernetes (Backstage plugin) A core Backstage plugin enabling a service owner-focused view of Kubernetes resources. +## Kubernetes (CNCF Project) + +Kubernetes (K8s) is an open-source platform that automates the deployment, scaling, and management of containerized applications. Originally developed at Google to manage its production workloads, it was later offered to the [Cloud Native Computing Foundation (CNCF)](#cloud-native-computing-foundation-aka-cncf) for open-source support and maintenance. + +According to its [official website](https://kubernetes.io/), Kubernetes automatically handles rollouts, rollbacks, and load balancing. It mounts storage systems, dynamically allocates containers based on resource requirements and other constraints, manages batch execution, restarts crashed containers, and more. For additional information, see the [Kubernetes article](https://en.wikipedia.org/wiki/Kubernetes) on Wikipedia. + ## Local Package -One of the [packages](#package) within a [monorepo](#monorepo). These package may or may not also be published to a [package registry](#package-registry). +One of the [packages](#package) within a [monorepo](#monorepo). A package may or may not also be published to a [package registry](#package-registry). ## Monorepo @@ -167,25 +210,31 @@ One of the [packages](#package) within a [monorepo](#monorepo). These package ma 2. A project layout that consists of multiple [packages](#package) within a single project, where packages are able to have local dependencies on each other. Often enabled through tooling such as [lerna](https://lerna.js.org/) and [yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/) +## Name + +To be completed. An element of the [kind|namespace|name triplet](#kind-namespace-name-triplet) that is an important concept for uniqueness. + ## Namespace (catalog plugin) -An optional attribute that can be used to organize [entities](#entity). - -## Objective - -A high level goal of a [user role](#User-Role) interacting with Backstage. Some goals of the _administrator_ user role, for example, are to maintain an instance ("app") of Backstage; to add and update functionality via plugins; and to troubleshoot issues. +An optional attribute that can be used to organize [entities](#entity). An element of the [kind|namespace|name triplet](#kind-namespace-name-triplet) that is an important concept for uniqueness. ## OAuth -Refers to: OAuth 2.0, a standard protocol for authorization. See [oauth.net/2/](https://oauth.net/2/). +Refers to [OAuth 2.0](https://oauth.net/2/), an industry-standard authorization framework that allows a website or application (the "client") to access protected resources (like user data or functionality) on a different service (the "resource server") on behalf of a user (the "resource owner"), without sharing the user's login credentials with the client application. + +See [OAuth 2.0 explained](https://connect2id.com/learn/oauth-2) for descriptions of OAuth 2.0 and [OIDC](#openid-connect-aka-oidc) (which is built on top of OAuth 2.0). + +## Objective + +A high-level goal of a [user role](#user-role) interacting with Backstage. For example, some objectives of the [administrator](#administrator) user role are to maintain an instance ([app](#app)) of Backstage, to add and update functionality via plugins, and to troubleshoot issues. ## Offline Access -[OAuth](#oauth) flow that results in both a refresh token and [access token](#access-token), where the refresh token has a long expiration or never expires, and can be used to request more access tokens in the future. This lets the user go "offline" with respect to the token issuer, but still be able to request more tokens at a later time without further direct interaction for the user. +[OAuth 2.0](https://oauth.net/2/) flow that results in both a [refresh token](#refresh-token) and an [access token](#access-token), where the refresh token has a long expiration or never expires, and can be used to request more access tokens in the future. This lets the user go "offline" with respect to the token issuer, but still be able to request more tokens at a later time without further direct interaction for the user. -## OpenID Connect +## OpenID Connect (aka OIDC) -A layer on top of [OAuth](#oauth) which standardises authentication. See [the Wikipedia article](https://en.wikipedia.org/wiki/OpenID_Connect) for more details. +A layer on top of [OAuth 2.0](https://oauth.net/2/) which standardises authentication. See [this Wikipedia article](https://en.wikipedia.org/wiki/OpenID_Connect) for details. ## OSS @@ -193,7 +242,9 @@ Open source software. ## Package -A package in the Node.js ecosystem, often published to a [package registry](#package-registry). +1. A bundled collection of executable files, libraries, configuration files, metadata, and other necessary components that allow a piece of software (or a group of related software) to be easily installed, managed, and used. It's a standardized format for distributing software that simplifies installation by ensuring all dependencies are included or easily resolvable, provides integrity checks, and enables easy upgrades or removal. + +2. A package in the Node.js ecosystem, often published to a [package registry](#package-registry). ## Package Registry @@ -201,15 +252,23 @@ A service that hosts [packages](#package). The most prominent example is [NPM](h ## Package Role -The declared role of a package, see [package roles](../tooling/cli/02-build-system.md#package-roles). +The declared role of a package, see [package roles](https://backstage.io/docs/tooling/cli/build-system#package-roles). -## Permission (core Backstage plugin) +## Permission -A core Backstage plugin and framework that allows restriction of actions to specific users. See [their docs](https://backstage.io/docs/permissions/overview) for more information. +Specific rules or settings that define the level of access and types of actions that a user, group, or system process is allowed to perform on a particular resource. Permissions are a core component of authorization that determine "what you can do" after you've been authenticated. Permissions are properties of objects or resources. Closely related to [Privilege](#privileges) (the terms are often used interchangeably). ## Permission (permission plugin) -A restriction on any action that a user can perform against a specific [resource](#resource-permission-plugin) or set of resources. See [the permission framework docs](../permissions/concepts.md#permission) for more details. +1. A core Backstage plugin and framework that allows actions to be limited to specific users. + +2. A rule that determines whether a user is authorized to access a specific [resource](#resource-permission-plugin) or set of resources if a specified set of conditions exists. + +By default, Backstage endpoints are unprotected; any user can perform any action on any resource. The Permission framework addresses this by enabling integrators to configure rules that specify precisely which users can access which resources and actions. Within this framework, a _permission_ is a uniquely named set of rules (or _conditions_) and the results to return based on their evaluation. + +An example might be a permission rule that returns `false` if an entity is not part of a system, where the entity and system are configured for the rule. (See [Defining custom permission rules](https://backstage.io/docs/permissions/custom-rules) for the example.) + +See the Permissions [Overview](https://backstage.io/docs/permissions/overview) and [Concepts](https://backstage.io/docs/permissions/concepts/) for details. ## Persona (use cases) @@ -217,98 +276,141 @@ Alternative term for a [User Role](#user-role). ## Plugin -A module in Backstage that adds a feature. All functionality outside of [the Backstage framework](#backstage-framework), even the core features, are implemented as plugins. +The fundamental building block that adds specific features, functionalities, and integrations to your developer portal. All functionality outside of [the Backstage framework](#backstage-framework), even core features, are implemented as plugins. This modular architecture is a key differentiator of Backstage, making it highly customizable and extensible. + +A plugin is a self-contained unit of code designed to perform a specific task or integrate with an external system. Instead of a monolithic application, Backstage is assembled from a collection of these independent plugins. + +A single logical plugin often consists of both a frontend (UI) component and a backend (API) component. + +- Frontend plugins are typically written in React and TypeScript to provide the user interface elements such as pages, cards, sidebar items, and dashboards that developers interact with. +- Backend plugin are written in Node.js and TypeScript, and handle data fetching, business logic, integrations with external services (like Git providers, CI/CD systems, and cloud platforms), and expose APIs for the frontend to consume. + +There are different types of plugins (run `yarn new` to see them). The current list includes: + +- `plugin` - A new frontend plugin +- `backend-plugin` - A new backend plugin +- `backend-module` - A new backend module +- `web-library` - A new web-library package +- `plugin-common` - A new isomorphic common plugin package +- `plugin-node` - A new `Node.js` library plugin package +- `plugin-react` - A new web library plugin package + +See [Introduction to Plugins](https://backstage.io/docs/plugins/) for how to create a plugin, suggest a plugin, and integrate a plugin into the Software Catalog. See [Create a Backstage Plugin](https://backstage.io/docs/plugins/create-a-plugin/) for how to create a frontend plugin. + +See [Plugin directory](https://backstage.io/plugins/) for a list of community driven plugins, although there is a community plugins repository that contains many more that might not be in that page. ## Policy (permission plugin) -A construct that takes in a Backstage user and a [permission](#permission-permission-plugin) and returns a [policy decision](#policy-decision-permission-plugin). +The central component of the Backstage permission framework that dictates who can do what, to which resources, and under what conditions. + +Backstage policies are implemented as functions or sets of rules that receive a request (containing information about a user and a desired permission/action on a resource) and return an authorization decision (allow, deny, or a conditional decision). Policies are independent of the [authorization](#authorization) model, such as role-based access control or attribute-based access control. See [Policy](https://backstage.io/docs/permissions/concepts/#policy-permission-plugin). + +Policies are defined in Typescript code in the permissions framework. See [Writing a permission policy](https://backstage.io/docs/permissions/writing-a-policy) for examples. ## Policy Decision (permission plugin) -A specific response to a user's request to perform an action on a list of [resources](#resource-permission-plugin). Can be either `Approve`, `Deny` or [`Conditional`](#conditional-decision-permission-plugin). +A specific response to a user's request to perform an action on a list of [resources](#resource-permission-plugin). Can be either `Approve`, `Deny` or `Conditional`. In Backstage, policies are only responsible for decisions regarding whether requests can be approved; the requestors (typically the backend) are responsible for enforcing those decisions. See [Policy decision versus enforcement](https://backstage.io/docs/permissions/concepts/#policy-decision-versus-enforcement). ## Popup -A separate browser window opened on top of the previous one. +A separate browser window that opens on top of the previous one. + +## Privileges + +The term _privilege_ is not a defined abstraction in the context of the Backstage permission framework's core concepts. Instead, Backstage's authorization model is built around [permissions](#permission), [policies](#policy-permission-plugin), and [conditions](#condition-permission-plugin) to determine what actions a user can take on a resource. If you encounter _privilege_ in a Backstage context, it's likely being used in a more general, colloquial sense of "level of access" or referring to a concept. + +Outside of Backstage contexts, _privilege_ typically refers to specific rules or settings that define what a user or process is allowed to do within the system, often relating to system-wide operations or capabilities. Privilege is closely related to [Permission](#permission); the terms are often used interchangeably. ## Procedure (use cases) -A set of actions that accomplish a goal, usually as part of a [use case](#Use-Case). A procedure can be high-level, containing other procedures, or can be as simple as a single [task](#Task). +A set of actions that accomplish a goal, usually as part of a [use case](#use-case). A procedure can be high-level, containing other procedures, or can be as simple as a single [task](#task-use-cases). ## Query Translators (search plugin) -An abstraction layer between a search engine and the [Backstage Search](#search) backend. Allows for translation into queries against your search engine. +An abstraction layer between a search engine and the [Backstage Search](#search) backend. It is an optional backend component that takes an abstract search query---which includes search terms, filters, and desired document types from a Backstage search client—--and transforms it into a concrete query that's optimized for a specific [Search Engine](#search-engine-backstage-search) used by Backstage. + +This translation layer is crucial because it enables Backstage components to utilize the distinct features of individual search engines (like Elasticsearch or Solr) while maintaining loose coupling from their detailed interfaces. Although Backstage's pre-packaged Search Engines come with simple, built-in translators, you can implement custom Query Translators to significantly enhance and tune search results for your organization's unique context. ## Refresh token -A special token that an [OAuth](#oauth) client can use to get a new [access token](#access-token) when the latter expires. - -https://oauth.net/2/refresh-tokens/ +A special token that an [OAuth](#oauth) client can use to get a new [access token](#access-token) when the latter expires. See [OAuth Refresh Tokens](https://oauth.net/2/refresh-tokens/). ## Resource (catalog plugin) -An [entity](#entity) that represents a piece of physical or virtual infrastructure, for example a database, required by a component. See [the catalog docs](https://backstage.io/docs/features/software-catalog/system-model) for more information. +An [entity](#entity) that represents a piece of physical or virtual infrastructure, such as a database, that's required by a component. See the [System Model](https://backstage.io/docs/features/software-catalog/system-model). ## Resource (permission plugin) A representation of an object that a user interacts with and that can be permissioned. Not to be confused with [Software Catalog resources](#resource-catalog-plugin). -## Rule (permission plugin) - -A predicate-based control that taps into a [resource](#resource-permission-plugin)'s data. - ## Role -See [User Role](#User-Role). +See [User Role](#user-role). + +## Rule (permission plugin) + +A specific type of dynamic access control associated with a [resource](#resource-permission-plugin) it protects. A simple example might be "the current user can access Resource X if Condition Y is true." The catalog plugin defines a resource for catalog entities and rules to check if an entity has a given annotation. ## Scaffolder -Known as [Software Templates](#software-templates). +Another name for [Software Templates](#software-templates-aka-scaffolder). (The term comes from the use of Software Templates as _scaffolds_ for building new components and projects.) ## Scope -A string that describes a certain type of access that can be granted to a user using OAuth, usually in conjunction with [access tokens](#access-token). +A string that describes a certain type of access that can be granted to a user using [OAuth](#oauth), usually in conjunction with [access tokens](#access-token). + +In [OAuth](#oauth), access control is managed through _scopes_ that define specific permissions granted to the application. An OAuth service can issue Access Tokens that are tied to particular sets of scopes, such as viewing profile information, or reading or writing user data. The format and handling of scopes are unique to each OAuth provider, with available scopes typically detailed in their authentication solution's documentation (see [https://developers.google.com/identity/protocols/oauth2/scopes](https://developers.google.com/identity/protocols/oauth2/scopes) for an example). + +For more information about scopes and OAuth, see [OAuth and OpenID Connect](https://backstage.io/docs/auth/oauth/). ## Search -A Backstage plugin that provides a framework for searching a Backstage [app](#app), including the [Software Catalog](#Software-Catalog) and [TechDocs](#TechDocs). A core feature of Backstage. +A Backstage plugin that provides a framework for searching a Backstage [app](#app), including the [Software Catalog](#software-catalog) and [TechDocs](#techdocs). A core feature of Backstage. ## Search Engine (Backstage search) -Existing search technology that [Backstage Search](#search) can take advantage of through its modular design. Lunr is the default search in Backstage Search. +Existing search technology that [Backstage Search](#search) can take advantage of through its modular design. Lunr is the default search in Backstage Search. Can be one of the search engines that are pre-packaged with Backstage or chosen specifically for your instance. ## Software Catalog -A Backstage plugin that provides a framework to keep track of ownership and metadata for any number and type of software [components](#component). A core feature of Backstage. +1. A centralized system that keeps track of ownership and metadata for all software in your ecosystem (services, websites, libraries, data pipelines, etc.). The catalog is built around metadata YAML files that are stored with the code, and are harvested and visualized in Backstage. -## Software Templates +2. The Backstage plugin that implements the Software Catalog feature. A core feature of Backstage. -A Backstage plugin with which to create [components](#component) in Backstage. A core feature of Backstage. Also known as the scaffolder. +The Software Catalog is a core feature of Backstage. See [Backstage Software Catalog](https://backstage.io/docs/next/features/software-catalog/) for an overview, the life of an entity in the catalog, how to configure the catalog, its architecture and high-level design, how to configure and customize it, and its API. The overview describes how the catalog works, how to add components to it, how to find software in it, and more. -## Software Template +## Software Templates (aka Scaffolder) -A "skeleton" software project created and managed in the Backstage Software Templates tool. +1. A "skeleton" software project created and managed in the Backstage Software Templates tool. + +2. A Backstage plugin for creating [components](#component-catalog-plugin) in Backstage. By default, it has the ability to load skeletons of code, template in some variables, and then publish the template to some locations like GitHub or GitLab. + +Software Templates is a core feature of Backstage. It's also known as the [Scaffolder](#scaffolder) for its utility in building new software components and projects. See [Backstage Software Templates](https://backstage.io/docs/features/software-templates/) for an overview, how to configure it, add your own templates, write a template, test it, and more. The overview describes such information as how to get started, choose a template, verify your inputs, run the template, and see a demo. ## System (catalog plugin) -A system is a collection of [entities](#entity) that cooperate to perform a function. A system generally provides one or a few public APIs and consists of a handful of components, resources and private APIs. See [the catalog docs](https://backstage.io/docs/features/software-catalog/system-model) for more information. +A collection of [entities](#entity) that cooperate to perform a function. A system generally consists of a handful of components, resources, and private or public APIs. See [the catalog docs](https://backstage.io/docs/features/software-catalog/system-model). ## Task (use cases) -A low-level step-by-step [Procedure](#Procedure). +A low-level step-by-step [procedure](#procedure-use-cases) to achieve a particular result. ## TechDocs -A documentation solution that manages and generates a technical documentation from Markdown files stored with software component code. A core feature of Backstage. +A documentation solution for generating and managing technical documentation in Markdown files that are stored with software component code. A core feature of Backstage. See [TechDocs Documentation](https://backstage.io/docs/features/techdocs/). ## Token -A string containing information. +A string containing information. The format and content depend on its purpose and how it's used. ## Use Case -A purpose for which a [user role](#User-Role) interacts with Backstage. Related to [Objective](#objective): An objective is _what_ the user wants to do; a use case is _how_ the user does it. +A purpose for which a [user role](#user-role) interacts with Backstage. Related to [Objective](#objective): An objective is _what_ a user wants to accomplish; a use case is _how_ the user does it. + +## User + +Any consumer of Backstage or Backstage products/applications. This includes individuals like employees and contractors, as well as software that interacts with the backend through an API or acts as a proxy for a person by operating the user interface. ## User Role -A class of Backstage user for purposes of analyzing [use cases](#use-case). One of: evaluator; administrator; developer; integrator; and contributor. +A class of Backstage user who has permissions to use Backstage for a particular set of [use cases](#use-case). One of: [evaluator](#evaluator); [administrator](#administrator); [developer](#developer); [integrator](#integrator); and [contributor](#contributor). diff --git a/docs/releases/v1.39.0-changelog.md b/docs/releases/v1.39.0-changelog.md new file mode 100644 index 0000000000..feae6629d4 --- /dev/null +++ b/docs/releases/v1.39.0-changelog.md @@ -0,0 +1,2850 @@ +# Release v1.39.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.39.0](https://backstage.github.io/upgrade-helper/?to=1.39.0) + +## @backstage/plugin-catalog-backend@2.0.0 + +### Major Changes + +- 90ab044: **BREAKING**: Removed all deprecated exports, and removed support for the old backend system. + + It also removes the `CodeOwnersProcessor` from the default set of processors, because it is expensive to run and has vague semantics. You need to update your backend to add it to the `catalogProcessingExtensionPoint` if you wish to continue using it. + + The following removed exports are available from `@backstage/plugin-catalog-node`: + + - `locationSpecToMetadataName` + - `locationSpecToLocationEntity` + - `processingResult` + - `EntitiesSearchFilter` + - `EntityFilter` + - `DeferredEntity` + - `EntityRelationSpec` + - `CatalogProcessor` + - `CatalogProcessorParser` + - `CatalogProcessorCache` + - `CatalogProcessorEmit` + - `CatalogProcessorLocationResult` + - `CatalogProcessorEntityResult` + - `CatalogProcessorRelationResult` + - `CatalogProcessorErrorResult` + - `CatalogProcessorRefreshKeysResult` + - `CatalogProcessorResult` + - `EntityProvider` + - `EntityProviderConnection` + - `EntityProviderMutation` + - `AnalyzeOptions` + - `LocationAnalyzer` + - `ScmLocationAnalyzer` + - `PlaceholderResolver` + - `PlaceholderResolverParams` + - `PlaceholderResolverRead` + - `PlaceholderResolverResolveUrl` + - `parseEntityYaml` + + The following removed exports are available from `@backstage/plugin-catalog-common`: + + - `LocationSpec` + - `AnalyzeLocationRequest` + - `AnalyzeLocationResponse` + - `AnalyzeLocationExistingEntity` + - `AnalyzeLocationGenerateEntity` + - `AnalyzeLocationEntityField` + + The following removed exports are instead implemented in the new backend system by `@backstage/plugin-search-backend-module-catalog`: + + - `defaultCatalogCollatorEntityTransformer` + - `CatalogCollatorEntityTransformer` + - `DefaultCatalogCollator` + + The following exports are removed without a direct replacement: + + - `DefaultCatalogCollatorFactory` + - `DefaultCatalogCollatorFactoryOptions` + - `LocationEntityProcessor` + - `LocationEntityProcessorOptions` + - `CatalogBuilder` + - `CatalogEnvironment` + - `CatalogPermissionRuleInput` + - `CatalogProcessingEngine` + - `createRandomProcessingInterval` + - `ProcessingIntervalFunction` + +### Minor Changes + +- 6c9b88e: **BREAKING ALPHA**: You can no longer import the catalog plugin from the `/alpha` export; please use the regular root default export instead. +- d88b922: Adds the ability to disable the default entity processors using a new boolean app config item `catalog.disableDefaultProcessors`. + +### Patch Changes + +- 0e710fc: This patch addresses an issue identified in Backstage when configured with a MySQL database. If an entity of type location + (e..all.yaml) has more than 70 referenced entities, clicking "Refresh" does not update the referenced entities as expected. This occurs because the TEXT type in MySQL has a limit of 65,535 bytes, which is insufficient to store all the referenced entities, causing the refresh operation to fail. +- 8e0f15f: "Added a note clarifying that `entity-fetch` audit events are not visible by default in the logs and are only displayed when the log severity level is adjusted." +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/catalog-client@1.10.0 + - @backstage/backend-openapi-utils@0.5.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/backend-defaults@0.10.0 + +### Minor Changes + +- d385854: **BREAKING**: The `DefaultSchedulerService` constructor options now requires `RootLifecycleService`, `HttpRouterService`, and `PluginMetadataService` fields. + + The scheduler will register a REST API for listing and triggering tasks. Please see [the scheduler documentation](https://backstage.io/docs/backend-system/core-services/scheduler) for more details about this API. + +### Patch Changes + +- 1e06afd: `GithubUrlReader`'s search detects glob-patterns supported by `minimatch`, instead of just detecting + `*` and `?` characters. + + For example, this allows to search for patterns like `{C,c}atalog-info.yaml`. + +- acea1d4: update documentation + +- 72d019d: Removed various typos + +- c6bc67d: Added Valkey support alongside Redis in backend-defaults cache clients, using the new Keyv Valkey package. Also extended backend-test-utils to support Valkey in tests. + +- 36f77e9: Bug fix: Pass user provided token through to gitlab url resolvers + +- 0e7a640: The `GithubUrlReader` will now use the token from `options` when fetching repo details + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-app-api@1.2.3 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/config-loader@1.10.1 + - @backstage/integration-aws-node@0.1.16 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/backend-dynamic-feature-service@0.7.0 + +### Minor Changes + +- 10f693c: **BREAKING** Removed support for the legacy backend, please migrate to the new backend system + +### Patch Changes + +- 72d019d: Fixed various typos. + `FrontendRemoteResolver`'s misspelled `getAdditionaRemoteInfo` has been deprecated. Use the correct spelling `getAdditionalRemoteInfo` instead. +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.0 + - @backstage/backend-defaults@0.10.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config-loader@1.10.1 + - @backstage/backend-openapi-utils@0.5.3 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.33 + - @backstage/plugin-events-backend@0.5.2 + - @backstage/plugin-events-node@0.4.11 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/backend-test-utils@1.5.0 + +### Minor Changes + +- b3832d1: Add a functional `mockServices.events()` +- c6bc67d: Added Valkey support alongside Redis in backend-defaults cache clients, using the new Keyv Valkey package. Also extended backend-test-utils to support Valkey in tests. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0 + - @backstage/backend-app-api@1.2.3 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/canon@0.4.0 + +### Minor Changes + +- ea36f74: **Breaking Change** Icons on Button and IconButton now need to be imported and placed like this: <Button iconStart={} /> +- ccb1fc6: We are modifying the way we treat custom render using 'useRender()' under the hood from BaseUI. +- 04a65c6: The icon prop in TextField now accept a ReactNode instead of an icon name. We also updated the icon sizes for each input sizes. + +### Patch Changes + +- c8f32db: Use correct colour token for TextField clear button icon, prevent layout shift whenever it is hidden or shown and properly size focus area around it. Also stop leading icon shrinking when used together with clear button. +- e996368: Fix Canon missing dependencies +- 720033c: For improved a11y, clicking a Select component label now focuses the Select trigger element, and the TextField component's label is now styled to indicate it's interactive. +- 6189bfd: Added new icon and onClear props to the TextField to make it easier to accessorize inputs. +- 9510105: Add new Tabs component to Canon +- 97b25a1: Pin version of @base-ui-components/react. +- 206ffbe: Fixed an issue with Canon's DataTable.Pagination component showing the wrong number for the "to" count. +- 72d019d: Removed various typos +- 4551fb7: Update Menu component in Canon to make the UI more condensed. We are also adding a new Combobox option for nested navigation. +- 185d3a8: Use the Field component from Base UI within the TextField. +- 1ea1db0: Add new truncate prop to Text and Heading components in Canon. + +## @backstage/catalog-client@1.10.0 + +### Minor Changes + +- 1a003ff: Add `getLocations` method to `CatalogApi` and `CatalogClient`. This method calls the [`GET /locations`](https://backstage.io/docs/features/software-catalog/software-catalog-api/#get-locations) endpoint from the catalog backend. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + +## @backstage/core-app-api@1.17.0 + +### Minor Changes + +- 1e0230e: Support custom `AuthConnector` for `OAuth2`. + + A user can pass their own `AuthConnector` implementation in `OAuth2` constructor. + In which case the session manager will use that instead of the `DefaultAuthConnector` to interact with the + authentication provider. + + A custom `AuthConnector` may call the authentication provider from the front-end, store and retrieve tokens + in the session storage, for example, and otherwise send custom requests to the authentication provider and + handle its responses. + + Note, that if the custom `AuthConnector` transforms scopes returned from the authentication provider, + the transformation must be the same as `OAuth2CreateOptions#scopeTransform` passed to `OAuth2` constructor. + See creating `DefaultAuthConnector` in `OAuth2#create(...)` for an example. + +### Patch Changes + +- 73f6cc3: Updated `I18nextTranslationApi` to support interpolation of JSX elements. +- cc119b2: Fixed an issue causing `OAuthRequestDialog` to re-render on mount. +- Updated dependencies + - @backstage/core-plugin-api@1.10.7 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/integration@1.17.0 + +### Minor Changes + +- d945206: Added support for federated credentials using managed identities in the Azure DevOps integration. Federated credentials are only available for Azure DevOps organizations that have been configured to use Entra ID for authentication. + + ```diff + integrations: + azure: + - host: dev.azure.com + credentials: + + - clientId: ${APP_REGISTRATION_CLIENT_ID} + + managedIdentityClientId: system-assigned + + tenantId: ${AZURE_TENANT_ID} + ``` + + This also adds support for automatically using the system-assigned managed identity of an Azure resource by specifying `system-assigned` as the client ID of the managed identity. + + ```diff + integrations: + azure: + - host: dev.azure.com + credentials: + - - clientId: ${AZURE_CLIENT_ID} + + - clientId: system-assigned + ``` + +- f134cea: Implement Edit URL feature for Gerrit 3.9+. + + It's possible to disable the edit url by adding the `disableEditUrl: true` config in the Gerrit integration. + +### Patch Changes + +- f3381d3: Added missing `organizations` property to `azure` section in `config.d.ts` file +- acea1d4: update documentation +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend@0.25.0 + +### Minor Changes + +- 57221d9: **BREAKING**: Removed support for the old backend system, and removed all deprecated exports. + + If you were using one of the deprecated imports from this package, you will have to follow the instructions in their respective deprecation notices before upgrading. Most of the general utilities are available from `@backstage/plugin-auth-node`, and the specific auth providers are available from dedicated packages such as for example `@backstage/plugin-auth-backend-module-github-provider`. See [the auth docs](https://backstage.io/docs/auth/) for specific instructions. + +### Patch Changes + +- 0d606ac: Added the configuration flag `auth.omitIdentityTokenOwnershipClaim` that causes issued user tokens to no longer contain the `ent` claim that represents the ownership references of the user. + + The benefit of this new flag is that issued user tokens will be much smaller in + size, but they will no longer be self-contained. This means that any consumers + of the token that require access to the ownership claims now need to call the + `/api/auth/v1/userinfo` endpoint instead. Within the Backstage ecosystem this is + done automatically, as clients will still receive the full set of claims during + authentication, while plugin backends will need to use the `UserInfoService` + which already calls the user info endpoint if necessary. + + When enabling this flag, it is important that any custom sign-in resolvers directly return the result of the sign-in method. For example, the following would not work: + + ```ts + const { token } = await ctx.issueToken({ + claims: { sub: entityRef, ent: [entityRef] }, + }); + return { token }; // WARNING: This will not work with the flag enabled + ``` + + Instead, the sign-in resolver should directly return the result: + + ```ts + return ctx.issueToken({ + claims: { sub: entityRef, ent: [entityRef] }, + }); + ``` + +- 72d019d: Removed various typos + +- ab53e6f: Added support for the new `dangerousEntityRefFallback` option for `signInWithCatalogUser` in `AuthResolverContext`. + +- b128ed9: The `static` key store now issues tokens with the same structure as other key stores. Tokens now include the `typ` field in the header and the `uip` (user identity proof) in the payload. + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-bitbucket-cloud-common@0.3.0 + +### Minor Changes + +- 5850717: Update Bitbucket Cloud schema and models. + + The latest schema was fetched from Bitbucket Cloud and stored locally. + Based on the updated schema, the models got regenerated. + + **BREAKING:** + + Due to the schema changes, the model update includes one breaking change: + + - `Account.username` was removed. + + Additionally, there were a couple of compatible changes including the addition of + `BaseCommit.committer` and others. + +### Patch Changes + +- 57ad208: Add support for `repo:updated` events as `Events.RepoUpdatedEvent`. +- Updated dependencies + - @backstage/integration@1.17.0 + +## @backstage/plugin-catalog@1.30.0 + +### Minor Changes + +- 970cb48: Show the pagination text for the offset-paginated catalog table, and remove the pagination bar from the top of the `CatalogTable` when pagination is enabled. + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 2ddbc50: A new `filter` parameter has been added to `EntityContextMenuItemBlueprint` to make it easier to configure which entities a menu item should appear for. The `filter` parameter is a function which accepts an entity and returns a boolean. +- bf85d37: Fix for missing `routeRef` when using `core-plugin-api` in a dialog context +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-search-react@1.9.0 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-scaffolder-common@1.5.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.0 + +### Minor Changes + +- e4dabc6: add new gitea provider module + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-github@0.9.0 + +### Minor Changes + +- ff335e5: **BREAKING** The `GithubLocationAnalyzer` now requires the `AuthService` and the `CatalogService` when being constructed and the `TokenManger` has been removed. + +### Patch Changes + +- ee9f59f: Added filter to include archived repositories +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-backend@2.0.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.0 + +### Minor Changes + +- 10f693c: **BREAKING** Removed support for the legacy backend, please [migrate to the new backend system](https://github.com/backstage/backstage/tree/v1.38.0/plugins/catalog-backend-module-incremental-ingestion#installation). Also, if you were importing from the `/alpha` export of this package, you should remove the `/alpha` part. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.0 + - @backstage/backend-defaults@0.10.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-catalog-backend-module-msgraph@0.7.0 + +### Minor Changes + +- 20c1ea7: Add new `userGroupMember.path`, `user.path` and, `group.path` option to each query type to allow more complex msgraph queries + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.0 + +### Minor Changes + +- f453d5c: **BREAKING** Removed support for the legacy backend and removed references to `@backstage/backend-common`, please [migrate to the new backend system](https://backstage.io/docs/backend-system/building-plugins-and-modules/migrating) + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.8 + +## @backstage/plugin-catalog-import@0.13.0 + +### Minor Changes + +- e2fd549: **BREAKING**: `generateStepper` and `defaultGenerateStepper` now require a translation argument to be passed through for supporting translations. + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 66a1140: Add i18n support for `catalog-import` plugin. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/integration@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-node@1.17.0 + +### Minor Changes + +- a459f17: Added `parseEntityYaml` from `@backstage/plugin-catalog-backend`, to make it more easily usable by custom plugins and modules +- 1a003ff: Add `getLocations` method to `CatalogApi` and `CatalogClient`. This method calls the [`GET /locations`](https://backstage.io/docs/features/software-catalog/software-catalog-api/#get-locations) endpoint from the catalog backend. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/catalog-client@1.10.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-react@1.18.0 + +### Minor Changes + +- d47aaa3: Added EntityOrderFilter to sort entities by different fields/columns. This new filter allows users to specify the order in which entities are displayed in the catalog. + + Example usage: + + ```ts + import { + EntityOrderFilter, + useEntityList, + } from '@backstage/plugin-catalog-react'; + // ... + const { updateFilters } = useEntityList(); + + // ... + updateFilters({ + order: new EntityOrderFilter([ + { + field: 'metadata.name', + order: 'desc', + }, + ]), + }); + ``` + +- 1a003ff: Add `getLocations` method to `CatalogApi` and `CatalogClient`. This method calls the [`GET /locations`](https://backstage.io/docs/features/software-catalog/software-catalog-api/#get-locations) endpoint from the catalog backend. + +### Patch Changes + +- 2ddbc50: A new `filter` parameter has been added to `EntityContextMenuItemBlueprint` to make it easier to configure which entities a menu item should appear for. The `filter` parameter is a function which accepts an entity and returns a boolean. +- 6d7f0d5: Fixed an issue causing entities of kind user and group to be empty when an owner was selected +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/frontend-test-utils@0.3.2 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-events-backend-module-github@0.4.0 + +### Minor Changes + +- ae249fc: **BREAKING**: Removed the `createGithubSignatureValidator` export. + + Added support webhook validation based on `integrations.github.[].apps.[].webhookSecret`. + +### Patch Changes + +- c7ef81c: Correct README installation instructions. +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.0 + +### Minor Changes + +- af853ef: Added a module that is able to transfer messages from Google Pub/Sub subscriptions into the Backstage events system. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-kubernetes-node@0.3.0 + +### Minor Changes + +- 9cdf001: **BREAKING**: The `PinnipedHelper` class now expects a regular `LoggerService` instance from the new backend system, instead of a Winston logger. + +### Patch Changes + +- 216c6b2: Updated dependency `@kubernetes/client-node` to `1.1.2`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/types@1.2.1 + +## @backstage/plugin-permission-backend@0.7.0 + +### Minor Changes + +- 4da2965: Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests. +- cf8fd51: **BREAKING** Removed support for the legacy backend system, please [migrate to the new backend system](https://backstage.io/docs/backend-system/building-backends/migrating) + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-permission-common@0.9.0 + +### Minor Changes + +- 4da2965: Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests. + +### Patch Changes + +- 37328b1: Fixed an issue causing `PermissionClient` to throw an error when authorizing basic permissions with the `permission.EXPERIMENTAL_enableBatchedRequests` config enabled. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-permission-node@0.10.0 + +### Minor Changes + +- 4da2965: Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder@1.31.0 + +### Minor Changes + +- 4235e87: add templating extensions page + +### Patch Changes + +- 92c3658: Full support in EntityPicker (and derivatives) for default EntityPresentationApi +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- d7da01d: Fix EntityPicker field to render description as markdown, matching other form components in the system. +- 36ae651: Fixing a bug where the name for `templatingExtensions` was incorrectly set to `templateExtensions` +- 72d019d: Removed various typos +- a274e0a: Migrate custom fields to new schema factory function; + standardize field descriptions to prefer ui:description and present consistently, + utilizing ScaffolderField component where possible. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/integration@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/plugin-scaffolder-react@1.16.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/plugin-scaffolder-backend@1.33.0 + +### Minor Changes + +- 587cb05: Added `workspace:template` and `workspace:template:file` actions to complement respective `fetch:*` actions + +### Patch Changes + +- eb39388: Fixed bug in fs:delete that prevented wildcard patterns from matching paths starting with "." +- 36ae651: Fixing a bug where the name for `templatingExtensions` was incorrectly set to `templateExtensions` +- 72d019d: Removed various typos +- ec42f8e: Generating new tokens on each Scaffolder Task Retry +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.0 + - @backstage/plugin-scaffolder-backend-module-github@0.7.1 + - @backstage/backend-defaults@0.10.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.10 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.9 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.9 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.9 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8 + - @backstage/plugin-events-node@0.4.11 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/plugin-scaffolder-react@1.16.0 + +### Minor Changes + +- 4235e87: add templating extensions page + +### Patch Changes + +- 36ae651: Fixing a bug where the name for `templatingExtensions` was incorrectly set to `templateExtensions` +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/plugin-search-react@1.9.0 + +### Minor Changes + +- 611c941: Allow search filters to provide labels and values separately, and not only values + +### Patch Changes + +- 2c76614: Fix memoization of `filterValue` in `SearchFilter.Autocomplete` to prevent unintended resets +- fa48594: search plugin support i18n +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/app-defaults@1.6.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.6 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-permission-react@0.4.34 + +## @backstage/backend-app-api@1.2.3 + +### Patch Changes + +- 729a7d6: Added a configuration to permit backend plugin module failures on startup: + + ```yaml + backend: + ... + startup: + plugins: + plugin-x: + modules: + module-y: + onPluginModuleBootFailure: continue + ``` + + This configuration permits `plugin-x` with `module-y` to fail on startup. Omitting the + `onPluginModuleBootFailure` configuration matches the previous behavior, wherein any + individual plugin module failure is forwarded to the plugin and aborts backend startup. + + The default can also be changed, so that continuing on failure is the default + unless otherwise specified: + + ```yaml + backend: + startup: + default: + onPluginModuleBootFailure: continue + plugins: + catalog: + modules: + github: + onPluginModuleBootFailure: abort + ``` + +- 72d019d: Removed various typos + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/backend-openapi-utils@0.5.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/backend-plugin-api@1.3.1 + +### Patch Changes + +- acea1d4: update documentation +- 72d019d: Removed various typos +- d385854: Minor doc comment update +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/config@1.3.2 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/catalog-model@1.7.4 + +### Patch Changes + +- ed4e625: Added support for icons containing colons +- Updated dependencies + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/cli@0.32.1 + +### Patch Changes + +- 674def9: fix: enable `lazyCompilation` and `refreshOptions` for rspack +- d649df0: Internal code cleanup +- c2cae47: Add missing modules to the Backstage CLI alpha entrypoint. +- 3f45861: Add a warning for React 17 deprecation that triggers when frontend packages and plugins start. +- 9aaec54: Internal refactor of opaque type handling. +- 9285385: Added `info` object to the context of the alpha CLI. +- 6cc9507: Updated dependency `@octokit/request` to `^8.0.0`. +- 5cd3c54: Updated dependency `react-refresh` to `^0.17.0`. +- 72d019d: Removed various typos +- 19a4e7c: Internal refactor to move things closer to home +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/catalog-model@1.7.4 + - @backstage/release-manifests@0.0.13 + - @backstage/config-loader@1.10.1 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.1.10 + - @backstage/types@1.2.1 + +## @backstage/config-loader@1.10.1 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/core-compat-api@0.4.2 + +### Patch Changes + +- 173db8f: Updates to use the new `plugin` property of `AppNodeSpec`. +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 6eedf46: Updated dependency `@backstage-community/plugin-puppetdb` to `^0.6.0`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-components@0.17.2 + +### Patch Changes + +- e0d1025: `LogViewer` now supports a `textWrap` prop that wraps log lines to the next line for overflowing content instead of using horizontal scroll +- bb84534: Fix the hidden sidebar's sub-menu when the sidebar is scrollable +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/theme@0.6.6 + - @backstage/core-plugin-api@1.10.7 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-plugin-api@1.10.7 + +### Patch Changes + +- 73f6cc3: The `TranslationApi` now supports interpolation of JSX elements by passing them directly as values to the translation function. If any of the provided interpolation values are JSX elements, the translation function will return a JSX element instead of a string. +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/create-app@0.6.2 + +### Patch Changes + +- 8448948: Removed `lerna-debug.log*` pattern from `.gitignore` as Lerna was removed from the package in version `@backstage/create-app@0.5.19`. +- ce2cf32: Bumped create-app version. +- 125d096: Bumped create-app version. +- 1c0cb7b: Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.15 + +## @backstage/dev-utils@1.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.6 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/app-defaults@1.6.2 + - @backstage/integration-react@1.2.7 + +## @backstage/frontend-app-api@0.11.2 + +### Patch Changes + +- 173db8f: Updates to use the new `plugin` property of `AppNodeSpec`. +- 1f04491: Added the ability to ignore unknown extension config by passing `{ flags: { allowUnknownExtensionConfig: true } }` to `createSpecializedApp`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-defaults@0.2.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-defaults@0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/frontend-app-api@0.11.2 + - @backstage/plugin-app@0.1.9 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/frontend-dynamic-feature-loader@0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/config@1.3.2 + +## @backstage/frontend-plugin-api@0.10.2 + +### Patch Changes + +- 173db8f: The `source` property of `AppNodeSpec` has been renamed to `plugin`. The old property has been deprecated and will be removed in a future release. + +- fb58f20: The `id` option of `createFrontendPlugin` has been renamed to `pluginId` in order to better align with similar APIs in the frontend and backend systems. + + The old `id` option is deprecated and will be removed in a future release. + +- 72d019d: Removed various typos + +- Updated dependencies + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-test-utils@0.3.2 + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/test-utils@1.7.8 + - @backstage/frontend-app-api@0.11.2 + - @backstage/plugin-app@0.1.9 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/integration-aws-node@0.1.16 + +### Patch Changes + +- db4630e: Fixed bug in DefaultAwsCredentialsManager where aws.mainAccount.region has no effect on the STS region used for account ID lookup during credential provider lookup when falling back to the main account, and it does not default to us-east-1 +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/integration-react@1.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/config@1.3.2 + +## @backstage/release-manifests@0.0.13 + +### Patch Changes + +- 163f3da: This expands the configurability of `release-manifests` to pave the road for more configuration options in the `cli`. + + Specifically it allows the specification of mirrored, proxied, or air-gapped hosts when upgrading across releases when + working in restricted or heavily governed development environments (common in large enterprises and government + entities). + +## @backstage/repo-tools@0.13.3 + +### Patch Changes + +- b229476: Support passing additional properties to OpenAPI server generator +- 659f2ce: Updated dependency `typedoc` to `^0.28.0`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config-loader@1.10.1 + - @backstage/cli-node@0.2.13 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## @techdocs/cli@1.9.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-techdocs-node@1.13.3 + - @backstage/config@1.3.2 + - @backstage/cli-common@0.1.15 + +## @backstage/test-utils@1.7.8 + +### Patch Changes + +- b573341: Added support for interpolating JSX elements with the `MockTranslationApi`. +- Updated dependencies + - @backstage/theme@0.6.6 + - @backstage/core-app-api@1.17.0 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/types@1.2.1 + +## @backstage/theme@0.6.6 + +### Patch Changes + +- 1b14572: Show arrow when MuiTableSortLabel receives focus + +## @backstage/plugin-api-docs@0.12.7 + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-catalog@1.30.0 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-app@0.1.9 + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/types@1.2.1 + +## @backstage/plugin-app-backend@0.5.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config-loader@1.10.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.33 + +## @backstage/plugin-app-node@0.1.33 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config-loader@1.10.1 + +## @backstage/plugin-app-visualizer@0.1.19 + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.8 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.3.3 + +### Patch Changes + +- 5cdfe05: Added missing types package +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.8 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.3 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## @backstage/plugin-auth-node@0.6.3 + +### Patch Changes + +- 332e934: Added the `identity` property to `BackstageSignInResult`. + + The `prepareBackstageIdentityResponse` function will now also forward the `identity` to the response if present in the provided sign-in result. + +- ab53e6f: Added a new `dangerousEntityRefFallback` option to the `signInWithCatalogUser` method in `AuthResolverContext`. The option will cause the provided entity reference to be used as a fallback in case the user is not found in the catalog. It is up to the caller to provide the fallback entity reference. + + Auth providers that include pre-defined sign-in resolvers are encouraged to define a flag named `dangerouslyAllowSignInWithoutUserInCatalog` in their config, which in turn enables use of the `dangerousEntityRefFallback` option. For example: + + ```ts + export const usernameMatchingUserEntityName = createSignInResolverFactory({ + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { + return async ( + info: SignInInfo>, + ctx, + ) => { + const { username } = info.result.fullProfile; + if (!username) { + throw new Error('User profile does not contain a username'); + } + + return ctx.signInWithCatalogUser( + { entityRef: { name: username } }, + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: username } } + : undefined, + }, + ); + }; + }, + }); + ``` + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-react@0.1.15 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog-backend-module-aws@0.4.11 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-defaults@0.10.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/integration-aws-node@0.1.16 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-azure@0.3.5 + +### Patch Changes + +- be82d83: visualstudio.com domains are now supported along with dev.azure.com +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.2 + +### Patch Changes + +- d385854: Do not swallow errors; instead allow them to bubble up to the task scheduler for better tracking and logging. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/backend-openapi-utils@0.5.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.4.8 + +### Patch Changes + +- 3bce578: Support Bitbucket Cloud's `repo:updated` events at `BitbucketCloudEntityProvider`. + + To make use of the new event type, you have to configure your webhook or add a new ones + that delivers this event type to Backstage similar to `repo:push` before. + + Only `repo:updated` events that modify a repository's URL (e.g., due to a name change) + will cause changes (removing the "old", adding the "new" repository). + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/config@1.3.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.10 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/plugin-catalog-backend-module-github@0.9.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-catalog-backend-module-gitlab@0.6.6 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-defaults@0.10.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.6.6 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-catalog-backend-module-ldap@0.11.5 + +### Patch Changes + +- e253d1d: Improves error reporting for missing metadata.name in LDAP catalog provider. +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @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.10 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @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.8 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/plugin-catalog-common@1.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-catalog-graph@0.4.19 + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 72d019d: Removed various typos +- 1307f00: Fix rendering of missing relations +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.17 + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.0 + +## @backstage/plugin-config-schema@0.1.68 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-devtools@0.1.27 + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/core-compat-api@0.4.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.16 + +## @backstage/plugin-devtools-backend@0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/config-loader@1.10.1 + - @backstage/config@1.3.2 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-devtools-common@0.1.16 + +## @backstage/plugin-devtools-common@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-events-backend@0.5.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/backend-openapi-utils@0.5.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-events-backend-module-azure@0.2.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-events-backend-module-gerrit@0.2.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-events-backend-module-gitlab@0.3.1 + +### Patch Changes + +- a820df1: Adds support for `object_kind` field with priority over `event_name` on Gitlab webhook event types +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-events-backend-test-utils@0.1.44 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-events-node@0.4.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-gateway-backend@1.0.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + +## @backstage/plugin-home@0.8.8 + +### Patch Changes + +- f7ca0fe: Added the Catalog presentation API to the HomePageRecentlyVisited and HomePageTopVisited components + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. + +- eddd96c: Added optional title prop to `customHomePageGrid` + +- 16eb4bf: Export ContentModal from `@backstage/plugin-home-react` so people can use this in other scenarios. + Renamed `CatalogReactComponentsNameToClassKey` to `PluginHomeComponentsNameToClassKey` in `overridableComponents.ts` + + Made QuickStartCard `docsLinkTitle` prop more flexible to allow for any React.JSX.Element instead of just a string. + Added QuickStartCard prop `additionalContent` which can eventually replace the prop `video`. + +- 2c1761f: Added a `variant` prop to the `WelcomeTitle` component making it work with the Customizable Home page feature. Adding it like this `` to the list of items under `CustomHomepageGrid` will allow it to render with a size that works well. + +- 195323f: Export root page route from the home plugin to enable adding links/nav to it from outside the plugin + +- 72d019d: Removed various typos + +- d710d74: docs: Update default for `preventCollision` prop + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-home-react@0.1.26 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + +## @backstage/plugin-home-react@0.1.26 + +### Patch Changes + +- 16eb4bf: Export ContentModal from `@backstage/plugin-home-react` so people can use this in other scenarios. + Renamed `CatalogReactComponentsNameToClassKey` to `PluginHomeComponentsNameToClassKey` in `overridableComponents.ts` + + Made QuickStartCard `docsLinkTitle` prop more flexible to allow for any React.JSX.Element instead of just a string. + Added QuickStartCard prop `additionalContent` which can eventually replace the prop `video`. + +- Updated dependencies + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + +## @backstage/plugin-kubernetes@0.12.7 + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 216c6b2: Updated dependency `@kubernetes/client-node` to `1.1.2`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/plugin-kubernetes-react@0.5.7 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-permission-react@0.4.34 + +## @backstage/plugin-kubernetes-backend@0.19.6 + +### Patch Changes + +- f6f692c: Changed logging of cluster details to debug to minimise log clutter. +- 216c6b2: Updated dependency `@kubernetes/client-node` to `1.1.2`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/plugin-kubernetes-node@0.3.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/integration-aws-node@0.1.16 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-kubernetes-cluster@0.0.25 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.5.7 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-permission-react@0.4.34 + +## @backstage/plugin-kubernetes-common@0.9.5 + +### Patch Changes + +- 216c6b2: Updated dependency `@kubernetes/client-node` to `1.1.2`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-kubernetes-react@0.5.7 + +### Patch Changes + +- 599c89e: Add headlamp formatter +- 216c6b2: Updated dependency `@kubernetes/client-node` to `1.1.2`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-notifications@0.5.5 + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/core-components@0.17.2 + - @backstage/core-compat-api@0.4.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-signals-react@0.0.13 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + +## @backstage/plugin-notifications-backend@0.5.6 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/plugin-notifications-node@0.2.15 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-signals-node@0.1.20 + +## @backstage/plugin-notifications-backend-module-email@0.3.9 + +### Patch Changes + +- aa3a63a: Enable the ability to configure the endpoint for the SES connection used in the notifications email module. This enables the configuration of alternate endpoints as required, for example for local testing or alternative stacks. +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/integration-aws-node@0.1.16 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/plugin-notifications-node@0.2.15 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + +## @backstage/plugin-notifications-backend-module-slack@0.1.1 + +### Patch Changes + +- 4f10768: Fix slack notification processor to handle a notification with an empty description + +- f6480c7: Fix dataloader caching, and use the proper catalog service ref + +- a1c5bbb: Added email-based Slack User ID lookup if `metadata.annotations.slack.com/bot-notify` is missing from user entity + +- e099d0a: Notifications which mention user entity refs are now replaced with Slack compatible mentions. + + Example: `Welcome <@user:default/billy>!` -> `Welcome <@U123456890>!` + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-notifications-node@0.2.15 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + +## @backstage/plugin-notifications-node@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/catalog-client@1.10.0 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-signals-node@0.1.20 + +## @backstage/plugin-org@0.6.39 + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. + +- 02e8af1: Enhance user profile card configuration: + + - Added a new optional `maxRelations` numerical configuration that controls over how many user groups are shown directly on the profile card: + - If the setting is omitted, all relations will be shown. + - If `maxRelations` is set to `0`, the list of user groups is not displayed. + - If `maxRelations` is set to a positive number, up to that many groups are displayed. + - If the user belongs to more groups than the specified limit, a clickable link appears that opens a dialog showing all associated user groups. + - A complementary boolean configuration, `hideIcons`, was added to optionally hide the visual icons associated with each group in the displayed list. + - Usage example: + ```yaml + # Example in app-config.yaml + app: + extensions: + - entity-card:org/user-profile: + config: + maxRelations: 5 # (optional) Show up to 5 groups on the card + hideIcons: true # (optional) Hide the group icons + ``` + +- 08ba448: display entity-ref in GroupProfileCard so groups can easily determine their Group ID + +- 18e84c9: Fixed missing spec.profile field on MyGroupsSidebarItem.tsx so the group spec.profile.displayName is shown on the sidebar" + +- 72d019d: Removed various typos + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-org-react@0.1.38 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + +## @backstage/plugin-permission-react@0.4.34 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/config@1.3.2 + +## @backstage/plugin-proxy-backend@0.6.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/types@1.2.1 + - @backstage/plugin-proxy-node@0.1.4 + +## @backstage/plugin-proxy-node@0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.10 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-defaults@0.10.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.9 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-github@0.7.1 + +### Patch Changes + +- 6579c2c: Use action context logger in Octokit client +- 72d019d: Removed various typos +- b2b654c: Added optional assignees parameter to `publish:github:pull-request` action +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.1 + +### Patch Changes + +- d15355c: If the commit action is not `create` log a more appropriate error message to the end user advising that the files they're trying to modify might not exist +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.10 + +### Patch Changes + +- b60253d: Change notification send scaffolder action to use native zod schemas +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/plugin-notifications-node@0.2.15 + - @backstage/plugin-notifications-common@0.0.8 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.9 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/plugin-scaffolder-node-test-utils@0.2.2 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-common@1.5.11 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-node@0.8.2 + +### Patch Changes + +- 16e2e9c: trim leading and trailing slashes from parseRepoUrl query parameters +- 72d019d: Removed various typos +- ec42f8e: Generating new tokens on each Scaffolder Task Retry +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/plugin-scaffolder-node-test-utils@0.2.2 + +### Patch Changes + +- b27c48d: Include optional `user` in `createMockActionContext` +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/backend-test-utils@1.5.0 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/types@1.2.1 + +## @backstage/plugin-search@1.4.26 + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- fa48594: search plugin support i18n +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-search-react@1.9.0 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend@2.0.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/backend-openapi-utils@0.5.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-catalog@0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.2 + +### Patch Changes + +- 01f772c: Fixed an issue where the `search.elasticsearch.queryOptions` config were not picked up by the `ElasticSearchSearchEngine`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/integration-aws-node@0.1.16 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-explore@0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-pg@0.5.44 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-techdocs@0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-techdocs-node@1.13.3 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-node@1.3.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-common@1.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-signals@0.0.19 + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-signals-react@0.0.13 + - @backstage/types@1.2.1 + +## @backstage/plugin-signals-backend@0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + - @backstage/plugin-signals-node@0.1.20 + +## @backstage/plugin-signals-node@0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## @backstage/plugin-signals-react@0.0.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-techdocs@1.12.6 + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 7d445da: Update keyboard focus on when clicking hash links. This fixes the issue where the "skip to content" link rendered by Material MkDocs isn't focused when used. +- 72d019d: Removed various typos +- 2ffd273: Add hover and focus styling to the "copy to clipboard" button within codeblocks in techdocs. Also added an aria-label to the button for accessibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/integration@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-search-react@1.9.0 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/plugin-techdocs-react@1.2.17 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.48 + +### Patch Changes + +- Updated dependencies + - @backstage/test-utils@1.7.8 + - @backstage/core-app-api@1.17.0 + - @backstage/plugin-techdocs@1.12.6 + - @backstage/plugin-catalog@1.30.0 + - @backstage/plugin-search-react@1.9.0 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/plugin-techdocs-react@1.2.17 + - @backstage/core-plugin-api@1.10.7 + - @backstage/integration-react@1.2.7 + +## @backstage/plugin-techdocs-backend@2.0.2 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-defaults@0.10.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-techdocs-node@1.13.3 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-search-backend-module-techdocs@0.4.2 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.24 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/integration@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/plugin-techdocs-react@1.2.17 + - @backstage/core-plugin-api@1.10.7 + - @backstage/integration-react@1.2.7 + +## @backstage/plugin-techdocs-node@1.13.3 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/integration-aws-node@0.1.16 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-react@1.2.17 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.7 + - @backstage/config@1.3.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/plugin-user-settings@0.8.22 + +### Patch Changes + +- a7bfdb6: plugin-user-settingsgs support i18n +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 5b04b14: Uppercase language name in language select +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-signals-react@0.0.13 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.20 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.109 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.6 + - @backstage/core-app-api@1.17.0 + - @backstage/canon@0.4.0 + - @backstage/cli@0.32.1 + - @backstage/plugin-user-settings@0.8.22 + - @backstage/core-components@0.17.2 + - @backstage/plugin-home@0.8.8 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-scaffolder@1.31.0 + - @backstage/frontend-app-api@0.11.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17 + - @backstage/plugin-catalog-import@0.13.0 + - @backstage/plugin-catalog-graph@0.4.19 + - @backstage/plugin-notifications@0.5.5 + - @backstage/plugin-kubernetes@0.12.7 + - @backstage/plugin-api-docs@0.12.7 + - @backstage/plugin-devtools@0.1.27 + - @backstage/plugin-techdocs@1.12.6 + - @backstage/plugin-catalog@1.30.0 + - @backstage/plugin-signals@0.0.19 + - @backstage/plugin-search@1.4.26 + - @backstage/plugin-org@0.6.39 + - @backstage/plugin-search-react@1.9.0 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/plugin-scaffolder-react@1.16.0 + - @backstage/plugin-kubernetes-cluster@0.0.25 + - @backstage/plugin-techdocs-react@1.2.17 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24 + - @backstage/app-defaults@1.6.2 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-search-common@1.2.18 + +## example-app-next@0.0.23 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/core-app-api@1.17.0 + - @backstage/canon@0.4.0 + - @backstage/cli@0.32.1 + - @backstage/plugin-user-settings@0.8.22 + - @backstage/core-components@0.17.2 + - @backstage/plugin-home@0.8.8 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-scaffolder@1.31.0 + - @backstage/frontend-app-api@0.11.2 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17 + - @backstage/plugin-app-visualizer@0.1.19 + - @backstage/plugin-catalog-import@0.13.0 + - @backstage/plugin-catalog-graph@0.4.19 + - @backstage/plugin-notifications@0.5.5 + - @backstage/plugin-kubernetes@0.12.7 + - @backstage/plugin-api-docs@0.12.7 + - @backstage/plugin-techdocs@1.12.6 + - @backstage/plugin-catalog@1.30.0 + - @backstage/plugin-signals@0.0.19 + - @backstage/plugin-search@1.4.26 + - @backstage/plugin-app@0.1.9 + - @backstage/plugin-org@0.6.39 + - @backstage/plugin-search-react@1.9.0 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/plugin-scaffolder-react@1.16.0 + - @backstage/plugin-kubernetes-cluster@0.0.25 + - @backstage/plugin-techdocs-react@1.2.17 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-defaults@0.2.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24 + - @backstage/app-defaults@1.6.2 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-search-common@1.2.18 + +## app-next-example-plugin@0.0.23 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + +## example-backend@0.0.38 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0 + - @backstage/plugin-catalog-backend@2.0.0 + - @backstage/plugin-scaffolder-backend-module-github@0.7.1 + - @backstage/backend-defaults@0.10.0 + - @backstage/plugin-auth-backend-module-github-provider@0.3.3 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.2 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-kubernetes-backend@0.19.6 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/plugin-scaffolder-backend@1.33.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-backend@0.7.0 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/plugin-techdocs-backend@2.0.2 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.10 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.0 + - @backstage/plugin-notifications-backend@0.5.6 + - @backstage/plugin-signals-backend@0.3.4 + - @backstage/plugin-catalog-backend-module-openapi@0.2.10 + - @backstage/plugin-app-backend@0.5.2 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.8 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8 + - @backstage/plugin-devtools-backend@0.5.5 + - @backstage/plugin-events-backend@0.5.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.8 + - @backstage/plugin-proxy-backend@0.6.2 + - @backstage/plugin-search-backend@2.0.2 + - @backstage/plugin-search-backend-module-catalog@0.3.4 + - @backstage/plugin-search-backend-module-explore@0.3.2 + - @backstage/plugin-search-backend-module-techdocs@0.4.2 + - @backstage/plugin-search-backend-node@1.3.11 + +## e2e-test@0.2.28 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.6.2 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## @internal/frontend@0.0.9 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @internal/scaffolder@0.0.9 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/plugin-scaffolder-react@1.16.0 + +## techdocs-cli-embedded-app@0.2.108 + +### Patch Changes + +- Updated dependencies + - @backstage/test-utils@1.7.8 + - @backstage/theme@0.6.6 + - @backstage/core-app-api@1.17.0 + - @backstage/cli@0.32.1 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-techdocs@1.12.6 + - @backstage/plugin-catalog@1.30.0 + - @backstage/plugin-techdocs-react@1.2.17 + - @backstage/core-plugin-api@1.10.7 + - @backstage/app-defaults@1.6.2 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7 + +## yarn-plugin-backstage@0.0.5 + +### Patch Changes + +- fd70d86: Add both `npm:` and `backstage:` ranges to the lockfile to ensure compatibility with tools that parse the lockfile and ensure dependencies stay locked when building dist workspaces. +- Updated dependencies + - @backstage/release-manifests@0.0.13 + - @backstage/cli-common@0.1.15 + +## @internal/plugin-todo-list@1.0.39 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + +## @internal/plugin-todo-list-backend@1.0.39 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + +## @internal/plugin-todo-list-common@1.0.25 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.0 diff --git a/docs/releases/v1.39.0-next.3-changelog.md b/docs/releases/v1.39.0-next.3-changelog.md new file mode 100644 index 0000000000..be45f844b8 --- /dev/null +++ b/docs/releases/v1.39.0-next.3-changelog.md @@ -0,0 +1,2119 @@ +# Release v1.39.0-next.3 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.39.0-next.3](https://backstage.github.io/upgrade-helper/?to=1.39.0-next.3) + +## @backstage/core-app-api@1.17.0-next.1 + +### Minor Changes + +- 1e0230e: Support custom `AuthConnector` for `OAuth2`. + + A user can pass their own `AuthConnector` implementation in `OAuth2` constructor. + In which case the session manager will use that instead of the `DefaultAuthConnector` to interact with the + authentication provider. + + A custom `AuthConnector` may call the authentication provider from the front-end, store and retrieve tokens + in the session storage, for example, and otherwise send custom requests to the authentication provider and + handle its responses. + + Note, that if the custom `AuthConnector` transforms scopes returned from the authentication provider, + the transformation must be the same as `OAuth2CreateOptions#scopeTransform` passed to `OAuth2` constructor. + See creating `DefaultAuthConnector` in `OAuth2#create(...)` for an example. + +### Patch Changes + +- cc119b2: Fixed an issue causing `OAuthRequestDialog` to re-render on mount. +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/integration@1.17.0-next.3 + +### Minor Changes + +- d945206: Added support for federated credentials using managed identities in the Azure DevOps integration. Federated credentials are only available for Azure DevOps organizations that have been configured to use Entra ID for authentication. + + ```diff + integrations: + azure: + - host: dev.azure.com + credentials: + + - clientId: ${APP_REGISTRATION_CLIENT_ID} + + managedIdentityClientId: system-assigned + + tenantId: ${AZURE_TENANT_ID} + ``` + + This also adds support for automatically using the system-assigned managed identity of an Azure resource by specifying `system-assigned` as the client ID of the managed identity. + + ```diff + integrations: + azure: + - host: dev.azure.com + credentials: + - - clientId: ${AZURE_CLIENT_ID} + + - clientId: system-assigned + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog@1.30.0-next.3 + +### Minor Changes + +- 970cb48: Show the pagination text for the offset-paginated catalog table, and remove the pagination bar from the top of the `CatalogTable` when pagination is enabled. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-react@1.9.0-next.2 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.0-next.0 + +### Minor Changes + +- e4dabc6: add new gitea provider module + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + +## @backstage/app-defaults@1.6.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + +## @backstage/backend-app-api@1.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/backend-defaults@0.10.0-next.3 + +### Patch Changes + +- 1e06afd: `GithubUrlReader`'s search detects glob-patterns supported by `minimatch`, instead of just detecting + `*` and `?` characters. + + For example, this allows to search for patterns like `{C,c}atalog-info.yaml`. + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-app-api@1.2.3-next.2 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + - @backstage/plugin-permission-node@0.10.0-next.2 + +## @backstage/backend-dynamic-feature-service@0.7.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/plugin-catalog-backend@2.0.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-openapi-utils@0.5.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.33-next.2 + - @backstage/plugin-events-backend@0.5.2-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/backend-openapi-utils@0.5.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/backend-plugin-api@1.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @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-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + +## @backstage/backend-test-utils@1.5.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-app-api@1.2.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/canon@0.4.0-next.3 + +### Patch Changes + +- c8f32db: Use correct colour token for TextField clear button icon, prevent layout shift whenever it is hidden or shown and properly size focus area around it. Also stop leading icon shrinking when used together with clear button. + +## @backstage/cli@0.32.1-next.3 + +### Patch Changes + +- 674def9: fix: enable `lazyCompilation` and `refreshOptions` for rspack +- 19a4e7c: Internal refactor to move things closer to home +- Updated dependencies + - @backstage/release-manifests@0.0.13-next.0 + - @backstage/integration@1.17.0-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.1.10 + - @backstage/types@1.2.1 + +## @backstage/core-compat-api@0.4.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/create-app@0.6.2-next.3 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.15 + +## @backstage/dev-utils@1.1.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/app-defaults@1.6.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/theme@0.6.6-next.0 + +## @backstage/frontend-app-api@0.11.2-next.3 + +### Patch Changes + +- 1f04491: Added the ability to ignore unknown extension config by passing `{ flags: { allowUnknownExtensionConfig: true } }` to `createSpecializedApp`. +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/frontend-defaults@0.2.2-next.3 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-defaults@0.2.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.11.2-next.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-app@0.1.9-next.3 + +## @backstage/frontend-test-utils@0.3.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.11.2-next.3 + - @backstage/test-utils@1.7.8-next.2 + - @backstage/config@1.3.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-app@0.1.9-next.3 + +## @backstage/integration-react@1.2.7-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + +## @backstage/release-manifests@0.0.13-next.0 + +### Patch Changes + +- 163f3da: This expands the configurability of `release-manifests` to pave the road for more configuration options in the `cli`. + + Specifically it allows the specification of mirrored, proxied, or air-gapped hosts when upgrading across releases when + working in restricted or heavily governed development environments (common in large enterprises and government + entities). + +## @backstage/repo-tools@0.13.3-next.3 + +### Patch Changes + +- 659f2ce: Updated dependency `typedoc` to `^0.28.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + +## @techdocs/cli@1.9.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.3-next.3 + +## @backstage/test-utils@1.7.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + +## @backstage/plugin-api-docs@0.12.7-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.30.0-next.3 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + +## @backstage/plugin-app@0.1.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + +## @backstage/plugin-app-backend@0.5.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.33-next.2 + +## @backstage/plugin-app-node@0.1.33-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config-loader@1.10.1-next.0 + +## @backstage/plugin-auth-backend@0.25.0-next.2 + +### Patch Changes + +- ab53e6f: Added support for the new `dangerousEntityRefFallback` option for `signInWithCatalogUser` in `AuthResolverContext`. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.2 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0-next.2 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.8-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.3.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.8-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0-next.2 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + +## @backstage/plugin-auth-node@0.6.3-next.2 + +### Patch Changes + +- ab53e6f: Added a new `dangerousEntityRefFallback` option to the `signInWithCatalogUser` method in `AuthResolverContext`. The option will cause the provided entity reference to be used as a fallback in case the user is not found in the catalog. It is up to the caller to provide the fallback entity reference. + + Auth providers that include pre-defined sign-in resolvers are encouraged to define a flag named `dangerouslyAllowSignInWithoutUserInCatalog` in their config, which in turn enables use of the `dangerousEntityRefFallback` option. For example: + + ```ts + export const usernameMatchingUserEntityName = createSignInResolverFactory({ + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { + return async ( + info: SignInInfo>, + ctx, + ) => { + const { username } = info.result.fullProfile; + if (!username) { + throw new Error('User profile does not contain a username'); + } + + return ctx.signInWithCatalogUser( + { entityRef: { name: username } }, + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: username } } + : undefined, + }, + ); + }; + }, + }); + ``` + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-bitbucket-cloud-common@0.3.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + +## @backstage/plugin-catalog-backend@2.0.0-next.3 + +### Patch Changes + +- 8e0f15f: "Added a note clarifying that `entity-fetch` audit events are not visible by default in the logs and are only displayed when the log severity level is adjusted." +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-openapi-utils@0.5.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + +## @backstage/plugin-catalog-backend-module-aws@0.4.11-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + +## @backstage/plugin-catalog-backend-module-azure@0.3.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.5.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-node@1.17.0-next.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.4.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.4.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + +## @backstage/plugin-catalog-backend-module-github@0.9.0-next.3 + +### Patch Changes + +- ee9f59f: Added filter to include archived repositories +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.0-next.3 + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.9.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-catalog-backend-module-gitlab@0.6.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/plugin-catalog-backend-module-gitlab@0.6.6-next.3 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/plugin-catalog-backend@2.0.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.11.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + +## @backstage/plugin-catalog-backend-module-logs@0.1.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-catalog-backend-module-msgraph@0.7.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.2 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.8-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-catalog-graph@0.4.19-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/types@1.2.1 + +## @backstage/plugin-catalog-import@0.13.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + +## @backstage/plugin-catalog-node@1.17.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + +## @backstage/plugin-catalog-react@1.18.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/frontend-test-utils@0.3.2-next.3 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + +## @backstage/plugin-devtools@0.1.27-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-devtools-common@0.1.16-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + +## @backstage/plugin-devtools-backend@0.5.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-devtools-common@0.1.16-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + +## @backstage/plugin-events-backend@0.5.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.5.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-events-backend-module-azure@0.2.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-events-backend-module-gerrit@0.2.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-events-backend-module-github@0.4.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-events-backend-module-gitlab@0.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-events-backend-test-utils@0.1.44-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-events-node@0.4.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-gateway-backend@1.0.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + +## @backstage/plugin-home@0.8.8-next.3 + +### Patch Changes + +- f7ca0fe: Added the Catalog presentation API to the HomePageRecentlyVisited and HomePageTopVisited components + +- eddd96c: Added optional title prop to `customHomePageGrid` + +- 16eb4bf: Export ContentModal from `@backstage/plugin-home-react` so people can use this in other scenarios. + Renamed `CatalogReactComponentsNameToClassKey` to `PluginHomeComponentsNameToClassKey` in `overridableComponents.ts` + + Made QuickStartCard `docsLinkTitle` prop more flexible to allow for any React.JSX.Element instead of just a string. + Added QuickStartCard prop `additionalContent` which can eventually replace the prop `video`. + +- 195323f: Export root page route from the home plugin to enable adding links/nav to it from outside the plugin + +- d710d74: docs: Update default for `preventCollision` prop + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/plugin-home-react@0.1.26-next.2 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/theme@0.6.6-next.0 + +## @backstage/plugin-home-react@0.1.26-next.2 + +### Patch Changes + +- 16eb4bf: Export ContentModal from `@backstage/plugin-home-react` so people can use this in other scenarios. + Renamed `CatalogReactComponentsNameToClassKey` to `PluginHomeComponentsNameToClassKey` in `overridableComponents.ts` + + Made QuickStartCard `docsLinkTitle` prop more flexible to allow for any React.JSX.Element instead of just a string. + Added QuickStartCard prop `additionalContent` which can eventually replace the prop `video`. + +- Updated dependencies + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + +## @backstage/plugin-kubernetes@0.12.7-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/plugin-kubernetes-react@0.5.7-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + +## @backstage/plugin-kubernetes-backend@0.19.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + - @backstage/plugin-kubernetes-node@0.3.0-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + +## @backstage/plugin-kubernetes-cluster@0.0.25-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/plugin-kubernetes-react@0.5.7-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + +## @backstage/plugin-kubernetes-node@0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + +## @backstage/plugin-notifications@0.5.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-signals-react@0.0.13-next.0 + +## @backstage/plugin-notifications-backend@0.5.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.15-next.2 + - @backstage/plugin-signals-node@0.1.20-next.2 + +## @backstage/plugin-notifications-backend-module-email@0.3.9-next.3 + +### Patch Changes + +- aa3a63a: Enable the ability to configure the endpoint for the SES connection used in the notifications email module. This enables the configuration of alternate endpoints as required, for example for local testing or alternative stacks. +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.15-next.2 + +## @backstage/plugin-notifications-backend-module-slack@0.1.1-next.3 + +### Patch Changes + +- f6480c7: Fix dataloader caching, and use the proper catalog service ref + +- e099d0a: Notifications which mention user entity refs are now replaced with Slack compatible mentions. + + Example: `Welcome <@user:default/billy>!` -> `Welcome <@U123456890>!` + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.15-next.2 + +## @backstage/plugin-notifications-node@0.2.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-signals-node@0.1.20-next.2 + +## @backstage/plugin-org@0.6.39-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + +## @backstage/plugin-org-react@0.1.38-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + +## @backstage/plugin-permission-backend@0.7.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + +## @backstage/plugin-permission-node@0.10.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-proxy-backend@0.6.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/types@1.2.1 + - @backstage/plugin-proxy-node@0.1.4-next.2 + +## @backstage/plugin-proxy-node@0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + +## @backstage/plugin-scaffolder@1.31.0-next.3 + +### Patch Changes + +- a274e0a: Migrate custom fields to new schema factory function; + standardize field descriptions to prefer ui:description and present consistently, + utilizing ScaffolderField component where possible. +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/plugin-scaffolder-react@1.16.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + +## @backstage/plugin-scaffolder-backend@1.33.0-next.3 + +### Patch Changes + +- ec42f8e: Generating new tokens on each Scaffolder Task Retry +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.1-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.2 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.9-next.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.10-next.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.3 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.9-next.3 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.9-next.3 + - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.3 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.3 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.3 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.15-next.2 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-node-test-utils@0.2.2-next.3 + +## @backstage/plugin-scaffolder-node@0.8.2-next.3 + +### Patch Changes + +- 16e2e9c: trim leading and trailing slashes from parseRepoUrl query parameters +- ec42f8e: Generating new tokens on each Scaffolder Task Retry +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + +## @backstage/plugin-scaffolder-node-test-utils@0.2.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/backend-test-utils@1.5.0-next.3 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-react@1.16.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + +## @backstage/plugin-search@1.4.26-next.3 + +### Patch Changes + +- fa48594: search plugin support i18n +- Updated dependencies + - @backstage/plugin-search-react@1.9.0-next.2 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-search-backend@2.0.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/backend-openapi-utils@0.5.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-search-backend-module-catalog@0.3.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.2-next.3 + +### Patch Changes + +- 01f772c: Fixed an issue where the `search.elasticsearch.queryOptions` config were not picked up by the `ElasticSearchSearchEngine`. +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-search-backend-module-explore@0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-search-backend-module-pg@0.5.44-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-search-backend-module-techdocs@0.4.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-node@1.13.3-next.3 + +## @backstage/plugin-search-backend-node@1.3.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-search-react@1.9.0-next.2 + +### Patch Changes + +- 2c76614: Fix memoization of `filterValue` in `SearchFilter.Autocomplete` to prevent unintended resets +- fa48594: search plugin support i18n +- Updated dependencies + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-signals-backend@0.3.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + - @backstage/plugin-signals-node@0.1.20-next.2 + +## @backstage/plugin-signals-node@0.1.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + +## @backstage/plugin-techdocs@1.12.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-search-react@1.9.0-next.2 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.48-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/plugin-catalog@1.30.0-next.3 + - @backstage/plugin-search-react@1.9.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/test-utils@1.7.8-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/plugin-techdocs@1.12.6-next.3 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + +## @backstage/plugin-techdocs-backend@2.0.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.3 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-node@1.13.3-next.3 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + +## @backstage/plugin-techdocs-node@1.13.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-user-settings@0.8.22-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.13-next.0 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.3.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.20-next.2 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.109-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/canon@0.4.0-next.3 + - @backstage/cli@0.32.1-next.3 + - @backstage/plugin-home@0.8.8-next.3 + - @backstage/plugin-catalog@1.30.0-next.3 + - @backstage/frontend-app-api@0.11.2-next.3 + - @backstage/plugin-search-react@1.9.0-next.2 + - @backstage/plugin-scaffolder@1.31.0-next.3 + - @backstage/plugin-search@1.4.26-next.3 + - @backstage/app-defaults@1.6.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-api-docs@0.12.7-next.3 + - @backstage/plugin-catalog-graph@0.4.19-next.3 + - @backstage/plugin-catalog-import@0.13.0-next.3 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/plugin-notifications@0.5.5-next.3 + - @backstage/plugin-org@0.6.39-next.3 + - @backstage/plugin-scaffolder-react@1.16.0-next.3 + - @backstage/plugin-signals@0.0.19-next.1 + - @backstage/plugin-techdocs@1.12.6-next.3 + - @backstage/plugin-user-settings@0.8.22-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.3 + - @backstage/plugin-devtools@0.1.27-next.3 + - @backstage/plugin-kubernetes@0.12.7-next.3 + - @backstage/plugin-kubernetes-cluster@0.0.25-next.3 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.3 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + +## example-app-next@0.0.23-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/canon@0.4.0-next.3 + - @backstage/cli@0.32.1-next.3 + - @backstage/plugin-home@0.8.8-next.3 + - @backstage/plugin-catalog@1.30.0-next.3 + - @backstage/frontend-app-api@0.11.2-next.3 + - @backstage/plugin-search-react@1.9.0-next.2 + - @backstage/plugin-scaffolder@1.31.0-next.3 + - @backstage/plugin-search@1.4.26-next.3 + - @backstage/app-defaults@1.6.2-next.2 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-api-docs@0.12.7-next.3 + - @backstage/plugin-catalog-graph@0.4.19-next.3 + - @backstage/plugin-catalog-import@0.13.0-next.3 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/plugin-notifications@0.5.5-next.3 + - @backstage/plugin-org@0.6.39-next.3 + - @backstage/plugin-scaffolder-react@1.16.0-next.3 + - @backstage/plugin-signals@0.0.19-next.1 + - @backstage/plugin-techdocs@1.12.6-next.3 + - @backstage/plugin-user-settings@0.8.22-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/frontend-defaults@0.2.2-next.3 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-app@0.1.9-next.3 + - @backstage/plugin-app-visualizer@0.1.19-next.1 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.3 + - @backstage/plugin-kubernetes@0.12.7-next.3 + - @backstage/plugin-kubernetes-cluster@0.0.25-next.3 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.3 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + +## example-backend@0.0.38-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/plugin-catalog-backend@2.0.0-next.3 + - @backstage/plugin-auth-backend@0.25.0-next.2 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/plugin-auth-backend-module-github-provider@0.3.3-next.2 + - @backstage/plugin-scaffolder-backend@1.33.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-app-backend@0.5.2-next.2 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.8-next.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.2-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.2.10-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.0-next.2 + - @backstage/plugin-devtools-backend@0.5.5-next.3 + - @backstage/plugin-events-backend@0.5.2-next.2 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.0-next.2 + - @backstage/plugin-kubernetes-backend@0.19.6-next.3 + - @backstage/plugin-notifications-backend@0.5.6-next.2 + - @backstage/plugin-permission-backend@0.7.0-next.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.8-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + - @backstage/plugin-proxy-backend@0.6.2-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.3 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.10-next.3 + - @backstage/plugin-search-backend@2.0.2-next.3 + - @backstage/plugin-search-backend-module-catalog@0.3.4-next.2 + - @backstage/plugin-search-backend-module-explore@0.3.2-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.3 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-signals-backend@0.3.4-next.2 + - @backstage/plugin-techdocs-backend@2.0.2-next.3 + +## e2e-test@0.2.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.6.2-next.3 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## @internal/scaffolder@0.0.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.16.0-next.3 + - @backstage/frontend-plugin-api@0.10.2-next.1 + +## techdocs-cli-embedded-app@0.2.108-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/cli@0.32.1-next.3 + - @backstage/plugin-catalog@1.30.0-next.3 + - @backstage/app-defaults@1.6.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/test-utils@1.7.8-next.2 + - @backstage/plugin-techdocs@1.12.6-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + +## yarn-plugin-backstage@0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/release-manifests@0.0.13-next.0 + - @backstage/cli-common@0.1.15 + +## @internal/plugin-todo-list-backend@1.0.39-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 diff --git a/docs/releases/v1.39.0.md b/docs/releases/v1.39.0.md new file mode 100644 index 0000000000..de6903adb4 --- /dev/null +++ b/docs/releases/v1.39.0.md @@ -0,0 +1,125 @@ +--- +id: v1.39.0 +title: v1.39.0 +description: Backstage Release v1.39.0 +--- + +These are the release notes for the v1.39.0 release of [Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done. + +## Highlights + +### **BREAKING**: Removals of Old Backend System support + +We are gradually removing support for the old backend system from plugins and modules. In this release, the following backend features no longer support the old backend system: + +- `auth-backend` +- `catalog-backend` +- `permission-backend` +- `catalog-backend-module-incremental-ingestion` +- `catalog-backend-module-unprocessed` + +If you have already migrated to the new backend system, you will not be required to do any code changes, except perhaps removing the /alpha suffix on your imports if you had that. + +If you have not yet migrated, now would be a great time to do so. + +Note that for the `@backstage/plugin-catalog-backend`, this necessitated a major semantic version bump. The others are on version 0.x still and received a minor level bump. If you are using the Backstage yarn plugin, you will get on the new versions automatically. If you do not, you may want to bump your dependency ranges (to e.g. ^2.0.0 for the major bumps) for these to ensure that you keep getting the most recent updates. + +Along with the major bump, most of the previously already deprecated exports were finally removed as well from these packages. There are a bit too many to list here, but as an example, some of the old deprecated exports from the Catalog backend have been available from their new home in `@backstage/plugin-catalog-node` instead. + +An upshot of these changes is that we have nearly entirely eliminated the usage of the old `@backstage/backend-common` package. We hope to get rid of the very final remnants of it as soon as possible. + +### **BREAKING**: `CodeOwnersProcessor` removed from built-in processors + +As part of the major bump of the catalog backend the `CodeOwnersProcessor` has been removed from the set of default processors, because it's quite expensive to run and has vague semantics. You need to update your backend to add it to the `catalogProcessingExtensionPoint` if you wish to continue using it. + +### Deprecating React 17 + +React 17 is now deprecated and the Backstage CLI will warn if you are using it. Please follow the [migration guide](https://backstage.io/docs/tutorials/react18-migration/) to upgrade to React 18. + +### Scheduler Service REST API + +The core scheduler service now comes with its own REST API that you can use to list and trigger tasks. For more information, see the [scheduler service documentation](https://backstage.io/docs/backend-system/core-services/scheduler). + +### Canon 0.4.0 + +Our new design system Canon has reached 0.4.0 in this release. You can read more about the changes in the [canon changelog](https://canon.backstage.io/releases). + +### Omitting ownership claims in issued identity tokens + +The `auth` plugin backend now supports omitting the ownership claims in issued Backstage identity tokens. By setting the `auth.omitIdentityTokenOwnershipClaim` configuration flag the issued tokens will no longer contain the `ent` claim that represents the ownership references of the user. + +The benefit of this new flag is that issued user tokens will be much smaller in size, but they will no longer be self-contained. This means that any consumers of the token that require access to the ownership claims now need to call the `/api/auth/v1/userinfo` endpoint instead. Within the Backstage ecosystem this is done automatically, as clients will still receive the full set of claims during authentication, while plugin backends will need to use the `UserInfoService` which already calls the user info endpoint if necessary. + +When enabling this flag, it is important that any custom sign-in resolvers directly return the result of the sign-in method. For example, the following would not work: + +```ts +const { token } = await ctx.issueToken({ + claims: { sub: entityRef, ent: [entityRef] }, +}); +return { token }; // WARNING: This will not work with the flag enabled +``` + +Instead, the sign-in resolver should return the entire result: + +```ts +return ctx.issueToken({ + claims: { sub: entityRef, ent: [entityRef] }, +}); +``` + +### JSX elements in I18n interpolation + +The internationalization APIs now transparently supports interpolation of JSX elements. If any of the provided replacements are a JSX element, the translation function will also return a JSX element where the parts are joined together in a fragment. This makes it much easier to include things like links in a message while keeping the translations flexible. + +### Built-in sign-in fallback for users not in the catalog + +It is sometimes useful to allow users to [sign-in without existing in the catalog](https://backstage.io/docs/auth/identity-resolver#sign-in-without-users-in-the-catalog), even though care must be taken when allowing this. There is now a new `dangerousEntityRefFallback` option for the `signInWithCatalogUser` method that lets you supply a fallback user identity in case the catalog lookup returns an empty response, which makes it even easier to implement this pattern. + +In addition, many of the available auth provider modules now support a new `dangerouslyAllowSignInWithoutUserInCatalog` configuration option that enables this using a provider-specific fallback. + +Contributed by [@JessicaJHee](https://github.com/JessicaJHee) in [#28967](https://github.com/backstage/backstage/pull/28967) + +### Improved compatibility for Yarn plugin + +The yarn plugin will now add both `npm:` and `backstage:` ranges to lockfile entries, in order to ensure better compatibility with tools that parse the lockfile and ensure dependencies stay locked when building dist workspaces. + +### Federated Credentials for Azure DevOps integration + +If you have configured your Azure DevOps organization to use Entra ID for authentication, you can now use federated credentials using a managed identity. This allows you to authenticate for an app registration without using a client secret. This is mostly useful when you have to authenticate against an Azure DevOps organization in a different Entra ID tenant, and effectively eliminates the need to manage and expose secrets for other tenants. + +Contributed by [@sanderaernouts](https://github.com/sanderaernouts) in [#28437](https://github.com/backstage/backstage/pull/28437) + +### Valkey support for cache service + +The cache service implementation provided by `@backstage/backend-defaults` now also supports Valkey as one of the configurable clients. + +Contributed by [@j1bulbul](https://github.com/j1bulbul) in [#29762](https://github.com/backstage/backstage/pull/29762) + +### Custom AuthConnector implementations + +The frontend `OAuth2` class now lets you provide a custom `AuthConnector` implementation, which can be useful if you need to integrate with auth providers that take a bit more liberty in their OAuth implementation. + +Contributed by [@gusega](https://github.com/gusega) in [#28004](https://github.com/backstage/backstage/pull/28004) + +## Security Fixes + +This release does not contain any security fixes. + +## Upgrade path + +We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). + +## Links and References + +Below you can find a list of links and references to help you learn about and start using this new release. + +- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/) +- [GitHub repository](https://github.com/backstage/backstage) +- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy) +- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support +- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.39.0-changelog.md) +- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) + +Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.40.0-changelog.md b/docs/releases/v1.40.0-changelog.md new file mode 100644 index 0000000000..e5b65ab8ed --- /dev/null +++ b/docs/releases/v1.40.0-changelog.md @@ -0,0 +1,2771 @@ +# Release v1.40.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.40.0](https://backstage.github.io/upgrade-helper/?to=1.40.0) + +## @backstage/plugin-scaffolder-backend@2.0.0 + +### 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. + +- 5863b04: **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, + }), + ); + }, + }); + }, + }); + ``` + +### 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 + +- 89a941d: Migrating to latest action format + +- 023629e: Enable usage of secrets within 'each' step of software templates. For example, you can now structure your `each` step like this: + + each: + [ + { name: "Service1", token: "${{ secrets.token1 }}" }, + { name: "Service2", token: "${{ secrets.token2 }}" }, + ] + +- e92e481: Add tests for Scaffolder + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.10 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10 + - @backstage/plugin-scaffolder-backend-module-github@0.8.0 + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.10 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.10 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.11 + - @backstage/backend-plugin-api@1.4.0 + - @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 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/backend-defaults@0.11.0 + +### Minor Changes + +- 3ccb7fc: Enhanced error handling in the auditor service factory to pass errors as objects. Aligned WinstonRootAuditorService with the default service factory's error handling. + +### Patch Changes + +- 1220cf8: Added new rate limit middleware to allow rate limiting requests to the backend + + If you are using the `configure` callback of the root HTTP router service and do NOT call `applyDefaults()` inside it, please see [the relevant changes](https://github.com/backstage/backstage/pull/28708/files#diff-86ad1b6a694dd250823aee39d410428dd837c9d9a04ca8c33bd1081fbe3f22af) that were made, to see if you want to apply them as well to your custom configuration. + Rate limiting can be turned on by adding the following configuration to `app-config.yaml`: + + ```yaml + backend: + rateLimit: + window: 6s + incomingRequestLimit: 100 + ``` + + Plugin specific rate limiting can be configured by adding the following configuration to `app-config.yaml`: + + ```yaml + backend: + rateLimit: + global: false # This will disable the global rate limiting + plugin: + catalog: + window: 6s + incomingRequestLimit: 100 + ``` + +- c999c25: Added some default implementations for the experimental `ActionsService` and `ActionsRegistryService` under `/alpha` that allow registration of actions for a particular plugin. + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-app-api@1.2.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/backend-dev-utils@0.1.5 + - @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 + - @backstage/plugin-permission-node@0.10.1 + +## @backstage/backend-plugin-api@1.4.0 + +### Minor Changes + +- 664c07a: Added `actionsRegistry` and `actions` experimental services to `/alpha` to allow registration of distributed actions from plugins, and the ability to invoke these actions. You can use these services by including them like the following: + + ```ts + import { + actionsRegistryServiceRef, + actionsServiceRef, + } from '@backstage/backend-plugin-api/alpha'; + + createBackendPlugin({ + pluginId: 'test-plugin', + register({ registerInit }) { + registerInit({ + deps: { + actions: actionsServiceRef, + actionsRegistry: actionsRegistryServiceRef, + }, + async init({ actions, actionsRegistry }) { + actionsRegistry.register({ + ..., + }); + + await actions.invoke(...); + }, + }); + }, + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @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 + +## @backstage/backend-test-utils@1.6.0 + +### Minor Changes + +- 6dfb7be: Added `mockServices.permissions()` that can return actual results. +- c999c25: Added an `actionsRegistryServiceMock` and `actionsServiceMock` to `/alpha` export for the experimental services. + + This allows you to write tests for your actions by doing something similar to the following: + + ```ts + import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; + + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock({ + entities: [ + ... + ], + }); + + createGetCatalogEntityAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:get-catalog-entity', + input: { name: 'test' }, + }), + ).resolves.toEqual(...) + ``` + +### Patch Changes + +- 12c1fd4: Make the `user` credentials mock behave more like production +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-app-api@1.2.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-permission-common@0.9.0 + +## @backstage/canon@0.5.0 + +### 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. +- 35fd51d: Move TextField component to use react Aria under the hood. Introducing a new FieldLabel component to help build custom fields. +- 78204a2: **Breaking** We are adding a new as prop on the Heading and Text component to make it easier to change the component tag. We are removing the render prop in favour of the as prop. +- c49e335: TextField in Canon now has multiple label sizes as well as the capacity to hide label and description but still make them available for screen readers. +- 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 + +- 44df879: Add min-width: 0; by default on every Flex components in Canon to help support truncated texts inside flex elements. +- ee6ffe6: Fix styling for the title4 prop on the Heading component in Canon. +- f2f814a: Added a render prop to the Button component in Canon to use it as a link. +- 98f02a6: Add new Switch component in Canon. +- c94f8e0: The filter input in menu comboboxes should now always use the full width of the menu it's in. +- 269316d: Remove leftover console.log from Container component. + +## @backstage/cli@0.33.0 + +### Minor Changes + +- eef0e83: Internal update to promote the modular CLI entrypoint to stable. + +### Patch Changes + +- d07fe35: 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. +- 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. +- d6d63c7: Updating the scaffolder action boilerplate to use new `zod` schema +- e36e855: Added `backstage.pluginId` field in `package.json` to all default plugin package templates for the `new` command. +- 1bab255: Internal refactor to combine alpha `build` and `start` modules. +- 713e957: fix: merge eslint reports when using json format +- 8a0164c: Fix an issue where some commands were not usable because of missing dist files +- Updated dependencies + - @backstage/eslint-plugin@0.1.11 + - @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/integration@1.17.0 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.1 + +## @backstage/create-app@0.7.0 + +### Minor Changes + +- 30474c4: Add .cache directory to shipped gitignore. + +### Patch Changes + +- d57a6f7: Bumped create-app version. +- f628f44: Bumped create-app version. +- 9c9faf2: Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.15 + +## @backstage/repo-tools@0.14.0 + +### Minor Changes + +- bf9a173: Add support for caching the per-package output from the `package-docs` command. + +### Patch Changes + +- 4bff5d0: Fixed a bug where linting would fail with the generated clients when defining top-level `enum` schema values. +- 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. +- c83cd8b: Fixed some circular or otherwise unclear imports +- a372bf1: Updated dependency `@electric-sql/pglite` to `^0.3.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @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 + +## @backstage/plugin-catalog@1.31.0 + +### Minor Changes + +- 406acb6: 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`). + +- 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/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/plugin-techdocs-react@1.3.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-search-react@1.9.1 + - @backstage/integration-react@1.2.8 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@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.35 + - @backstage/plugin-scaffolder-common@1.5.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-catalog-backend@2.1.0 + +### Minor Changes + +- 2e7adf0: Implement the action `get-catalog-entity` with the `ActionsRegistry` + +### Patch Changes + +- 2cac8b0: You can now specify an optional value when applying the `HAS_LABEL` permission rule, similar to the `HAS_ANNOTATION` permission rule. +- c83cd8b: Fixed some circular or otherwise unclear imports +- 4654a78: Update `refresh_state_references.id` to be a big int +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/backend-openapi-utils@0.5.4 + - @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 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.0 + +### Minor Changes + +- 8a150bf: **BREAKING**: `BitbucketCloudEntityProvider` now accepts a `CatalogService` instead of a `CatalogApi`. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.0 + +### Minor Changes + +- eb8b3aa: Add validateLocationsExist option to avoid generating locations for catalog-info.yaml files that do not exist in the source repository. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +## @backstage/plugin-catalog-backend-module-github@0.10.0 + +### Minor Changes + +- 7c0dfb0: GitHub organization now matches in a case-insensitive manner when processing events. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-catalog-backend@2.1.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.0 + +### Minor Changes + +- 42bb3b8: **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. + +### Patch Changes + +- 57a0bad: Implement retry for GitLab API calls to handle rate limiting +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +## @backstage/plugin-catalog-react@1.19.0 + +### Minor Changes + +- 406acb6: 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. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/integration-react@1.2.8 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/frontend-test-utils@0.3.3 + - @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.35 + +## @backstage/plugin-events-backend-module-kafka@0.1.0 + +### Minor Changes + +- b034b9d: Adds a new module `kafka` for plugin-events-backend + + The module introduces the `KafkaConsumerClient` which creates a Kafka client used to establish consumer connections. It also provides the `KafkaConsumingEventPublisher`, a consumer that subscribes to configured Kafka topics and publishes received messages to the Event Service. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-mcp-actions-backend@0.1.0 + +### Minor Changes + +- 4ed0fb6: Initial implementation of an `mcp-actions` backend + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder@1.32.0 + +### Minor Changes + +- 6c972fe: Added information about the `entityRef` and `taskId` to the analytics events whenever is possible. +- 3c59ece: **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. + +### Patch Changes + +- d781b33: render details for composite property schemas +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- b00c160: Remove React import form notification and scaffolder plugin +- 95a1d72: Added appropriate message when global templating function metadata is absent. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-scaffolder-react@1.17.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/integration-react@1.2.8 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/plugin-scaffolder-backend-module-github@0.8.0 + +### Minor Changes + +- 5863b04: **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, + }), + ); + }, + }); + }, + }); + ``` + +### Patch Changes + +- 575c76b: Migrate to using new actions +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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-node@0.9.0 + +### Minor Changes + +- 5863b04: **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. + +### Patch Changes + +- e89d7b6: Use `LoggerService` instead of `Logger`. This is a non-breaking change, as the `LoggerService` is a subset of the `Logger` interface. +- 9c8ff0c: Update pull request creation filter to include .gitignore files in the created pull request +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @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.3.0 + +### Minor Changes + +- 3cea7ee: **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. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-test-utils@1.6.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-react@1.17.0 + +### Minor Changes + +- 6c972fe: Added information about the `entityRef` and `taskId` to the analytics events whenever is possible. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/plugin-techdocs@1.13.0 + +### Minor Changes + +- 3c59ece: **New Frontend System Only:** + The `TechDocs` plugin is now responsible for providing an entity icon link extension to read documentation from the catalog entity page. +- ec7b35d: Introduced `backstage.io/techdocs-entity-path` annotation which allows deep linking into another entities TechDocs in conjunction with `backstage.io/techdocs-entity`. + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- 9dde3ba: Improved Keyboard accessibility in techdocs. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/plugin-techdocs-react@1.3.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-search-react@1.9.1 + - @backstage/integration-react@1.2.8 + - @backstage/plugin-auth-react@0.1.16 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/theme@0.6.6 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-techdocs-react@1.3.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/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/app-defaults@1.6.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/core-app-api@1.17.1 + - @backstage/theme@0.6.6 + - @backstage/plugin-permission-react@0.4.35 + +## @backstage/backend-app-api@1.2.4 + +### Patch Changes + +- bb9a501: Fixed a bug where occasionally the initialization order of multiple modules consuming a single extension point could happen in the wrong order. +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/backend-dynamic-feature-service@0.7.1 + +### Patch Changes + +- c83cd8b: Fixed some circular or otherwise unclear imports +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/plugin-catalog-backend@2.1.0 + - @backstage/plugin-events-backend@0.5.3 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/backend-openapi-utils@0.5.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/types@1.2.1 + - @backstage/plugin-app-node@0.1.34 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/backend-openapi-utils@0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/catalog-client@1.10.1 + +### 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/core-app-api@1.17.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.8 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-compat-api@0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-components@0.17.3 + +### Patch Changes + +- 6232160: table actions header support i18n +- Updated dependencies + - @backstage/core-plugin-api@1.10.8 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-plugin-api@1.10.8 + +### Patch Changes + +- c83cd8b: Fixed some circular or otherwise unclear imports +- 0169b23: Internal tweak to avoid circular dependencies +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/dev-utils@1.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/app-defaults@1.6.3 + - @backstage/integration-react@1.2.8 + - @backstage/catalog-model@1.7.4 + - @backstage/core-app-api@1.17.1 + - @backstage/theme@0.6.6 + +## @backstage/eslint-plugin@0.1.11 + +### Patch Changes + +- 098ef95: Fix custom rules package scanning performance. +- 063b2d3: Added new eslint rule to restrict mixed plugin imports. + + New rule `@backstage/no-mixed-plugin-imports` disallows mixed imports between plugins that are mixing + the backstage architecture. This rule forces that: + + - No imports from frontend plugins to backend plugins or other frontend plugins. + - No imports from backend plugins to frontend plugins or other backend plugins. + - No imports from common plugins to frontend or backend plugins. + + The current recommended configuration is giving a warning for mixed imports. This is to be changed in + the future to an error so please adjust your workspace accordingly. + +## @backstage/frontend-app-api@0.11.3 + +### Patch Changes + +- 0169b23: Internal tweak to avoid circular dependencies +- 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/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/frontend-defaults@0.2.3 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-defaults@0.2.3 + +### Patch Changes + +- fa5650c: Forwarded the new `pluginInfoResolver` option for `createApp`. +- Updated dependencies + - @backstage/plugin-app@0.1.10 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/frontend-app-api@0.11.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/frontend-dynamic-feature-loader@0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/config@1.3.2 + +## @backstage/frontend-plugin-api@0.10.3 + +### Patch Changes + +- 0169b23: Internal tweak to avoid circular dependencies + +- 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`. + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-test-utils@0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.1.10 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/frontend-app-api@0.11.3 + - @backstage/config@1.3.2 + - @backstage/test-utils@1.7.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/integration-react@1.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.8 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + +## @techdocs/cli@1.9.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.4 + +## @backstage/test-utils@1.7.9 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.8 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-react@0.4.35 + +## @backstage/plugin-api-docs@0.12.8 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/plugin-catalog@1.31.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.35 + +## @backstage/plugin-app@0.1.10 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/integration-react@1.2.8 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-react@0.4.35 + +## @backstage/plugin-app-backend@0.5.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +## @backstage/plugin-app-node@0.1.34 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config-loader@1.10.1 + +## @backstage/plugin-app-visualizer@0.1.20 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + +## @backstage/plugin-auth-backend@0.25.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.4 + +### Patch Changes + +- c83cd8b: Fixed some circular or otherwise unclear imports +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-backend@0.25.1 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-backend@0.25.1 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.4 + +### Patch Changes + +- b25977f: Updated dependency `@davidzemon/passport-okta-oauth` to `^0.0.6`. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + +## @backstage/plugin-auth-node@0.6.4 + +### Patch Changes + +- 0169b23: Internal tweak to avoid circular dependencies +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-react@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog-backend-module-aws@0.4.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/backend-openapi-utils@0.5.4 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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-org@0.3.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-catalog-backend-module-github@0.10.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.7.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-catalog-backend@2.1.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + - @backstage/plugin-permission-common@0.9.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.11.6 + +### Patch Changes + +- 087d51d: Export LDAP vendor types and instances for testing custom transformers + +- f07b0ad: Added the ability to configure disabling one side of the relations tree with LDAP. + + Groups have a `member` attribute and users have a `memberOf` attribute, however these can drift out of sync in some LDAP installations, leaving weird states in the Catalog as we collate these results together and deduplicate them. + + You can chose to optionally disable one side of these relationships, or even both by setting the respective mapping to `null` in your `app-config.yaml` for your groups and/or users: + + ```yaml + catalog: + providers: + ldapOrg: + default: + 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: + # this ensures that outgoing memberships from users is ignored + memberOf: null + groups: + - dn: ou=access,ou=groups,ou=example,dc=example,dc=net + options: + filter: (&(objectClass=some-group-class)(!(groupType=email))) + map: + description: l + set: + metadata.customField: 'hello' + map: + # this ensures that outgoing memberships from groups is ignored + members: null + ``` + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.1.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-catalog-backend-module-msgraph@0.7.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3 + - @backstage/types@1.2.1 + +## @backstage/plugin-catalog-import@0.13.1 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- 5d7c539: Catalog import plugin full support i18n +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/integration-react@1.2.8 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-node@1.17.1 + +### Patch Changes + +- 0169b23: Internal tweak to avoid circular dependencies +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.18 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-config-schema@0.1.69 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-devtools@0.1.28 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.16 + - @backstage/plugin-permission-react@0.4.35 + +## @backstage/plugin-devtools-backend@0.5.6 + +### Patch Changes + +- 9a99735: Removed the usage of `permissionIntegrationRouter` in favor of using the new `coreServices.permissionsRegistry` +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +## @backstage/plugin-events-backend@0.5.3 + +### Patch Changes + +- c83cd8b: Fixed some circular or otherwise unclear imports +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/backend-openapi-utils@0.5.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-events-backend-module-azure@0.2.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-events-backend-module-gerrit@0.2.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-events-backend-module-github@0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-events-backend-module-gitlab@0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.1 + +### Patch Changes + +- f983e99: Add an `EventConsumingGooglePubSubPublisher`, for pushing Backstage events to pubsub +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-events-backend-test-utils@0.1.45 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-events-node@0.4.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-gateway-backend@1.0.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + +## @backstage/plugin-home@0.8.9 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- 0ebad54: Fixed the `WelcomeTitle` to properly default to the previous value of `inherit` +- cef60db: Home plugin support i18n +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-home-react@0.1.27 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/core-compat-api@0.4.3 + - @backstage/theme@0.6.6 + +## @backstage/plugin-home-react@0.1.27 + +### Patch Changes + +- c83cd8b: Fixed some circular or otherwise unclear imports +- cef60db: Home plugin support i18n +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + +## @backstage/plugin-kubernetes@0.12.8 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- 3ea5d1b: chore(deps): Update deprecated xterm to new package under @xterm +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/plugin-kubernetes-react@0.5.8 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-permission-react@0.4.35 + +## @backstage/plugin-kubernetes-backend@0.19.7 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @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 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1 + +## @backstage/plugin-kubernetes-cluster@0.0.26 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/plugin-kubernetes-react@0.5.8 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-permission-react@0.4.35 + +## @backstage/plugin-kubernetes-node@0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5 + +## @backstage/plugin-kubernetes-react@0.5.8 + +### Patch Changes + +- 3ea5d1b: chore(deps): Update deprecated xterm to new package under @xterm +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5 + +## @backstage/plugin-notifications@0.5.6 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- b00c160: Remove React import form notification and scaffolder plugin +- 1fb5f06: Adds ability for user to turn on/off notifications for specific topics within an origin. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-notifications-common@0.0.9 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.14 + +## @backstage/plugin-notifications-backend@0.5.7 + +### Patch Changes + +- 41d4d6e: Notifications are now automatically deleted after 1 year by default. + + There is a new scheduled task that runs every 24 hours to delete notifications older than 1 year. + This can be configured by setting the `notifications.retention` in the `app-config.yaml` file. + + ```yaml + notifications: + retention: 1y + ``` + + If the retention is set to false, notifications will not be automatically deleted. + +- 8a150bf: Internal changes to switch to the non-alpha `catalogServiceRef` + +- 1fb5f06: Adds ability for user to turn on/off notifications for specific topics within an origin. + +- ef9ab82: Notifications API will now return user as null always for broadcast notifications + +- Updated dependencies + - @backstage/plugin-notifications-common@0.0.9 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-notifications-node@0.2.16 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-signals-node@0.1.21 + +## @backstage/plugin-notifications-backend-module-email@0.3.10 + +### Patch Changes + +- 8a150bf: Internal changes to switch to the non-alpha `catalogServiceRef` +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-notifications-common@0.0.9 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-notifications-node@0.2.16 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + +## @backstage/plugin-notifications-backend-module-slack@0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-common@0.0.9 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-notifications-node@0.2.16 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-notifications-common@0.0.9 + +### 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 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-notifications-common@0.0.9 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-signals-node@0.1.21 + +## @backstage/plugin-org@0.6.40 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- 4fe364f: Org plugin support i18n +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-org-react@0.1.39 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + +## @backstage/plugin-permission-backend@0.7.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1 + +## @backstage/plugin-permission-node@0.10.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0 + +## @backstage/plugin-permission-react@0.4.35 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.8 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-common@0.9.0 + +## @backstage/plugin-proxy-backend@0.6.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/types@1.2.1 + - @backstage/plugin-proxy-node@0.1.5 + +## @backstage/plugin-proxy-node@0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.10 + +### Patch Changes + +- e89d7b6: Migrating `azure` actions to using the new `zod` schema format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.11 + +### Patch Changes + +- 7f710d2: Migrating `bitbucket` actions to use the new `zod` format +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10 + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +### Patch Changes + +- ca9fdc0: Migrate `bitbucket-cloud` to new actions format +- 9c8ff0c: Update pull request creation filter to include .gitignore files in the created pull request +- 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.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +### Patch Changes + +- d8169fc: Migrate the actions to the new format +- 9c8ff0c: Update pull request creation filter to include .gitignore files in the created pull request +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +### Patch Changes + +- 4a86bca: Migrate actions to new format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.11 + +### Patch Changes + +- 0b2bbf1: Migrate using new actions format +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.10 + +### Patch Changes + +- e24b29b: Migrating to use new format for actions +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.10 + +### Patch Changes + +- ed41017: Migrate to new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.2 + +### Patch Changes + +- 063b2d3: Fixed dependency to frontend package from tests +- a296637: Migrate to new action format +- 3d6493a: Support merge request labels in publish:gitlab:merge-request +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.11 + +### Patch Changes + +- 1fb5f06: Adds ability for user to turn on/off notifications for specific topics within an origin. +- 3f56115: Removed `octokit` dependency as it was not being used +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/plugin-notifications-common@0.0.9 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-notifications-node@0.2.16 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.10 + +### Patch Changes + +- a579693: Migrate to new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +### Patch Changes + +- 6c6fb4a: Migrate to new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.11 + +### Patch Changes + +- b392a34: Migrate to new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node-test-utils@0.3.0 + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-search@1.4.27 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/plugin-search-react@1.9.1 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend@2.0.3 + +### Patch Changes + +- c83cd8b: Fixed some circular or otherwise unclear imports +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/backend-openapi-utils@0.5.4 + - @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 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-catalog@0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-explore@0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-pg@0.5.45 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-techdocs@0.4.3 + +### Patch Changes + +- b47ec38: Exports the default document collator for use in document transformation during search indexing. +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-node@1.13.4 + +## @backstage/plugin-search-backend-node@1.3.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @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-search-react@1.9.1 + +### Patch Changes + +- 869fa46: SearchBar clear button support i18n +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-signals@0.0.20 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.14 + +## @backstage/plugin-signals-backend@0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-signals-node@0.1.21 + +## @backstage/plugin-signals-node@0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-signals-react@0.0.14 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.8 + - @backstage/types@1.2.1 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.49 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.0 + - @backstage/plugin-catalog@1.31.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/plugin-techdocs-react@1.3.0 + - @backstage/plugin-search-react@1.9.1 + - @backstage/integration-react@1.2.8 + - @backstage/core-app-api@1.17.1 + - @backstage/test-utils@1.7.9 + +## @backstage/plugin-techdocs-backend@2.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.3 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/backend-plugin-api@1.4.0 + - @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-node@1.13.4 + +## @backstage/plugin-techdocs-common@0.1.1 + +### 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 + +### Patch Changes + +- 9dde3ba: Improved Keyboard accessibility in techdocs. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-techdocs-react@1.3.0 + - @backstage/integration-react@1.2.8 + - @backstage/integration@1.17.0 + +## @backstage/plugin-techdocs-node@1.13.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/backend-plugin-api@1.4.0 + - @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-user-settings@0.8.23 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- ab6d3dd: Add some translation to user-setting plugin +- c83cd8b: Fixed some circular or otherwise unclear imports +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-app-api@1.17.1 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.14 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.21 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.110 + +### Patch Changes + +- Updated dependencies + - @backstage/canon@0.5.0 + - @backstage/core-components@0.17.3 + - @backstage/cli@0.33.0 + - @backstage/plugin-scaffolder-react@1.17.0 + - @backstage/plugin-scaffolder@1.32.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18 + - @backstage/plugin-catalog-import@0.13.1 + - @backstage/plugin-catalog-graph@0.4.20 + - @backstage/plugin-notifications@0.5.6 + - @backstage/plugin-user-settings@0.8.23 + - @backstage/plugin-kubernetes@0.12.8 + - @backstage/plugin-api-docs@0.12.8 + - @backstage/plugin-devtools@0.1.28 + - @backstage/plugin-techdocs@1.13.0 + - @backstage/plugin-catalog@1.31.0 + - @backstage/plugin-signals@0.0.20 + - @backstage/plugin-search@1.4.27 + - @backstage/plugin-home@0.8.9 + - @backstage/plugin-org@0.6.40 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-app-api@0.11.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/plugin-techdocs-react@1.3.0 + - @backstage/plugin-search-react@1.9.1 + - @backstage/app-defaults@1.6.3 + - @backstage/integration-react@1.2.8 + - @backstage/plugin-auth-react@0.1.16 + - @backstage/plugin-kubernetes-cluster@0.0.26 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/theme@0.6.6 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-search-common@1.2.18 + +## example-app-next@0.0.24 + +### Patch Changes + +- Updated dependencies + - @backstage/canon@0.5.0 + - @backstage/core-components@0.17.3 + - @backstage/cli@0.33.0 + - @backstage/plugin-scaffolder-react@1.17.0 + - @backstage/plugin-scaffolder@1.32.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18 + - @backstage/plugin-app-visualizer@0.1.20 + - @backstage/plugin-catalog-import@0.13.1 + - @backstage/plugin-catalog-graph@0.4.20 + - @backstage/plugin-notifications@0.5.6 + - @backstage/plugin-user-settings@0.8.23 + - @backstage/plugin-kubernetes@0.12.8 + - @backstage/plugin-api-docs@0.12.8 + - @backstage/plugin-techdocs@1.13.0 + - @backstage/plugin-catalog@1.31.0 + - @backstage/plugin-signals@0.0.20 + - @backstage/plugin-search@1.4.27 + - @backstage/plugin-home@0.8.9 + - @backstage/plugin-app@0.1.10 + - @backstage/plugin-org@0.6.40 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/frontend-app-api@0.11.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/plugin-techdocs-react@1.3.0 + - @backstage/frontend-defaults@0.2.3 + - @backstage/plugin-search-react@1.9.1 + - @backstage/app-defaults@1.6.3 + - @backstage/integration-react@1.2.8 + - @backstage/plugin-auth-react@0.1.16 + - @backstage/plugin-kubernetes-cluster@0.0.26 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/core-compat-api@0.4.3 + - @backstage/theme@0.6.6 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-search-common@1.2.18 + +## app-next-example-plugin@0.0.24 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/frontend-plugin-api@0.10.3 + +## example-backend@0.0.39 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-backend@0.5.7 + - @backstage/plugin-scaffolder-backend-module-github@0.8.0 + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-scaffolder-backend@2.0.0 + - @backstage/plugin-catalog-backend@2.1.0 + - @backstage/plugin-devtools-backend@0.5.6 + - @backstage/plugin-mcp-actions-backend@0.1.0 + - @backstage/plugin-events-backend@0.5.3 + - @backstage/plugin-search-backend@2.0.3 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.1 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.11 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/plugin-search-backend-module-techdocs@0.4.3 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-kubernetes-backend@0.19.7 + - @backstage/plugin-search-backend-module-catalog@0.3.5 + - @backstage/plugin-techdocs-backend@2.0.3 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-app-backend@0.5.3 + - @backstage/plugin-auth-backend@0.25.1 + - @backstage/plugin-auth-backend-module-github-provider@0.3.4 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.9 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.3 + - @backstage/plugin-catalog-backend-module-openapi@0.2.11 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.1 + - @backstage/plugin-permission-backend@0.7.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.9 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/plugin-proxy-backend@0.6.3 + - @backstage/plugin-search-backend-module-explore@0.3.3 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-signals-backend@0.3.5 + +## e2e-test@0.2.29 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.0 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## @internal/frontend@0.0.10 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @internal/scaffolder@0.0.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.17.0 + - @backstage/frontend-plugin-api@0.10.3 + +## techdocs-cli-embedded-app@0.2.109 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/cli@0.33.0 + - @backstage/plugin-techdocs@1.13.0 + - @backstage/plugin-catalog@1.31.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-techdocs-react@1.3.0 + - @backstage/app-defaults@1.6.3 + - @backstage/integration-react@1.2.8 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/test-utils@1.7.9 + - @backstage/theme@0.6.6 + +## yarn-plugin-backstage@0.0.6 + +### Patch Changes + +- a0f9e4e: added functionality so that adding or updating a backstage dependency to a package would maintain the "backstage:^" placeholder for the version. +- Updated dependencies + - @backstage/cli-common@0.1.15 + - @backstage/release-manifests@0.0.13 + +## @internal/plugin-todo-list@1.0.40 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + +## @internal/plugin-todo-list-backend@1.0.40 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 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/releases/v1.40.0-next.2-changelog.md b/docs/releases/v1.40.0-next.2-changelog.md new file mode 100644 index 0000000000..ed14cbf003 --- /dev/null +++ b/docs/releases/v1.40.0-next.2-changelog.md @@ -0,0 +1,1539 @@ +# Release v1.40.0-next.2 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.40.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.40.0-next.2) + +## @backstage/plugin-scaffolder-backend@2.0.0-next.2 + +### Major Changes + +- 5863b04: **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, + }), + ); + }, + }); + }, + }); + ``` + +### Patch Changes + +- 89a941d: Migrating to latest action format + +- 023629e: Enable usage of secrets within 'each' step of software templates. For example, you can now structure your `each` step like this: + + each: + [ + { name: "Service1", token: "${{ secrets.token1 }}" }, + { name: "Service2", token: "${{ secrets.token2 }}" }, + ] + +- e92e481: Add tests for Scaffolder + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.2-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.8.0-next.2 + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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-auth-node@0.6.4-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9-next.1 + - @backstage/plugin-catalog-node@1.17.1-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-backend-module-azure@0.2.10-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.11-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.2 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.10-next.2 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.10-next.2 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/backend-defaults@0.11.0-next.2 + +### Minor Changes + +- 3ccb7fc: Enhanced error handling in the auditor service factory to pass errors as objects. Aligned WinstonRootAuditorService with the default service factory's error handling. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@1.2.4-next.2 + - @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-auth-node@0.6.4-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## @backstage/cli@0.33.0-next.1 + +### Minor Changes + +- eef0e83: Internal update to promote the modular CLI entrypoint to stable. + +### Patch Changes + +- d07fe35: 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. +- d6d63c7: Updating the scaffolder action boilerplate to use new `zod` schema +- e36e855: Added `backstage.pluginId` field in `package.json` to all default plugin package templates for the `new` command. +- 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/plugin-catalog@1.31.0-next.2 + +### Minor Changes + +- 406acb6: 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`). + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@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-techdocs-common@0.1.1-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.0-next.2 + +### Minor Changes + +- 8a150bf: **BREAKING**: `BitbucketCloudEntityProvider` now accepts a `CatalogService` instead of a `CatalogApi`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.0-next.2 + +### Minor Changes + +- 42bb3b8: **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. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @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-catalog-node@1.17.1-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-catalog-react@1.19.0-next.2 + +### Minor Changes + +- 406acb6: 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. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-test-utils@0.3.3-next.1 + - @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-scaffolder@1.32.0-next.2 + +### Minor Changes + +- 3c59ece: **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. + +### Patch Changes + +- b00c160: Remove React import form notification and scaffolder plugin +- 95a1d72: Added appropriate message when global templating function metadata is absent. +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-scaffolder-react@1.16.1-next.2 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/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-github@0.8.0-next.2 + +### Minor Changes + +- 5863b04: **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, + }), + ); + }, + }); + }, + }); + ``` + +### Patch Changes + +- 575c76b: Migrate to using new actions +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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-node@1.17.1-next.1 + +## @backstage/plugin-scaffolder-node@0.9.0-next.2 + +### Minor Changes + +- 5863b04: **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. + +### Patch Changes + +- 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.3.0-next.2 + +### Minor Changes + +- 3cea7ee: **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. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @backstage/backend-test-utils@1.6.0-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/types@1.2.1 + +## @backstage/plugin-techdocs@1.13.0-next.2 + +### Minor Changes + +- 3c59ece: **New Frontend System Only:** + The `TechDocs` plugin is now responsible for providing an entity icon link extension to read documentation from the catalog entity page. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-auth-react@0.1.16-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/theme@0.6.6 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + +## @backstage/app-defaults@1.6.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-app-api@1.17.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/theme@0.6.6 + - @backstage/plugin-permission-react@0.4.34 + +## @backstage/backend-app-api@1.2.4-next.2 + +### Patch Changes + +- bb9a501: Fixed a bug where occasionally the initialization order of multiple modules consuming a single extension point could happen in the wrong order. +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/backend-dynamic-feature-service@0.7.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/plugin-catalog-backend@2.0.1-next.2 + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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-auth-node@0.6.4-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-test-utils@1.6.0-next.2 + +### Patch Changes + +- 12c1fd4: Make the `user` credentials mock behave more like production +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/backend-app-api@1.2.4-next.2 + - @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-auth-node@0.6.4-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/canon@0.5.0-next.2 + +### Patch Changes + +- 44df879: Add min-width: 0; by default on every Flex components in Canon to help support truncated texts inside flex elements. +- ee6ffe6: Fix styling for the title4 prop on the Heading component in Canon. +- f2f814a: Added a render prop to the Button component in Canon to use it as a link. + +## @backstage/core-compat-api@0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/core-plugin-api@1.10.7 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-components@0.17.3-next.0 + +### Patch Changes + +- 6232160: table actions header support i18n +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/version-bridge@1.0.11 + +## @backstage/create-app@0.7.0-next.2 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.15 + +## @backstage/dev-utils@1.1.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/app-defaults@1.6.3-next.0 + - @backstage/integration-react@1.2.7 + - @backstage/catalog-model@1.7.4 + - @backstage/core-app-api@1.17.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/theme@0.6.6 + +## @backstage/frontend-app-api@0.11.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-defaults@0.2.3-next.1 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-defaults@0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/plugin-app@0.1.10-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/frontend-app-api@0.11.3-next.1 + +## @backstage/frontend-dynamic-feature-loader@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/config@1.3.2 + +## @backstage/frontend-plugin-api@0.10.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-test-utils@0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/plugin-app@0.1.10-next.1 + - @backstage/config@1.3.2 + - @backstage/frontend-app-api@0.11.3-next.1 + - @backstage/test-utils@1.7.8 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/repo-tools@0.14.0-next.2 + +### Patch Changes + +- 4bff5d0: Fixed a bug where linting would fail with the generated clients when defining top-level `enum` schema values. +- 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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.4-next.1 + +## @backstage/plugin-api-docs@0.12.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog@1.31.0-next.2 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.34 + +## @backstage/plugin-app@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/core-plugin-api@1.10.7 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-react@0.4.34 + +## @backstage/plugin-app-visualizer@0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/core-plugin-api@1.10.7 + +## @backstage/plugin-auth-react@0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog-backend@2.0.1-next.2 + +### Patch Changes + +- 2cac8b0: You can now specify an optional value when applying the `HAS_LABEL` permission rule, similar to the `HAS_ANNOTATION` permission rule. +- Updated dependencies + - @backstage/backend-openapi-utils@0.5.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-node@1.17.1-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-catalog-backend-module-aws@0.4.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @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-catalog-node@1.17.1-next.1 + - @backstage/plugin-kubernetes-common@0.9.5 + +## @backstage/plugin-catalog-backend-module-github@0.9.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.1-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend-module-github@0.9.1-next.2 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.7.0-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/plugin-catalog-backend@2.0.1-next.2 + - @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-node@1.17.1-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-permission-common@0.9.0 + +## @backstage/plugin-catalog-backend-module-logs@0.1.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.1-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-catalog-graph@0.4.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-catalog-import@0.13.1-next.2 + +### Patch Changes + +- 5d7c539: Catalog import plugin full support i18n +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + +## @backstage/plugin-config-schema@0.1.69-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-devtools@0.1.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.16 + - @backstage/plugin-permission-react@0.4.34 + +## @backstage/plugin-devtools-backend@0.5.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @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-home@0.8.9-next.2 + +### Patch Changes + +- 0ebad54: Fixed the `WelcomeTitle` to properly default to the previous value of `inherit` +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/plugin-home-react@0.1.27-next.0 + - @backstage/catalog-client@1.10.1-next.0 + - @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.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/theme@0.6.6 + +## @backstage/plugin-home-react@0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-plugin-api@1.10.7 + +## @backstage/plugin-kubernetes@0.12.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/plugin-kubernetes-react@0.5.8-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-permission-react@0.4.34 + +## @backstage/plugin-kubernetes-cluster@0.0.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-kubernetes-react@0.5.8-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-permission-react@0.4.34 + +## @backstage/plugin-kubernetes-react@0.5.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5 + +## @backstage/plugin-notifications@0.5.6-next.2 + +### Patch Changes + +- b00c160: Remove React import form notification and scaffolder plugin +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-signals-react@0.0.13 + +## @backstage/plugin-notifications-backend@0.5.7-next.2 + +### Patch Changes + +- 8a150bf: Internal changes to switch to the non-alpha `catalogServiceRef` +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.1 + - @backstage/plugin-signals-node@0.1.21-next.1 + +## @backstage/plugin-notifications-backend-module-email@0.3.10-next.2 + +### Patch Changes + +- 8a150bf: Internal changes to switch to the non-alpha `catalogServiceRef` +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.1 + +## @backstage/plugin-org@0.6.40-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-org-react@0.1.39-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.7 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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-gitlab@0.9.2-next.2 + +### Patch Changes + +- a296637: Migrate to new action format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.2 + +### Patch Changes + +- 3f56115: Removed `octokit` dependency as it was not being used +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.10-next.2 + +### Patch Changes + +- a579693: Migrate to new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.2 + +### Patch Changes + +- 6c6fb4a: Migrate to new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.2 + +### Patch Changes + +- b392a34: Migrate to new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node-test-utils@0.3.0-next.2 + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-react@1.16.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.7 + - @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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend@2.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @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-react@1.9.1-next.1 + +### Patch Changes + +- 869fa46: SearchBar clear button support i18n +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/core-plugin-api@1.10.7 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-signals@0.0.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/core-plugin-api@1.10.7 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.13 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.49-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.0-next.2 + - @backstage/plugin-techdocs@1.13.0-next.2 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/core-app-api@1.17.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/test-utils@1.7.8 + +## @backstage/plugin-techdocs-backend@2.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.3-next.1 + - @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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/core-plugin-api@1.10.7 + - @backstage/integration@1.17.0 + +## @backstage/plugin-techdocs-react@1.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + +## @backstage/plugin-user-settings@0.8.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-app-api@1.17.0 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-signals-node@0.1.21-next.1 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.110-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/cli@0.33.0-next.1 + - @backstage/canon@0.5.0-next.2 + - @backstage/plugin-notifications@0.5.6-next.2 + - @backstage/plugin-scaffolder@1.32.0-next.2 + - @backstage/plugin-catalog@1.31.0-next.2 + - @backstage/plugin-techdocs@1.13.0-next.2 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-catalog-import@0.13.1-next.2 + - @backstage/plugin-home@0.8.9-next.2 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/app-defaults@1.6.3-next.0 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-api-docs@0.12.8-next.2 + - @backstage/plugin-auth-react@0.1.16-next.0 + - @backstage/plugin-catalog-graph@0.4.20-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.2 + - @backstage/plugin-devtools@0.1.28-next.2 + - @backstage/plugin-kubernetes@0.12.8-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.2 + - @backstage/plugin-org@0.6.40-next.2 + - @backstage/plugin-scaffolder-react@1.16.1-next.2 + - @backstage/plugin-search@1.4.27-next.2 + - @backstage/plugin-signals@0.0.20-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.2 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/plugin-user-settings@0.8.23-next.2 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-app-api@0.11.3-next.1 + - @backstage/theme@0.6.6 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-search-common@1.2.18 + +## example-app-next@0.0.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/cli@0.33.0-next.1 + - @backstage/canon@0.5.0-next.2 + - @backstage/plugin-notifications@0.5.6-next.2 + - @backstage/plugin-scaffolder@1.32.0-next.2 + - @backstage/plugin-catalog@1.31.0-next.2 + - @backstage/plugin-techdocs@1.13.0-next.2 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-catalog-import@0.13.1-next.2 + - @backstage/plugin-home@0.8.9-next.2 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/app-defaults@1.6.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-api-docs@0.12.8-next.2 + - @backstage/plugin-app@0.1.10-next.1 + - @backstage/plugin-app-visualizer@0.1.20-next.1 + - @backstage/plugin-auth-react@0.1.16-next.0 + - @backstage/plugin-catalog-graph@0.4.20-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.2 + - @backstage/plugin-kubernetes@0.12.8-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.2 + - @backstage/plugin-org@0.6.40-next.2 + - @backstage/plugin-scaffolder-react@1.16.1-next.2 + - @backstage/plugin-search@1.4.27-next.2 + - @backstage/plugin-signals@0.0.20-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.2 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/plugin-user-settings@0.8.23-next.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.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-app-api@0.11.3-next.1 + - @backstage/frontend-defaults@0.2.3-next.1 + - @backstage/theme@0.6.6 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-search-common@1.2.18 + +## app-next-example-plugin@0.0.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + +## example-backend@0.0.39-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-github@0.8.0-next.2 + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/plugin-scaffolder-backend@2.0.0-next.2 + - @backstage/plugin-notifications-backend@0.5.7-next.2 + - @backstage/plugin-catalog-backend@2.0.1-next.2 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.11-next.2 + - @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-auth-node@0.6.4-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.2 + - @backstage/plugin-events-backend@0.5.3-next.1 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.1-next.1 + - @backstage/plugin-kubernetes-backend@0.19.7-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.2 + - @backstage/plugin-search-backend-module-catalog@0.3.5-next.1 + - @backstage/plugin-search-backend-module-explore@0.3.3-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.3-next.1 + - @backstage/plugin-search-backend-node@1.3.12-next.1 + - @backstage/plugin-signals-backend@0.3.5-next.1 + - @backstage/plugin-techdocs-backend@2.0.3-next.2 + +## e2e-test@0.2.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.0-next.2 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## @internal/frontend@0.0.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @internal/scaffolder@0.0.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/plugin-scaffolder-react@1.16.1-next.2 + +## techdocs-cli-embedded-app@0.2.109-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/cli@0.33.0-next.1 + - @backstage/plugin-catalog@1.31.0-next.2 + - @backstage/plugin-techdocs@1.13.0-next.2 + - @backstage/app-defaults@1.6.3-next.0 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/test-utils@1.7.8 + - @backstage/theme@0.6.6 + +## @internal/plugin-todo-list@1.0.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-plugin-api@1.10.7 diff --git a/docs/releases/v1.40.0-next.3-changelog.md b/docs/releases/v1.40.0-next.3-changelog.md new file mode 100644 index 0000000000..6dfc2aa342 --- /dev/null +++ b/docs/releases/v1.40.0-next.3-changelog.md @@ -0,0 +1,166 @@ +# Release v1.40.0-next.3 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.40.0-next.3](https://backstage.github.io/upgrade-helper/?to=1.40.0-next.3) + +## @backstage/plugin-events-backend-module-kafka@0.1.0-next.0 + +### Minor Changes + +- b034b9d: Adds a new module `kafka` for plugin-events-backend + + The module introduces the `KafkaConsumerClient` which creates a Kafka client used to establish consumer connections. It also provides the `KafkaConsumingEventPublisher`, a consumer that subscribes to configured Kafka topics and publishes received messages to the Event Service. + +### 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/cli@0.33.0-next.2 + +### Patch Changes + +- 8a0164c: Fix an issue where some commands were not usable because of missing dist files +- Updated dependencies + - @backstage/eslint-plugin@0.1.11-next.0 + - @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/integration@1.17.0 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.1 + +## @backstage/create-app@0.7.0-next.3 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.15 + +## @backstage/eslint-plugin@0.1.11-next.0 + +### Patch Changes + +- 098ef95: Fix custom rules package scanning performance. + +## example-app@0.2.110-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.33.0-next.2 + - @backstage/app-defaults@1.6.3-next.0 + - @backstage/canon@0.5.0-next.2 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-app-api@0.11.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/plugin-api-docs@0.12.8-next.2 + - @backstage/plugin-auth-react@0.1.16-next.0 + - @backstage/plugin-catalog@1.31.0-next.2 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-graph@0.4.20-next.2 + - @backstage/plugin-catalog-import@0.13.1-next.2 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.2 + - @backstage/plugin-devtools@0.1.28-next.2 + - @backstage/plugin-home@0.8.9-next.2 + - @backstage/plugin-kubernetes@0.12.8-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.2 + - @backstage/plugin-notifications@0.5.6-next.2 + - @backstage/plugin-org@0.6.40-next.2 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-scaffolder@1.32.0-next.2 + - @backstage/plugin-scaffolder-react@1.16.1-next.2 + - @backstage/plugin-search@1.4.27-next.2 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/plugin-signals@0.0.20-next.1 + - @backstage/plugin-techdocs@1.13.0-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.2 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/plugin-user-settings@0.8.23-next.2 + +## example-app-next@0.0.24-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.33.0-next.2 + - @backstage/app-defaults@1.6.3-next.0 + - @backstage/canon@0.5.0-next.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.2 + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-app-api@0.11.3-next.1 + - @backstage/frontend-defaults@0.2.3-next.1 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/plugin-api-docs@0.12.8-next.2 + - @backstage/plugin-app@0.1.10-next.1 + - @backstage/plugin-app-visualizer@0.1.20-next.1 + - @backstage/plugin-auth-react@0.1.16-next.0 + - @backstage/plugin-catalog@1.31.0-next.2 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-graph@0.4.20-next.2 + - @backstage/plugin-catalog-import@0.13.1-next.2 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.2 + - @backstage/plugin-home@0.8.9-next.2 + - @backstage/plugin-kubernetes@0.12.8-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.2 + - @backstage/plugin-notifications@0.5.6-next.2 + - @backstage/plugin-org@0.6.40-next.2 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-scaffolder@1.32.0-next.2 + - @backstage/plugin-scaffolder-react@1.16.1-next.2 + - @backstage/plugin-search@1.4.27-next.2 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/plugin-signals@0.0.20-next.1 + - @backstage/plugin-techdocs@1.13.0-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.2 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/plugin-user-settings@0.8.23-next.2 + +## e2e-test@0.2.29-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.0-next.3 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## techdocs-cli-embedded-app@0.2.109-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.33.0-next.2 + - @backstage/app-defaults@1.6.3-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.3-next.0 + - @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-catalog@1.31.0-next.2 + - @backstage/plugin-techdocs@1.13.0-next.2 + - @backstage/plugin-techdocs-react@1.3.0-next.1 diff --git a/docs/releases/v1.40.0.md b/docs/releases/v1.40.0.md new file mode 100644 index 0000000000..befaddfd66 --- /dev/null +++ b/docs/releases/v1.40.0.md @@ -0,0 +1,200 @@ +--- +id: v1.40.0 +title: v1.40.0 +description: Backstage Release v1.40.0 +--- + +These are the release notes for the v1.40.0 release of [Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done. + +## Highlights + +### Scaffolder 2.0 Breaking Changes + +The deprecated legacy action formats have been removed and replaced with the new native `zod` formats. + +This also cleaned up some deprecated `logStream` and `WinstonLogger` usages in the old patterns. + +```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' }) + } + } +}) + +``` + +:::note + +As the new `zod` format is more strict if there are any additional properties in your Software Template YAML where you define your action it will trigger an `InputError`. Here is an exact example for reference: + +> "InputError: Invalid input passed to action publish:github, instance is not allowed to have the additional property "allowedHosts" + +To fix these you will simply need to remove the extra property details in your Software Template YAML where you define your action. + +::: + +The `scaffolder-backend` plugin has been converted to being New Backend System only, which means a lot of the public API has been cleaned up. + +The `createRouter` and `createBuiltinActions` have been removed as they were only used in the legacy backend system. + +A lot of deprecated types which were re-exported in the `-backend` package from `-common` and `-node` have been removed, and can be fixed by importing them from the correct package. + +The deprecated `copyWithoutRender` option has been removed from `fetch:template` and should be renamed to `copyWithoutTemplating`. + +The re-exported action creators from the provider packages such as `createPublishAzureAction` and `createPublishGithubAction` should now be imported from the provider modules themselves. + +As always you can head over the latest changelogs for [`@backstage/plugin-scaffolder-backend`](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/CHANGELOG.md) and [`@backstage/plugin-scaffolder-node`](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-node/CHANGELOG.md) +for more detailed information. + +There’s also a number of new deprecation for types which are currently exported from `plugin-scaffolder-node`. We’re going to be looking at re-working the Scaffolder architecture in the future, so we’re going to be removing these types in a future version to iterate on the new API surfaces. Stay tuned for more updates! + +### Actions Registry + +We’ve added two new `alpha` services which can be used to define distributed Actions across Backstage. Plugins can use the `ActionsRegistry` to define actions that are automatically installed with their associated plugin. + +On top of this Actions Registry is a new `mcp-actions` backend plugin, which will surface these actions as tools as an MCP server. This can be used to expose plugin Actions with your favourite AI tools, such as Cursor, Claude or ChatGPT. + +You can find out more information about the `ActionsRegistry` and MCP integration in [this RFC](https://github.com/backstage/backstage/issues/30218). + +We’re actively working on this area, therefore it’s currently highly experimental and could be subject to breaking changes in future releases. However we’d really appreciate some feedback if you want to try and create some Actions for your plugins and surface them in an MCP server! + +In the next release we’ll be looking to integrate these Actions with the Scaffolder allowing you to call these actions from a template. + +### TechDocs deep linking + +Introduced `backstage.io/techdocs-entity-path` annotation which allows deep linking into other TechDocs in conjunction with `backstage.io/techdocs-entity`. + +Contributed by [@csuich2](https://github.com/csuich2) in [#29760](https://github.com/backstage/backstage/pull/29760) + +### Catalog modules Breaking Changes + +- `BitbucketCloudEntityProvider` now accepts a `CatalogService` instead of `CatalogApi` +- User and Group discovery for GitLab 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. + +### New Module drop: `@backstage/plugin-events-backend-module-kafka` + +The module introduces the `KafkaConsumerClient` which creates a Kafka client used to establish consumer connections. It also provides the `KafkaConsumingEventPublisher`, a consumer that subscribes to configured Kafka topics and publishes received messages to the Backstage events service where Backstage plugins can access them. + +Contributed by [@Jonas-Beck](https://github.com/Jonas-Beck) in [#29315](https://github.com/backstage/backstage/pull/29315) + +### New lint rule added + +A new lint rule `@backstage/no-mixed-plugin-imports` was added to the defaults. This enforces rules that were always true but until now were implicit: that you should not import across frontend and backend package boundaries, isomorphic packages should not import non-isomorphic, etc. + +This rule is initially only set at a warning level, to give you some time to react. You may see warnings appear in your build output that were not there before - do pay attention to them, because they may expose some issues that you had not yet noticed. + +We intend to make this rule be at an error level in a future release. + +The rule has an option `excludedTargetPackages` that lets you opt out of enforcement in specific packages. You can put something like this in your root `.eslintrc.js`: + +```js +module.exports = { + root: true, + plugins: ['@spotify', 'react', 'testing-library', '@backstage'], + rules: { + '@backstage/no-mixed-plugin-imports': [ + 'error', // if you want to opt in to strict checks early + { + excludedTargetPackages: [ + '@internal/plugin-foo', + // ... +``` + +Contributed by [@drodil](https://github.com/drodil) in [#30227](https://github.com/backstage/backstage/pull/30227) + +### CLI: movement toward `rspack` + +The `BACKSTAGE_CLI_EXPERIMENTAL_BUILD_CACHE` flag has been removed entirely. If you were using this flag, we encourage you to switch to `EXPERIMENTAL_RSPACK` instead. In the near future, we hope to make `rspack` the default out of the box. + +While making these changes, the experimental `FORCE_REACT_DEVELOPMENT` flag was removed as well. + +### Backend rate limiting + +The backend system now has builtin support for rate limiting of incoming requests, both for your entire backends and on a per-plugin basis. It can use different backing stores for its state, and in-memory and redis is currently implemented out of the box. + +You can read more about this opt-in feature in [the documentation](https://backstage.io/docs/backend-system/core-services/http-router) for the HTTP router service. + +Contributed by [@drodil](https://github.com/drodil) in [#28708](https://github.com/backstage/backstage/pull/28708) + +### New Frontend System plugin info + +Plugins in the new frontend system can now expose rich, extensible metadata about themselves. This data is accessible at runtime inside your app. This is for example useful for adding contact and support information, and much more - and you can even overlay your own data on top of what is provided by open-source plugins. + +You can read more about this in the [frontend app](https://backstage.io/docs/frontend-system/architecture/app) and [plugin](https://backstage.io/docs/frontend-system/architecture/plugins) architecture documentation. + +### Notifications retention + +Notifications that are stored by the notifications backend plugin now have a default retention of one year before being deleted. This helps keep the storage size in check over time. You can override this using the `notifications.retention` duration setting in your app-config. + +Contributed by [@drodil](https://github.com/drodil) in [#30206](https://github.com/backstage/backstage/pull/30206) + +### Lots of i18n work + +A bunch of work was done to enable i18n (translations) in several plugins! This affected at least the following: + +- core components +- the org plugin +- the catalog import plugin +- the home plugin +- the search plugin +- the user settings plugin + +Contributed by [@mario-mui](https://github.com/mario-mui) + +### Canon steams ahead + +While it’s still early days, we still want to call out that the canon design system work steams ahead with various controls added and improvements made. Hang tight! + +## Security Fixes + +This release does not contain any security fixes. + +## Upgrade path + +We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). + +## Links and References + +Below you can find a list of links and references to help you learn about and start using this new release. + +- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/) +- [GitHub repository](https://github.com/backstage/backstage) +- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy) +- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support +- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.40.0-changelog.md) +- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) + +Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.41.0-changelog.md b/docs/releases/v1.41.0-changelog.md new file mode 100644 index 0000000000..d8c5d866f2 --- /dev/null +++ b/docs/releases/v1.41.0-changelog.md @@ -0,0 +1,2258 @@ +# Release v1.41.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.41.0](https://backstage.github.io/upgrade-helper/?to=1.41.0) + +## @backstage/plugin-catalog-backend@3.0.0 + +### Major Changes + +- 5127ebe: **BREAKING**: The default `catalog.stitchingStrategy` has been switched to `{ mode: 'deferred' }`. + +- d675d96: **BREAKING**: The relations compatibility mode is no longer enabled by default, and the `disableRelationsCompatiblity` flag has been removed. To re-enable relations compatibility, the new `enableRelationsCompatibility` flag can be used instead. + +- 2339363: **BREAKING:** The experimental `catalog.useUrlReadersSearch` configuration flag (introduced in v1.36) has been removed. + + The `UrlReaderProcessor` now always uses the `search` method of `UrlReaders`. Built-in `UrlReaderService` implementations have been updated accordingly. + If you use custom `UrlReaderService` implementations, you need to adapt their `search` method to correctly handle both specific URLs and potential + search patterns (see changes on built-in readers [in the original PR](https://github.com/backstage/backstage/pull/28379/files#diff-68b0452f173ee54bdd40f7b5e047a9cb8bb59200425622c212c217b76dac1d1b)). + + Previous behavior was to call the `search` method only if the parsed Git URL's filename contained a wildcard and use `readUrl` otherwise. Each `UrlReaderService` must implement this logic in the `search` method instead. + + This allows each `UrlReaderService` implementation to check whether it's a search URL (that contains a wildcard pattern) or not using logic that is specific to each provider. + +- 687bfc8: **BREAKING**: The default `catalog.orphanStrategy` has been switched to `'delete'`. + +- 5de7a9d: **BREAKING**: The default `catalog.orphanProviderStrategy` has been switched to `'delete'`. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/integration@1.17.1 + - @backstage/backend-openapi-utils@0.5.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/backend-test-utils@1.7.0 + +### Minor Changes + +- ead925a: Add a standard `toString` on credentials objects + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/backend-defaults@0.11.1 + - @backstage/backend-app-api@1.2.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/canon@0.6.0 + +### Minor Changes + +- 1d64db6: **Breaking changes** We are updating our Link component to use React Aria under the hood. To match their API we are updating the `to` prop to `href` to match both internal and external routing. We are also updating our variant naming to include all our new font sizes. + +- 83fd7f4: **Breaking change** We are moving the Select component to use React Aria under the hood. We updated most props and events according to their underlying API. + +- cae63df: **Breaking changes** The Tabs components has been updates to use React Aria under the hood and to work with react-router-dom directly. + +- 4c6d891: **BREAKING CHANGES** + + We’re updating our Button component to provide better support for button links. + + - We’re introducing a new `ButtonLink` component, which replaces the previous render prop pattern. + - To maintain naming consistency across components, `IconButton` is being renamed to `ButtonIcon`. + - Additionally, the render prop will be removed from all button-related components. + + These changes aim to simplify usage and improve clarity in our component API. + +- 2e30459: We are moving our Tooltip component to use React Aria under the hood. In doing so, the structure of the component and its prop are changing to follow the new underlying structure. + +- 8fd6fcb: We are renaming @backstage/canon into @backstage/ui. As part of this move we are renaming all class names and CSS variables to follow the new name. "--canon" prefix is becoming "--bui" and all component class names starting with ".canon" will now start with ".bui" + +### Patch Changes + +- 140f652: We are consolidating all css files into a single styles.css in Canon. +- 76255b8: Add new Card component to Canon. +- 8154fb9: Add new SearchField component in Canon +- b0a6c8e: Add new Header component to Canon. +- 6910892: Add new `RadioGroup` + `Radio` component to Canon +- 9c17305: Fix scrolling width and height on ScrollArea component in Canon. +- 390ea20: Export Card and Skeleton components. +- be76576: Improve Button, ButtonIcon and ButtonLink styling in Canon. +- 17beb9b: Update return types for Heading & Text components for React 19. +- a8a8514: We are transforming how we structure our class names and data attributes definitions for all components. They are now all set in the same place. +- 667b951: Added placeholder prop to TextField component. +- eac4a4c: Add new tertiary variant to Button, ButtonIcon and ButtonLink in Canon. +- e71333a: adding export for ButtonLink so it's importable +- 8f2e82d: Add new Skeleton component in Canon +- Updated dependencies + - @backstage/ui@0.6.0 + +## @backstage/core-app-api@1.18.0 + +### Minor Changes + +- 5ddc0fe: if session exists and refresh fails, then create a new session if not instant popup + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + +## @backstage/repo-tools@0.15.0 + +### Minor Changes + +- 1e7082e: Clear output directory before running `package-docs` and skip all internal packages. + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/ui@0.6.0 + +### Minor Changes + +- e92bb9b: Canon has been renamed to Backstage UI. This means that `@backstage/canon` has been deprecated and replaced by `@backstage/ui`. + +## @backstage/plugin-scaffolder@1.33.0 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- 289e4a1: Filter MultiEntityPicker options based on rendered option value +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-scaffolder-react@1.18.0 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-scaffolder-common@1.6.0 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-scaffolder-backend@2.1.0 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- 424610a: Scaffolder audit Log now includes taskId and createdBy +- dbde180: An internal refactor which adds additional types to experimental checkpoints +- fc70b43: Replaced deprecated uses of `@backstage/backend-common` with the equivalents in `@backstage/backend-defaults` and `@backstage/backend-plugin-api`. +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-defaults@0.11.1 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/plugin-scaffolder-backend-module-github@0.8.1 + - @backstage/plugin-scaffolder-common@1.6.0 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-bitbucket-cloud-common@0.3.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.10 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.11 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.12 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.11 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.11 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.3 + +## @backstage/plugin-scaffolder-common@1.6.0 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + +## @backstage/plugin-scaffolder-node@0.10.0 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- dbde180: An internal refactor which adds additional types to experimental checkpoints +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + - @backstage/integration@1.17.1 + - @backstage/plugin-scaffolder-common@1.6.0 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-scaffolder-react@1.18.0 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- 94c11a5: Scroll to the top of the page when navigating between steps in template forms. +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/plugin-scaffolder-common@1.6.0 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/app-defaults@1.6.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/core-app-api@1.18.0 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/backend-app-api@1.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/backend-defaults@0.11.1 + +### Patch Changes + +- ead925a: Add a standard `toString` on credentials objects +- e0189b8: UrlReader: Fix handling of access tokens for GitLab readURL requests +- d1e4a6d: Fixed bug where the GitLab user token and GitLab integration token were being merged together +- Updated dependencies + - @backstage/config-loader@1.10.2 + - @backstage/config@1.3.3 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/integration@1.17.1 + - @backstage/backend-app-api@1.2.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/backend-dynamic-feature-service@0.7.2 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- 3d61c36: Fix wrong imports which lead to module initialization failures when enabling dynamic plugins. +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0 + - @backstage/config-loader@1.10.2 + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/backend-defaults@0.11.1 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/backend-openapi-utils@0.5.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-app-node@0.1.35 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-events-backend@0.5.4 + - @backstage/plugin-events-node@0.4.13 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/backend-openapi-utils@0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/backend-plugin-api@1.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/catalog-client@1.10.2 + +### Patch Changes + +- 6fb4143: allow arrays in the InMemoryCatalogClient to filter entities +- Updated dependencies + - @backstage/catalog-model@1.7.5 + +## @backstage/catalog-model@1.7.5 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup + +## @backstage/cli@0.33.1 + +### Patch Changes + +- 50f0ce6: Fixes a module not found error when running `backstage-cli info`. +- Updated dependencies + - @backstage/config-loader@1.10.2 + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/integration@1.17.1 + +## @backstage/config@1.3.3 + +### Patch Changes + +- ff23618: Loosen the requirements for a key to be considered valid config. +- 3507fcd: Just some more circular dep cleanup + +## @backstage/config-loader@1.10.2 + +### Patch Changes + +- ff23618: Loosen the requirements for a key to be considered valid config. +- Updated dependencies + - @backstage/config@1.3.3 + +## @backstage/core-compat-api@0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/core-components@0.17.4 + +### Patch Changes + +- f6ffea6: Add optional message field for auth providers. This is intended to be a user friendly message that displays in the OAuth request dialog. A default message will be displayed if one is not provided. +- aa3b054: Added `signIn` and `signOut` analytic events to the `@backstage/core-components` of sign in and sign out. +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + +## @backstage/core-plugin-api@1.10.9 + +### Patch Changes + +- f6ffea6: Add optional message field for auth providers. This is intended to be a user friendly message that displays in the OAuth request dialog. A default message will be displayed if one is not provided. +- Updated dependencies + - @backstage/config@1.3.3 + +## @backstage/create-app@0.7.1 + +### Patch Changes + +- 952cefe: Bumped create-app version. + +- 2339363: **BREAKING:** The experimental `catalog.useUrlReadersSearch` configuration flag (introduced in v1.36) has been removed. + + The `UrlReaderProcessor` now always uses the `search` method of `UrlReaders`. Built-in `UrlReaderService` implementations have been updated accordingly. + If you use custom `UrlReaderService` implementations, you need to adapt their `search` method to correctly handle both specific URLs and potential + search patterns (see changes on built-in readers [in the original PR](https://github.com/backstage/backstage/pull/28379/files#diff-68b0452f173ee54bdd40f7b5e047a9cb8bb59200425622c212c217b76dac1d1b)). + + Previous behavior was to call the `search` method only if the parsed Git URL's filename contained a wildcard and use `readUrl` otherwise. Each `UrlReaderService` must implement this logic in the `search` method instead. + + This allows each `UrlReaderService` implementation to check whether it's a search URL (that contains a wildcard pattern) or not using logic that is specific to each provider. + +- c4a0830: Removed deprecated 'allowedHosts' field from GitHub Publish action input in default template files to fix validation errors with strict Zod validation. + +## @backstage/dev-utils@1.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/core-app-api@1.18.0 + - @backstage/integration-react@1.2.9 + - @backstage/app-defaults@1.6.4 + +## @backstage/frontend-app-api@0.11.4 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-app-api@1.18.0 + - @backstage/frontend-defaults@0.2.4 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/frontend-defaults@0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/frontend-app-api@0.11.4 + - @backstage/plugin-app@0.1.11 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/frontend-dynamic-feature-loader@0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/frontend-plugin-api@0.10.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + +## @backstage/frontend-test-utils@0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/frontend-app-api@0.11.4 + - @backstage/plugin-app@0.1.11 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/test-utils@1.7.10 + +## @backstage/integration@1.17.1 + +### Patch Changes + +- e0189b8: UrlReader: Fix handling of access tokens for GitLab readURL requests +- d1e4a6d: Fixed bug where the GitLab user token and GitLab integration token were being merged together +- Updated dependencies + - @backstage/config@1.3.3 + +## @backstage/integration-aws-node@0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + +## @backstage/integration-react@1.2.9 + +### Patch Changes + +- 998fd15: Separated gitlab `write_repository` and `api` scope to pass checks in `RefreshingAuthSessionManager` +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration@1.17.1 + +## @techdocs/cli@1.9.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-defaults@0.11.1 + - @backstage/plugin-techdocs-node@1.13.5 + +## @backstage/test-utils@1.7.10 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/core-app-api@1.18.0 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/theme@0.6.7 + +### Patch Changes + +- 373486e: Add a different background colour to focused `MenuItem`s to fix a bug in MUI 4 (which is fixed in MUI 5) +- b68d269: Update uncomfortably bright color in recently visited and top visited home page cards + +## @backstage/plugin-api-docs@0.12.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-catalog@1.31.1 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-app@0.1.11 + +### Patch Changes + +- 09f5e36: Remove trailing slashes in the `AppRoutes` extension to ensure any nested routing behaves correctly. +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/integration-react@1.2.9 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-app-backend@0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2 + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-app-node@0.1.35 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-app-node@0.1.35 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-app-visualizer@0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-auth-backend@0.25.2 + +### Patch Changes + +- e88cb70: Small internal refactor to move out the `userInfo` database from the `tokenIssuer`. Also removes `exp` from being stored in `UserInfo` and it's now replaced with `created_at` and `updated_at` in the database instead. +- 207778c: Internal refactor of OIDC endpoints and `UserInfoDatabase` +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-catalog-node@1.17.2 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-github-provider@0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-auth-backend@0.25.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-auth-node@0.6.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-auth-react@0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + +## @backstage/plugin-bitbucket-cloud-common@0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1 + +## @backstage/plugin-catalog@1.31.1 + +### Patch Changes + +- 6991dab: Turn on `pagination` by default in new frontend system, and also make configurable +- 3ab9b96: Updated card extensions for the new frontend system to use the new entity predicates, and to not show the about card on User and Group pages. +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-scaffolder-common@1.6.0 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-search-react@1.9.2 + - @backstage/plugin-techdocs-react@1.3.1 + +## @backstage/plugin-catalog-backend-module-aws@0.4.13 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-defaults@0.11.1 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-kubernetes-common@0.9.6 + +## @backstage/plugin-catalog-backend-module-azure@0.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-openapi-utils@0.5.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-node@1.17.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-kubernetes-common@0.9.6 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + +## @backstage/plugin-catalog-backend-module-github@0.10.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0 + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.12 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-backend-module-github@0.10.1 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-defaults@0.11.1 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.1 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.2 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- e2dd095: Fixed bug in `IncrementalIngestionEngine` by adding `burstLength` check when a burst completes +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0 + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-defaults@0.11.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-catalog-backend-module-ldap@0.11.7 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + +## @backstage/plugin-catalog-backend-module-logs@0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-catalog-backend-module-msgraph@0.7.2 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.12 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-node@1.17.2 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-scaffolder-common@1.6.0 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.9 + +## @backstage/plugin-catalog-common@1.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-catalog-graph@0.4.21 + +### Patch Changes + +- fe1a2f4: Catalog graph plugin support i18n +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-catalog-import@0.13.3 + +### Patch Changes + +- 406b8b8: Fixed bug with error message since ResponseError is now thrown from CatalogClient +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-catalog-node@1.17.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-catalog-react@1.19.1 + +### Patch Changes + +- a07feb7: Fixed a but in the new alpha entity predicates where the `$in` operator mistakenly case sensitive. +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/frontend-test-utils@0.3.4 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-config-schema@0.1.70 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + +## @backstage/plugin-devtools@0.1.29 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-devtools-common@0.1.17 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-devtools-backend@0.5.7 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2 + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/backend-defaults@0.11.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-devtools-common@0.1.17 + +## @backstage/plugin-devtools-common@0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-events-backend@0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-openapi-utils@0.5.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.13 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-events-backend-module-azure@0.2.22 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.22 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-events-backend-module-gerrit@0.2.22 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-events-backend-module-github@0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-events-backend-module-gitlab@0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-events-backend-module-kafka@0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-events-backend-test-utils@0.1.46 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-events-node@0.4.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-gateway-backend@1.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-home@0.8.10 + +### Patch Changes + +- d52d67f: Added a New Frontend System App Root Element for the `` component +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/core-app-api@1.18.0 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-home-react@0.1.28 + +## @backstage/plugin-home-react@0.1.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-kubernetes@0.12.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-kubernetes-common@0.9.6 + - @backstage/plugin-kubernetes-react@0.5.9 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-kubernetes-backend@0.19.8 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-kubernetes-node@0.3.2 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-kubernetes-common@0.9.6 + +## @backstage/plugin-kubernetes-cluster@0.0.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-kubernetes-common@0.9.6 + - @backstage/plugin-kubernetes-react@0.5.9 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-kubernetes-common@0.9.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + +## @backstage/plugin-kubernetes-node@0.3.2 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-kubernetes-common@0.9.6 + +## @backstage/plugin-kubernetes-react@0.5.9 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-kubernetes-common@0.9.6 + +## @backstage/plugin-mcp-actions-backend@0.1.1 + +### Patch Changes + +- 6bc0799: Fixed the example in the README for generating a static token by adding a subject field +- Updated dependencies + - @backstage/backend-defaults@0.11.1 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-node@1.17.2 + +## @backstage/plugin-notifications@0.5.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-signals-react@0.0.15 + +## @backstage/plugin-notifications-backend@0.5.8 + +### Patch Changes + +- 4401dfb: Allow defining default notification settings via configuration +- 9a5a73f: Fix `addTopic` migration when `user_settings` present +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-notifications-node@0.2.17 + - @backstage/plugin-signals-node@0.1.22 + +## @backstage/plugin-notifications-backend-module-email@0.3.11 + +### Patch Changes + +- f92c9fc: Add optional config for `ses` mail options with `sourceArn`, `fromArn`, `configurationSetName` +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-notifications-node@0.2.17 + +## @backstage/plugin-notifications-backend-module-slack@0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-notifications-node@0.2.17 + +## @backstage/plugin-notifications-common@0.0.10 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + +## @backstage/plugin-notifications-node@0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-signals-node@0.1.22 + +## @backstage/plugin-org@0.6.41 + +### Patch Changes + +- 3ab9b96: Updated card extensions for the new frontend system to use the new entity predicates, and switch the card type to `info` for the user and group profile cards. +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-org-react@0.1.40 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + +## @backstage/plugin-permission-backend@0.7.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-permission-common@0.9.1 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3 + +## @backstage/plugin-permission-node@0.10.2 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## @backstage/plugin-permission-react@0.4.36 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/core-plugin-api@1.10.9 + +## @backstage/plugin-proxy-backend@0.6.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-proxy-node@0.1.6 + +## @backstage/plugin-proxy-node@0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.12 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.11 + +### Patch Changes + +- 642282d: Added support for new link format for on-prem Confluence +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.13 + +### Patch Changes + +- cf9ba6f: Fixing the typescript issue with using `z.unknown()` +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-defaults@0.11.1 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.8.1 + +### Patch Changes + +- f36bcf9: Added support for file deletion to `publish:github:pull-request` action. + + Example usage: + + ```diff + - action: publish:github:pull-request + id: clean-up-pr + input: + description: This is the description + + filesToDelete: + + - outdated/changelog.md + + - sample-file.txt + owner: owner + repo: repo + title: Title Goes Here + + ``` + +- 38db3eb: Fix typo in `InputError` + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-node@1.17.2 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.3 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-notifications-node@0.2.17 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.11 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.11 + +### Patch Changes + +- 648b3d7: Add optional input to sentry:project:create to set the new Sentry project's platform +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.12 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node-test-utils@0.3.1 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.1 + +### Patch Changes + +- dbde180: An internal refactor which adds additional types to experimental checkpoints +- Updated dependencies + - @backstage/backend-test-utils@1.7.0 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/backend-plugin-api@1.4.1 + +## @backstage/plugin-search@1.4.28 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-search-react@1.9.2 + +## @backstage/plugin-search-backend@2.0.4 + +### Patch Changes + +- 69fb975: Error messages should not contain backend SQL query strings in the API response, this change will ensure that messages are logged and empty response is returned to the user +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/backend-defaults@0.11.1 + - @backstage/backend-openapi-utils@0.5.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-module-catalog@0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.4 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-module-explore@0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-module-pg@0.5.46 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.11 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-module-techdocs@0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/plugin-techdocs-node@1.13.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-node@1.3.13 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-common@1.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-search-react@1.9.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-signals@0.0.21 + +### Patch Changes + +- d52d67f: Added a New Frontend System App Root Element for the `` component +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-signals-react@0.0.15 + +## @backstage/plugin-signals-backend@0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-events-node@0.4.13 + - @backstage/plugin-signals-node@0.1.22 + +## @backstage/plugin-signals-node@0.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-events-node@0.4.13 + +## @backstage/plugin-signals-react@0.0.15 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.9 + +## @backstage/plugin-techdocs@1.13.2 + +### Patch Changes + +- 1debf7f: Fixed an issue causing TechDocs to not properly handle initial redirect. +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration@1.17.1 + - @backstage/theme@0.6.7 + - @backstage/integration-react@1.2.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-auth-react@0.1.17 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-search-react@1.9.2 + - @backstage/plugin-techdocs-react@1.3.1 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.51 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.2 + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/plugin-catalog@1.31.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-app-api@1.18.0 + - @backstage/integration-react@1.2.9 + - @backstage/test-utils@1.7.10 + - @backstage/plugin-search-react@1.9.2 + - @backstage/plugin-techdocs-react@1.3.1 + +## @backstage/plugin-techdocs-backend@2.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-defaults@0.11.1 + - @backstage/catalog-client@1.10.2 + - @backstage/integration@1.17.1 + - @backstage/plugin-techdocs-node@1.13.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-search-backend-module-techdocs@0.4.4 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.26 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-techdocs-react@1.3.1 + +## @backstage/plugin-techdocs-node@1.13.5 + +### Patch Changes + +- 029526c: Updated the error message thrown by parseReferenceAnnotation to reflect the annotation value passed as an argument rather than in correctly assuming location. +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-techdocs-react@1.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-user-settings@0.8.24 + +### Patch Changes + +- aa3b054: Added `signIn` and `signOut` analytic events to the `@backstage/core-components` of sign in and sign out. +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/core-app-api@1.18.0 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-signals-react@0.0.15 + +## @backstage/plugin-user-settings-backend@0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-signals-node@0.1.22 + +## example-app@0.2.111 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.8.10 + - @backstage/plugin-techdocs@1.13.2 + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/plugin-catalog-graph@0.4.21 + - @backstage/config@1.3.3 + - @backstage/frontend-app-api@0.11.4 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-scaffolder@1.33.0 + - @backstage/ui@0.6.0 + - @backstage/cli@0.33.1 + - @backstage/plugin-scaffolder-react@1.18.0 + - @backstage/plugin-catalog@1.31.1 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-org@0.6.41 + - @backstage/theme@0.6.7 + - @backstage/plugin-catalog-import@0.13.3 + - @backstage/core-app-api@1.18.0 + - @backstage/plugin-user-settings@0.8.24 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-signals@0.0.21 + - @backstage/app-defaults@1.6.4 + - @backstage/plugin-api-docs@0.12.9 + - @backstage/plugin-auth-react@0.1.17 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-unprocessed-entities@0.2.19 + - @backstage/plugin-devtools@0.1.29 + - @backstage/plugin-kubernetes@0.12.9 + - @backstage/plugin-kubernetes-cluster@0.0.27 + - @backstage/plugin-notifications@0.5.7 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search@1.4.28 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-search-react@1.9.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26 + - @backstage/plugin-techdocs-react@1.3.1 + +## example-app-next@0.0.25 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.8.10 + - @backstage/plugin-techdocs@1.13.2 + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/plugin-catalog-graph@0.4.21 + - @backstage/config@1.3.3 + - @backstage/frontend-app-api@0.11.4 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-scaffolder@1.33.0 + - @backstage/ui@0.6.0 + - @backstage/cli@0.33.1 + - @backstage/plugin-scaffolder-react@1.18.0 + - @backstage/plugin-catalog@1.31.1 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-org@0.6.41 + - @backstage/theme@0.6.7 + - @backstage/plugin-catalog-import@0.13.3 + - @backstage/core-app-api@1.18.0 + - @backstage/plugin-user-settings@0.8.24 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-app@0.1.11 + - @backstage/plugin-signals@0.0.21 + - @backstage/app-defaults@1.6.4 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-defaults@0.2.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-api-docs@0.12.9 + - @backstage/plugin-app-visualizer@0.1.21 + - @backstage/plugin-auth-react@0.1.17 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-unprocessed-entities@0.2.19 + - @backstage/plugin-kubernetes@0.12.9 + - @backstage/plugin-kubernetes-cluster@0.0.27 + - @backstage/plugin-notifications@0.5.7 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search@1.4.28 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-search-react@1.9.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26 + - @backstage/plugin-techdocs-react@1.3.1 + +## app-next-example-plugin@0.0.25 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/frontend-plugin-api@0.10.4 + +## example-backend@0.0.40 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0 + - @backstage/plugin-scaffolder-backend@2.1.0 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-defaults@0.11.1 + - @backstage/plugin-notifications-backend@0.5.8 + - @backstage/plugin-mcp-actions-backend@0.1.1 + - @backstage/plugin-auth-backend@0.25.2 + - @backstage/plugin-scaffolder-backend-module-github@0.8.1 + - @backstage/plugin-search-backend@2.0.4 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-app-backend@0.5.4 + - @backstage/plugin-auth-backend-module-github-provider@0.3.5 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.10 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.4 + - @backstage/plugin-catalog-backend-module-openapi@0.2.12 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.10 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.2 + - @backstage/plugin-devtools-backend@0.5.7 + - @backstage/plugin-events-backend@0.5.4 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.2 + - @backstage/plugin-kubernetes-backend@0.19.8 + - @backstage/plugin-permission-backend@0.7.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.10 + - @backstage/plugin-proxy-backend@0.6.4 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.12 + - @backstage/plugin-search-backend-module-catalog@0.3.6 + - @backstage/plugin-search-backend-module-explore@0.3.4 + - @backstage/plugin-search-backend-module-techdocs@0.4.4 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-signals-backend@0.3.6 + - @backstage/plugin-techdocs-backend@2.0.4 + +## e2e-test@0.2.30 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.1 + +## @internal/frontend@0.0.11 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.4 + +## @internal/scaffolder@0.0.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.18.0 + - @backstage/frontend-plugin-api@0.10.4 + +## techdocs-cli-embedded-app@0.2.110 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.2 + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/cli@0.33.1 + - @backstage/plugin-catalog@1.31.1 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/core-app-api@1.18.0 + - @backstage/integration-react@1.2.9 + - @backstage/app-defaults@1.6.4 + - @backstage/test-utils@1.7.10 + - @backstage/plugin-techdocs-react@1.3.1 + +## yarn-plugin-backstage@0.0.7 + +### Patch Changes + +- d6084b8: Fixed a bug that would prevent the yarn plugin from installing new dependencies with the `backstage:^` protocol. + +## @internal/plugin-todo-list@1.0.41 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + +## @internal/plugin-todo-list-backend@1.0.41 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + +## @internal/plugin-todo-list-common@1.0.26 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 diff --git a/docs/releases/v1.41.0-next.0-changelog.md b/docs/releases/v1.41.0-next.0-changelog.md new file mode 100644 index 0000000000..9988f3005b --- /dev/null +++ b/docs/releases/v1.41.0-next.0-changelog.md @@ -0,0 +1,1497 @@ +# Release v1.41.0-next.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.41.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.41.0-next.0) + +## @backstage/plugin-catalog-backend@3.0.0-next.0 + +### Major Changes + +- 2339363: **BREAKING:** The experimental `catalog.useUrlReadersSearch` configuration flag (introduced in v1.36) has been removed. + + The `UrlReaderProcessor` now always uses the `search` method of `UrlReaders`. Built-in `UrlReaderService` implementations have been updated accordingly. + If you use custom `UrlReaderService` implementations, you need to adapt their `search` method to correctly handle both specific URLs and potential + search patterns (see changes on built-in readers [in the original PR](https://github.com/backstage/backstage/pull/28379/files#diff-68b0452f173ee54bdd40f7b5e047a9cb8bb59200425622c212c217b76dac1d1b)). + + Previous behavior was to call the `search` method only if the parsed Git URL's filename contained a wildcard and use `readUrl` otherwise. Each `UrlReaderService` must implement this logic in the `search` method instead. + + This allows each `UrlReaderService` implementation to check whether it's a search URL (that contains a wildcard pattern) or not using logic that is specific to each provider. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/backend-openapi-utils@0.5.4 + - @backstage/catalog-client@1.10.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-permission-common@0.9.0 + +## @backstage/backend-test-utils@1.7.0-next.0 + +### Minor Changes + +- ead925a: Add a standard `toString` on credentials objects + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/backend-app-api@1.2.4 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + +## @backstage/canon@0.6.0-next.0 + +### Minor Changes + +- 4c6d891: **BREAKING CHANGES** + + We’re updating our Button component to provide better support for button links. + + - We’re introducing a new `ButtonLink` component, which replaces the previous render prop pattern. + - To maintain naming consistency across components, `IconButton` is being renamed to `ButtonIcon`. + - Additionally, the render prop will be removed from all button-related components. + + These changes aim to simplify usage and improve clarity in our component API. + +### Patch Changes + +- 140f652: We are consolidating all css files into a single styles.css in Canon. +- 8154fb9: Add new SearchField component in Canon +- 6910892: Add new `RadioGroup` + `Radio` component to Canon +- a8a8514: We are transforming how we structure our class names and data attributes definitions for all components. They are now all set in the same place. +- 667b951: Added placeholder prop to TextField component. +- e71333a: adding export for ButtonLink so it's importable + +## @backstage/repo-tools@0.15.0-next.0 + +### Minor Changes + +- 1e7082e: Clear output directory before running `package-docs` and skip all internal packages. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## @backstage/app-defaults@1.6.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-app-api@1.17.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-permission-react@0.4.35 + +## @backstage/backend-defaults@0.11.1-next.0 + +### Patch Changes + +- ead925a: Add a standard `toString` on credentials objects +- e0189b8: UrlReader: Fix handling of access tokens for GitLab readURL requests +- d1e4a6d: Fixed bug where the GitLab user token and GitLab integration token were being merged together +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-app-api@1.2.4 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + +## @backstage/backend-dynamic-feature-service@0.7.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0-next.0 + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/plugin-events-backend@0.5.3 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/backend-openapi-utils@0.5.4 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.34 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/cli@0.33.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.1.11 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.1 + +## @backstage/core-compat-api@0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-components@0.17.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/version-bridge@1.0.11 + +## @backstage/create-app@0.7.1-next.0 + +### Patch Changes + +- 2339363: **BREAKING:** The experimental `catalog.useUrlReadersSearch` configuration flag (introduced in v1.36) has been removed. + + The `UrlReaderProcessor` now always uses the `search` method of `UrlReaders`. Built-in `UrlReaderService` implementations have been updated accordingly. + If you use custom `UrlReaderService` implementations, you need to adapt their `search` method to correctly handle both specific URLs and potential + search patterns (see changes on built-in readers [in the original PR](https://github.com/backstage/backstage/pull/28379/files#diff-68b0452f173ee54bdd40f7b5e047a9cb8bb59200425622c212c217b76dac1d1b)). + + Previous behavior was to call the `search` method only if the parsed Git URL's filename contained a wildcard and use `readUrl` otherwise. Each `UrlReaderService` must implement this logic in the `search` method instead. + + This allows each `UrlReaderService` implementation to check whether it's a search URL (that contains a wildcard pattern) or not using logic that is specific to each provider. + +- Updated dependencies + - @backstage/cli-common@0.1.15 + +## @backstage/dev-utils@1.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.9-next.0 + - @backstage/theme@0.6.7-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/app-defaults@1.6.4-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-app-api@1.17.1 + - @backstage/core-plugin-api@1.10.8 + +## @backstage/frontend-app-api@0.11.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-defaults@0.2.4-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-defaults@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.1.11-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/frontend-app-api@0.11.4-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.0 + +## @backstage/frontend-dynamic-feature-loader@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/frontend-plugin-api@0.10.4-next.0 + +## @backstage/frontend-plugin-api@0.10.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-test-utils@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.1.11-next.0 + - @backstage/test-utils@1.7.10-next.0 + - @backstage/config@1.3.2 + - @backstage/frontend-app-api@0.11.4-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/integration@1.17.1-next.0 + +### Patch Changes + +- e0189b8: UrlReader: Fix handling of access tokens for GitLab readURL requests +- d1e4a6d: Fixed bug where the GitLab user token and GitLab integration token were being merged together +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/integration-react@1.2.9-next.0 + +### Patch Changes + +- 998fd15: Separated gitlab `write_repository` and `api` scope to pass checks in `RefreshingAuthSessionManager` +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/config@1.3.2 + - @backstage/core-plugin-api@1.10.8 + +## @techdocs/cli@1.9.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-techdocs-node@1.13.5-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + +## @backstage/test-utils@1.7.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-react@0.4.35 + +## @backstage/theme@0.6.7-next.0 + +### Patch Changes + +- b68d269: Update uncomfortably bright color in recently visited and top visited home page cards + +## @backstage/plugin-api-docs@0.12.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.1-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.35 + +## @backstage/plugin-app@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.9-next.0 + - @backstage/theme@0.6.7-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-react@0.4.35 + +## @backstage/plugin-app-visualizer@0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + +## @backstage/plugin-auth-react@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + +## @backstage/plugin-bitbucket-cloud-common@0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + +## @backstage/plugin-catalog@1.31.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/plugin-search-react@1.9.2-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-scaffolder-common@1.5.11 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-catalog-backend-module-aws@0.4.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.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/plugin-catalog-common@1.1.4 + - @backstage/plugin-kubernetes-common@0.9.5 + +## @backstage/plugin-catalog-backend-module-azure@0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-bitbucket-cloud-common@0.3.1-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/catalog-client@1.10.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-catalog-backend-module-gerrit@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-github@0.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.10.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/config@1.3.2 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.7.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0-next.0 + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + +## @backstage/plugin-catalog-backend-module-logs@0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/catalog-model@1.7.4 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-graph@0.4.21-next.0 + +### Patch Changes + +- fe1a2f4: Catalog graph plugin support i18n +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-catalog-import@0.13.2-next.0 + +### Patch Changes + +- 406b8b8: Fixed bug with error message since ResponseError is now thrown from CatalogClient +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-react@1.19.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.9-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/frontend-test-utils@0.3.4-next.0 + - @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.35 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + +## @backstage/plugin-config-schema@0.1.70-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-devtools@0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-devtools-common@0.1.16 + - @backstage/plugin-permission-react@0.4.35 + +## @backstage/plugin-devtools-backend@0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config-loader@1.10.1 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @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-events-backend-module-github@0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## @backstage/plugin-home@0.8.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-home-react@0.1.28-next.0 + +## @backstage/plugin-home-react@0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + +## @backstage/plugin-kubernetes@0.12.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-kubernetes-react@0.5.9-next.0 + - @backstage/plugin-permission-react@0.4.35 + +## @backstage/plugin-kubernetes-cluster@0.0.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-kubernetes-react@0.5.9-next.0 + - @backstage/plugin-permission-react@0.4.35 + +## @backstage/plugin-kubernetes-react@0.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5 + +## @backstage/plugin-mcp-actions-backend@0.1.1-next.0 + +### Patch Changes + +- 6bc0799: Fixed the example in the README for generating a static token by adding a subject field +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/catalog-client@1.10.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-notifications@0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.9 + - @backstage/plugin-signals-react@0.0.14 + +## @backstage/plugin-notifications-backend@0.5.8-next.0 + +### Patch Changes + +- 9a5a73f: Fix `addTopic` migration when `user_settings` present +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-notifications-node@0.2.16 + - @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 + - @backstage/plugin-signals-node@0.1.21 + +## @backstage/plugin-org@0.6.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-org-react@0.1.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.8 + +## @backstage/plugin-scaffolder@1.32.1-next.0 + +### Patch Changes + +- 289e4a1: Filter MultiEntityPicker options based on rendered option value +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/plugin-scaffolder-react@1.17.1-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/plugin-scaffolder-backend@2.0.1-next.0 + +### Patch Changes + +- dbde180: An internal refactor which adds additional types to experimental checkpoints +- fc70b43: Replaced deprecated uses of `@backstage/backend-common` with the equivalents in `@backstage/backend-defaults` and `@backstage/backend-plugin-api`. +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.8.1-next.0 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.12-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.11-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.11-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.3-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.11-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.1-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.13-next.0 + +### Patch Changes + +- cf9ba6f: Fixing the typescript issue with using `z.unknown()` +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-github@0.8.1-next.0 + +### Patch Changes + +- f36bcf9: Added support for file deletion to `publish:github:pull-request` action. + + Example usage: + + ```diff + - action: publish:github:pull-request + id: clean-up-pr + input: + description: This is the description + + filesToDelete: + + - outdated/changelog.md + + - sample-file.txt + owner: owner + repo: repo + title: Title Goes Here + + ``` + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-notifications-node@0.2.16 + - @backstage/plugin-notifications-common@0.0.9 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node-test-utils@0.3.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-node@0.9.1-next.0 + +### Patch Changes + +- dbde180: An internal refactor which adds additional types to experimental checkpoints +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.1-next.0 + +### Patch Changes + +- dbde180: An internal refactor which adds additional types to experimental checkpoints +- Updated dependencies + - @backstage/backend-test-utils@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-react@1.17.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/plugin-search@1.4.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/plugin-search-react@1.9.2-next.0 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend@2.0.4-next.0 + +### Patch Changes + +- 69fb975: Error messages should not contain backend SQL query strings in the API response, this change will ensure that messages are logged and empty response is returned to the user +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/backend-openapi-utils@0.5.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-techdocs@0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-techdocs-node@1.13.5-next.0 + - @backstage/catalog-client@1.10.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-common@1.2.18 + +## @backstage/plugin-search-react@1.9.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-signals@0.0.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.14 + +## @backstage/plugin-techdocs@1.13.2-next.0 + +### Patch Changes + +- 1debf7f: Fixed an issue causing TechDocs to not properly handle initial redirect. +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/theme@0.6.7-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/plugin-search-react@1.9.2-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-auth-react@0.1.17-next.0 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.51-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.2-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/plugin-catalog@1.31.1-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/test-utils@1.7.10-next.0 + - @backstage/plugin-search-react@1.9.2-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.0 + - @backstage/core-app-api@1.17.1 + - @backstage/core-plugin-api@1.10.8 + +## @backstage/plugin-techdocs-backend@2.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.4-next.0 + - @backstage/plugin-techdocs-node@1.13.5-next.0 + - @backstage/catalog-client@1.10.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-techdocs-common@0.1.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + +## @backstage/plugin-techdocs-node@1.13.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.16 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-techdocs-react@1.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-user-settings@0.8.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-app-api@1.17.1 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.14 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.21 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.111-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.2-next.0 + - @backstage/plugin-catalog-graph@0.4.21-next.0 + - @backstage/canon@0.6.0-next.0 + - @backstage/plugin-scaffolder@1.32.1-next.0 + - @backstage/plugin-catalog-import@0.13.2-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/theme@0.6.7-next.0 + - @backstage/cli@0.33.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26-next.0 + - @backstage/plugin-catalog@1.31.1-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/app-defaults@1.6.4-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/plugin-home@0.8.10-next.0 + - @backstage/plugin-notifications@0.5.7-next.0 + - @backstage/plugin-scaffolder-react@1.17.1-next.0 + - @backstage/plugin-search-react@1.9.2-next.0 + - @backstage/plugin-signals@0.0.21-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.0 + - @backstage/plugin-user-settings@0.8.24-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-app-api@0.11.4-next.0 + - @backstage/plugin-api-docs@0.12.9-next.0 + - @backstage/plugin-auth-react@0.1.17-next.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-unprocessed-entities@0.2.19-next.0 + - @backstage/plugin-devtools@0.1.29-next.0 + - @backstage/plugin-kubernetes@0.12.9-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.27-next.0 + - @backstage/plugin-org@0.6.41-next.0 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-search@1.4.28-next.0 + - @backstage/plugin-search-common@1.2.18 + +## example-app-next@0.0.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.2-next.0 + - @backstage/plugin-catalog-graph@0.4.21-next.0 + - @backstage/canon@0.6.0-next.0 + - @backstage/plugin-scaffolder@1.32.1-next.0 + - @backstage/plugin-catalog-import@0.13.2-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/theme@0.6.7-next.0 + - @backstage/cli@0.33.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26-next.0 + - @backstage/plugin-app@0.1.11-next.0 + - @backstage/plugin-catalog@1.31.1-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/app-defaults@1.6.4-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/plugin-home@0.8.10-next.0 + - @backstage/plugin-notifications@0.5.7-next.0 + - @backstage/plugin-scaffolder-react@1.17.1-next.0 + - @backstage/plugin-search-react@1.9.2-next.0 + - @backstage/plugin-signals@0.0.21-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.0 + - @backstage/plugin-user-settings@0.8.24-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-app-api@0.11.4-next.0 + - @backstage/frontend-defaults@0.2.4-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-api-docs@0.12.9-next.0 + - @backstage/plugin-app-visualizer@0.1.21-next.0 + - @backstage/plugin-auth-react@0.1.17-next.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-unprocessed-entities@0.2.19-next.0 + - @backstage/plugin-kubernetes@0.12.9-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.27-next.0 + - @backstage/plugin-org@0.6.41-next.0 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-search@1.4.28-next.0 + - @backstage/plugin-search-common@1.2.18 + +## app-next-example-plugin@0.0.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.0 + +## example-backend@0.0.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0-next.0 + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-scaffolder-backend@2.0.1-next.0 + - @backstage/plugin-notifications-backend@0.5.8-next.0 + - @backstage/plugin-mcp-actions-backend@0.1.1-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.8.1-next.0 + - @backstage/plugin-search-backend@2.0.4-next.0 + - @backstage/plugin-app-backend@0.5.3 + - @backstage/plugin-auth-backend@0.25.1 + - @backstage/plugin-auth-backend-module-github-provider@0.3.4 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/plugin-devtools-backend@0.5.7-next.0 + - @backstage/plugin-events-backend@0.5.3 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.1 + - @backstage/plugin-kubernetes-backend@0.19.7 + - @backstage/plugin-permission-backend@0.7.1 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/plugin-proxy-backend@0.6.3 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-signals-backend@0.3.5 + - @backstage/plugin-techdocs-backend@2.0.4-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.9 + - @backstage/plugin-catalog-backend-module-openapi@0.2.12-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.9 + - @backstage/plugin-search-backend-module-catalog@0.3.5 + - @backstage/plugin-search-backend-module-explore@0.3.3 + - @backstage/plugin-search-backend-module-techdocs@0.4.4-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.12-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.3 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.1 + - @backstage/plugin-permission-common@0.9.0 + +## e2e-test@0.2.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.1-next.0 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## @internal/frontend@0.0.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @internal/scaffolder@0.0.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.17.1-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.0 + +## techdocs-cli-embedded-app@0.2.110-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.2-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/theme@0.6.7-next.0 + - @backstage/cli@0.33.1-next.0 + - @backstage/plugin-catalog@1.31.1-next.0 + - @backstage/app-defaults@1.6.4-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/test-utils@1.7.10-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/core-plugin-api@1.10.8 + +## @internal/plugin-todo-list@1.0.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 diff --git a/docs/releases/v1.41.0-next.1-changelog.md b/docs/releases/v1.41.0-next.1-changelog.md new file mode 100644 index 0000000000..7efa9025d7 --- /dev/null +++ b/docs/releases/v1.41.0-next.1-changelog.md @@ -0,0 +1,2000 @@ +# Release v1.41.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.41.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.41.0-next.1) + +## @backstage/app-defaults@1.6.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-permission-react@0.4.36-next.0 + +## @backstage/backend-app-api@1.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + +## @backstage/backend-defaults@0.11.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2-next.0 + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/integration-aws-node@0.1.17-next.0 + - @backstage/backend-app-api@1.2.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/backend-dynamic-feature-service@0.7.2-next.1 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- 3d61c36: Fix wrong imports which lead to module initialization failures when enabling dynamic plugins. +- Updated dependencies + - @backstage/config-loader@1.10.2-next.0 + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/plugin-app-node@0.1.35-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-catalog-backend@3.0.0-next.1 + - @backstage/plugin-events-backend@0.5.4-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/backend-openapi-utils@0.5.5-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/backend-openapi-utils@0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + +## @backstage/backend-plugin-api@1.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/backend-test-utils@1.7.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/backend-app-api@1.2.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/catalog-client@1.10.2-next.0 + +### Patch Changes + +- 6fb4143: allow arrays in the InMemoryCatalogClient to filter entities +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + +## @backstage/catalog-model@1.7.5-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup + +## @backstage/cli@0.33.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2-next.0 + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/integration@1.17.1-next.1 + +## @backstage/config@1.3.3-next.0 + +### Patch Changes + +- ff23618: Loosen the requirements for a key to be considered valid config. +- 3507fcd: Just some more circular dep cleanup + +## @backstage/config-loader@1.10.2-next.0 + +### Patch Changes + +- ff23618: Loosen the requirements for a key to be considered valid config. +- Updated dependencies + - @backstage/config@1.3.3-next.0 + +## @backstage/core-app-api@1.17.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/core-plugin-api@1.10.9-next.0 + +## @backstage/core-compat-api@0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/core-components@0.17.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/core-plugin-api@1.10.9-next.0 + +## @backstage/core-plugin-api@1.10.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + +## @backstage/create-app@0.7.1-next.1 + +### Patch Changes + +- Bumped create-app version. + +## @backstage/dev-utils@1.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/app-defaults@1.6.4-next.1 + +## @backstage/frontend-app-api@0.11.4-next.1 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-defaults@0.2.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/frontend-defaults@0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/frontend-app-api@0.11.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + - @backstage/plugin-app@0.1.11-next.1 + +## @backstage/frontend-dynamic-feature-loader@0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/frontend-plugin-api@0.10.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## @backstage/frontend-test-utils@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/frontend-app-api@0.11.4-next.1 + - @backstage/test-utils@1.7.10-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + - @backstage/plugin-app@0.1.11-next.1 + +## @backstage/integration@1.17.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + +## @backstage/integration-aws-node@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + +## @backstage/integration-react@1.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## @backstage/repo-tools@0.15.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + +## @techdocs/cli@1.9.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/plugin-techdocs-node@1.13.5-next.1 + +## @backstage/test-utils@1.7.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-permission-react@0.4.36-next.0 + +## @backstage/plugin-api-docs@0.12.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-catalog@1.31.1-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-app@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-app-backend@0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2-next.0 + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-app-node@0.1.35-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-app-node@0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + +## @backstage/plugin-app-visualizer@0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-auth-backend@0.25.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-backend@0.25.2-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-github-provider@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-backend@0.25.2-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-auth-node@0.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + +## @backstage/plugin-auth-react@0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## @backstage/plugin-catalog@1.31.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-scaffolder-common@1.5.12-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-catalog-backend@3.0.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/backend-openapi-utils@0.5.5-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-catalog-backend-module-aws@0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/integration@1.17.1-next.1 + - @backstage/integration-aws-node@0.1.17-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-kubernetes-common@0.9.6-next.0 + +## @backstage/plugin-catalog-backend-module-azure@0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/backend-openapi-utils@0.5.5-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-kubernetes-common@0.9.6-next.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## @backstage/plugin-catalog-backend-module-github@0.10.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-backend@3.0.0-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-backend-module-github@0.10.1-next.1 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.1-next.1 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.2-next.1 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- e2dd095: Fixed bug in `IncrementalIngestionEngine` by adding `burstLength` check when a burst completes +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-backend@3.0.0-next.1 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.11.7-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## @backstage/plugin-catalog-backend-module-logs@0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-backend@3.0.0-next.1 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.7.2-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.12-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-scaffolder-common@1.5.12-next.0 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.9-next.0 + +## @backstage/plugin-catalog-common@1.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## @backstage/plugin-catalog-graph@0.4.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-catalog-import@0.13.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-catalog-node@1.17.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + +## @backstage/plugin-catalog-react@1.19.1-next.1 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-test-utils@0.3.4-next.1 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + +## @backstage/plugin-config-schema@0.1.70-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## @backstage/plugin-devtools@0.1.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-devtools-common@0.1.17-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-devtools-backend@0.5.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2-next.0 + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-devtools-common@0.1.17-next.0 + +## @backstage/plugin-devtools-common@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + +## @backstage/plugin-events-backend@0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/backend-openapi-utils@0.5.5-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-events-backend-module-azure@0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-events-backend-module-gerrit@0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-events-backend-module-github@0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-events-backend-module-gitlab@0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-events-backend-module-kafka@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-events-backend-test-utils@0.1.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-events-node@0.4.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + +## @backstage/plugin-gateway-backend@1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + +## @backstage/plugin-home@0.8.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + - @backstage/plugin-home-react@0.1.28-next.1 + +## @backstage/plugin-home-react@0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-kubernetes@0.12.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-kubernetes-common@0.9.6-next.0 + - @backstage/plugin-kubernetes-react@0.5.9-next.1 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-kubernetes-backend@0.19.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-kubernetes-node@0.3.2-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration-aws-node@0.1.17-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-kubernetes-common@0.9.6-next.0 + +## @backstage/plugin-kubernetes-cluster@0.0.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-kubernetes-common@0.9.6-next.0 + - @backstage/plugin-kubernetes-react@0.5.9-next.1 + +## @backstage/plugin-kubernetes-common@0.9.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + +## @backstage/plugin-kubernetes-node@0.3.2-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-kubernetes-common@0.9.6-next.0 + +## @backstage/plugin-kubernetes-react@0.5.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-kubernetes-common@0.9.6-next.0 + +## @backstage/plugin-mcp-actions-backend@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## @backstage/plugin-notifications@0.5.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-notifications-common@0.0.10-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + - @backstage/plugin-signals-react@0.0.15-next.0 + +## @backstage/plugin-notifications-backend@0.5.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-notifications-common@0.0.10-next.0 + - @backstage/plugin-signals-node@0.1.22-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-notifications-node@0.2.17-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-notifications-backend-module-email@0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration-aws-node@0.1.17-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-notifications-common@0.0.10-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-notifications-node@0.2.17-next.0 + +## @backstage/plugin-notifications-backend-module-slack@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-notifications-common@0.0.10-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-notifications-node@0.2.17-next.0 + +## @backstage/plugin-notifications-common@0.0.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + +## @backstage/plugin-notifications-node@0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-notifications-common@0.0.10-next.0 + - @backstage/plugin-signals-node@0.1.22-next.0 + +## @backstage/plugin-org@0.6.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-org-react@0.1.40-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## @backstage/plugin-permission-backend@0.7.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-permission-common@0.9.1-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3-next.0 + +## @backstage/plugin-permission-node@0.10.2-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## @backstage/plugin-permission-react@0.4.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/core-plugin-api@1.10.9-next.0 + +## @backstage/plugin-proxy-backend@0.6.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-proxy-node@0.1.6-next.0 + +## @backstage/plugin-proxy-node@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + +## @backstage/plugin-scaffolder@1.32.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-scaffolder-common@1.5.12-next.0 + - @backstage/plugin-scaffolder-react@1.17.1-next.1 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-scaffolder-backend@2.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.11-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.12-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.11-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.11-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.8.1-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.3-next.1 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-scaffolder-common@1.5.12-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.10-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.1 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.8.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-notifications-common@0.0.10-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + - @backstage/plugin-notifications-node@0.2.17-next.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.3.1-next.1 + +## @backstage/plugin-scaffolder-common@1.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + +## @backstage/plugin-scaffolder-node@0.9.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-common@1.5.12-next.0 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/backend-test-utils@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## @backstage/plugin-scaffolder-react@1.17.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-scaffolder-common@1.5.12-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-search@1.4.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-search-backend@2.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/backend-openapi-utils@0.5.5-next.0 + +## @backstage/plugin-search-backend-module-catalog@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.4-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration-aws-node@0.1.17-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## @backstage/plugin-search-backend-module-explore@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## @backstage/plugin-search-backend-module-pg@0.5.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## @backstage/plugin-search-backend-module-techdocs@0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-techdocs-node@1.13.5-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## @backstage/plugin-search-backend-node@1.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## @backstage/plugin-search-common@1.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + +## @backstage/plugin-search-react@1.9.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-signals@0.0.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + - @backstage/plugin-signals-react@0.0.15-next.0 + +## @backstage/plugin-signals-backend@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-signals-node@0.1.22-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-signals-node@0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## @backstage/plugin-signals-react@0.0.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.9-next.0 + +## @backstage/plugin-techdocs@1.13.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-techdocs-react@1.3.1-next.1 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + - @backstage/plugin-auth-react@0.1.17-next.1 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.51-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/test-utils@1.7.10-next.1 + - @backstage/plugin-techdocs@1.13.2-next.1 + - @backstage/plugin-techdocs-react@1.3.1-next.1 + - @backstage/plugin-catalog@1.31.1-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + +## @backstage/plugin-techdocs-backend@2.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.4-next.1 + - @backstage/plugin-techdocs-node@1.13.5-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-techdocs-react@1.3.1-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-techdocs-node@1.13.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/integration-aws-node@0.1.17-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## @backstage/plugin-techdocs-react@1.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## @backstage/plugin-user-settings@0.8.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + - @backstage/plugin-signals-react@0.0.15-next.0 + +## @backstage/plugin-user-settings-backend@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-signals-node@0.1.22-next.0 + +## example-app@0.2.111-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/frontend-app-api@0.11.4-next.1 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/cli@0.33.1-next.1 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-catalog-import@0.13.3-next.1 + - @backstage/plugin-home@0.8.10-next.1 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-techdocs@1.13.2-next.1 + - @backstage/plugin-techdocs-react@1.3.1-next.1 + - @backstage/plugin-catalog@1.31.1-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-org@0.6.41-next.1 + - @backstage/plugin-scaffolder@1.32.1-next.1 + - @backstage/plugin-scaffolder-react@1.17.1-next.1 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/plugin-api-docs@0.12.9-next.1 + - @backstage/plugin-catalog-graph@0.4.21-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.19-next.1 + - @backstage/plugin-kubernetes@0.12.9-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.27-next.1 + - @backstage/plugin-user-settings@0.8.24-next.1 + - @backstage/app-defaults@1.6.4-next.1 + - @backstage/plugin-notifications@0.5.7-next.1 + - @backstage/plugin-search@1.4.28-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/plugin-signals@0.0.21-next.1 + - @backstage/plugin-auth-react@0.1.17-next.1 + - @backstage/plugin-devtools@0.1.29-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26-next.1 + +## example-app-next@0.0.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/frontend-app-api@0.11.4-next.1 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/cli@0.33.1-next.1 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-defaults@0.2.4-next.1 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-catalog-import@0.13.3-next.1 + - @backstage/plugin-home@0.8.10-next.1 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-techdocs@1.13.2-next.1 + - @backstage/plugin-techdocs-react@1.3.1-next.1 + - @backstage/plugin-catalog@1.31.1-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-org@0.6.41-next.1 + - @backstage/plugin-scaffolder@1.32.1-next.1 + - @backstage/plugin-scaffolder-react@1.17.1-next.1 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/plugin-api-docs@0.12.9-next.1 + - @backstage/plugin-catalog-graph@0.4.21-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.19-next.1 + - @backstage/plugin-kubernetes@0.12.9-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.27-next.1 + - @backstage/plugin-user-settings@0.8.24-next.1 + - @backstage/app-defaults@1.6.4-next.1 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/plugin-notifications@0.5.7-next.1 + - @backstage/plugin-search@1.4.28-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/plugin-signals@0.0.21-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + - @backstage/plugin-app@0.1.11-next.1 + - @backstage/plugin-app-visualizer@0.1.21-next.1 + - @backstage/plugin-auth-react@0.1.17-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26-next.1 + +## example-backend@0.0.40-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/plugin-app-backend@0.5.4-next.0 + - @backstage/plugin-devtools-backend@0.5.7-next.1 + - @backstage/plugin-proxy-backend@0.6.4-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-backend@0.25.2-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.10-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-catalog-backend@3.0.0-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.4-next.0 + - @backstage/plugin-events-backend@0.5.4-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.2-next.0 + - @backstage/plugin-kubernetes-backend@0.19.8-next.0 + - @backstage/plugin-notifications-backend@0.5.8-next.1 + - @backstage/plugin-permission-backend@0.7.2-next.0 + - @backstage/plugin-scaffolder-backend@2.0.1-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.8.1-next.1 + - @backstage/plugin-search-backend@2.0.4-next.1 + - @backstage/plugin-search-backend-module-catalog@0.3.6-next.0 + - @backstage/plugin-search-backend-module-explore@0.3.4-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.4-next.1 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-signals-backend@0.3.6-next.0 + - @backstage/plugin-techdocs-backend@2.0.4-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.2-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.10-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.12-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.10-next.0 + - @backstage/plugin-mcp-actions-backend@0.1.1-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.3.5-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.12-next.1 + +## techdocs-cli-embedded-app@0.2.110-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/cli@0.33.1-next.1 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/test-utils@1.7.10-next.1 + - @backstage/plugin-techdocs@1.13.2-next.1 + - @backstage/plugin-techdocs-react@1.3.1-next.1 + - @backstage/plugin-catalog@1.31.1-next.1 + - @backstage/app-defaults@1.6.4-next.1 + +## yarn-plugin-backstage@0.0.7-next.0 + +### Patch Changes + +- d6084b8: Fixed a bug that would prevent the yarn plugin from installing new dependencies with the `backstage:^` protocol. + +## @internal/plugin-todo-list@1.0.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## @internal/plugin-todo-list-backend@1.0.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + +## @internal/plugin-todo-list-common@1.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 diff --git a/docs/releases/v1.41.0-next.2-changelog.md b/docs/releases/v1.41.0-next.2-changelog.md new file mode 100644 index 0000000000..b788ac0a35 --- /dev/null +++ b/docs/releases/v1.41.0-next.2-changelog.md @@ -0,0 +1,488 @@ +# Release v1.41.0-next.2 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.41.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.41.0-next.2) + +## @backstage/canon@0.6.0-next.1 + +### Minor Changes + +- 2e30459: We are moving our Tooltip component to use React Aria under the hood. In doing so, the structure of the component and its prop are changing to follow the new underlying structure. + +### Patch Changes + +- 76255b8: Add new Card component to Canon. +- b0a6c8e: Add new Header component to Canon. +- be76576: Improve Button, ButtonIcon and ButtonLink styling in Canon. +- 17beb9b: Update return types for Heading & Text components for React 19. +- eac4a4c: Add new tertiary variant to Button, ButtonIcon and ButtonLink in Canon. +- 8f2e82d: Add new Skeleton component in Canon + +## @backstage/core-app-api@1.18.0-next.1 + +### Minor Changes + +- 5ddc0fe: if session exists and refresh fails, then create a new session if not instant popup + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.9-next.0 + +## @backstage/plugin-scaffolder@1.33.0-next.2 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.18.0-next.2 + - @backstage/core-components@0.17.4-next.2 + - @backstage/plugin-scaffolder-common@1.6.0-next.1 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + +## @backstage/plugin-scaffolder-backend@2.1.0-next.2 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.6.0-next.1 + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.10-next.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.11-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.12-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.2 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.11-next.2 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.11-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.8.1-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.3-next.2 + +## @backstage/plugin-scaffolder-common@1.6.0-next.1 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +## @backstage/plugin-scaffolder-node@0.10.0-next.2 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.6.0-next.1 + +## @backstage/plugin-scaffolder-react@1.18.0-next.2 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- 94c11a5: Scroll to the top of the page when navigating between steps in template forms. +- Updated dependencies + - @backstage/theme@0.6.7-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/plugin-scaffolder-common@1.6.0-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + +## @backstage/app-defaults@1.6.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.1 + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/core-plugin-api@1.10.9-next.0 + +## @backstage/backend-dynamic-feature-service@0.7.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## @backstage/cli@0.33.1-next.2 + +### Patch Changes + +- 50f0ce6: Fixes a module not found error when running `backstage-cli info`. + +## @backstage/core-components@0.17.4-next.2 + +### Patch Changes + +- aa3b054: Added `signIn` and `signOut` analytic events to the `@backstage/core-components` of sign in and sign out. +- Updated dependencies + - @backstage/theme@0.6.7-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## @backstage/create-app@0.7.1-next.2 + +### Patch Changes + +- c4a0830: Removed deprecated 'allowedHosts' field from GitHub Publish action input in default template files to fix validation errors with strict Zod validation. + +## @backstage/dev-utils@1.1.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.1 + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/app-defaults@1.6.4-next.2 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + +## @backstage/frontend-app-api@0.11.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## @backstage/test-utils@1.7.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.1 + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## @backstage/theme@0.6.7-next.1 + +### Patch Changes + +- 373486e: Add a different background colour to focused `MenuItem`s to fix a bug in MUI 4 (which is fixed in MUI 5) + +## @backstage/plugin-auth-backend@0.25.2-next.1 + +### Patch Changes + +- e88cb70: Small internal refactor to move out the `userInfo` database from the `tokenIssuer`. Also removes `exp` from being stored in `UserInfo` and it's now replaced with `created_at` and `updated_at` in the database instead. +- 207778c: Internal refactor of OIDC endpoints and `UserInfoDatabase` + +## @backstage/plugin-catalog@1.31.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.2 + - @backstage/plugin-scaffolder-common@1.6.0-next.1 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.6.0-next.1 + +## @backstage/plugin-home@0.8.10-next.2 + +### Patch Changes + +- d52d67f: Added a New Frontend System App Root Element for the `` component +- Updated dependencies + - @backstage/theme@0.6.7-next.1 + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + +## @backstage/plugin-notifications-backend-module-email@0.3.11-next.1 + +### Patch Changes + +- f92c9fc: Add optional config for `ses` mail options with `sourceArn`, `fromArn`, `configurationSetName` + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.11-next.2 + +### Patch Changes + +- 642282d: Added support for new link format for on-prem Confluence +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-github@0.8.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + - @backstage/plugin-scaffolder-node-test-utils@0.3.1-next.2 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## @backstage/plugin-signals@0.0.21-next.2 + +### Patch Changes + +- d52d67f: Added a New Frontend System App Root Element for the `` component +- Updated dependencies + - @backstage/theme@0.6.7-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.51-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/test-utils@1.7.10-next.2 + - @backstage/plugin-catalog@1.31.1-next.2 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/plugin-techdocs@1.13.2-next.1 + +## @backstage/plugin-techdocs-node@1.13.5-next.2 + +### Patch Changes + +- 029526c: Updated the error message thrown by parseReferenceAnnotation to reflect the annotation value passed as an argument rather than in correctly assuming location. + +## @backstage/plugin-user-settings@0.8.24-next.2 + +### Patch Changes + +- aa3b054: Added `signIn` and `signOut` analytic events to the `@backstage/core-components` of sign in and sign out. +- Updated dependencies + - @backstage/theme@0.6.7-next.1 + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + +## example-app@0.2.111-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.8.10-next.2 + - @backstage/canon@0.6.0-next.1 + - @backstage/cli@0.33.1-next.2 + - @backstage/plugin-scaffolder-react@1.18.0-next.2 + - @backstage/theme@0.6.7-next.1 + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/plugin-user-settings@0.8.24-next.2 + - @backstage/plugin-scaffolder@1.33.0-next.2 + - @backstage/plugin-signals@0.0.21-next.2 + - @backstage/app-defaults@1.6.4-next.2 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-app-api@0.11.4-next.2 + - @backstage/plugin-api-docs@0.12.9-next.1 + - @backstage/plugin-catalog@1.31.1-next.2 + - @backstage/plugin-catalog-graph@0.4.21-next.1 + - @backstage/plugin-catalog-import@0.13.3-next.1 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/plugin-notifications@0.5.7-next.1 + - @backstage/plugin-org@0.6.41-next.1 + - @backstage/plugin-search@1.4.28-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/plugin-techdocs@1.13.2-next.1 + +## example-app-next@0.0.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.8.10-next.2 + - @backstage/canon@0.6.0-next.1 + - @backstage/cli@0.33.1-next.2 + - @backstage/plugin-scaffolder-react@1.18.0-next.2 + - @backstage/theme@0.6.7-next.1 + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/plugin-user-settings@0.8.24-next.2 + - @backstage/plugin-scaffolder@1.33.0-next.2 + - @backstage/plugin-signals@0.0.21-next.2 + - @backstage/app-defaults@1.6.4-next.2 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-app-api@0.11.4-next.2 + - @backstage/plugin-api-docs@0.12.9-next.1 + - @backstage/plugin-catalog@1.31.1-next.2 + - @backstage/plugin-catalog-graph@0.4.21-next.1 + - @backstage/plugin-catalog-import@0.13.3-next.1 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/plugin-notifications@0.5.7-next.1 + - @backstage/plugin-org@0.6.41-next.1 + - @backstage/plugin-search@1.4.28-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/plugin-techdocs@1.13.2-next.1 + +## example-backend@0.0.40-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.2-next.1 + - @backstage/plugin-scaffolder-backend@2.1.0-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.10-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.8.1-next.2 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.12-next.2 + +## @internal/scaffolder@0.0.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.18.0-next.2 + +## techdocs-cli-embedded-app@0.2.110-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.33.1-next.2 + - @backstage/theme@0.6.7-next.1 + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/app-defaults@1.6.4-next.2 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/test-utils@1.7.10-next.2 + - @backstage/plugin-catalog@1.31.1-next.2 + - @backstage/plugin-techdocs@1.13.2-next.1 diff --git a/docs/releases/v1.41.0.md b/docs/releases/v1.41.0.md new file mode 100644 index 0000000000..2e16e9ffba --- /dev/null +++ b/docs/releases/v1.41.0.md @@ -0,0 +1,102 @@ +--- +id: v1.41.0 +title: v1.41.0 +description: Backstage Release v1.41.0 +--- + +These are the release notes for the v1.41.0 release of [Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done. + +## Highlights + +### **BREAKING**: Catalog Backend 3.0 - New Defaults + +This major release of the catalog plugin backend switches the default value of a number of different flags that affect the behavior of the catalog. +https://github.com/backstage/backstage/pull/30546 + +#### `catalog.orphanStrategy` is now `delete` by default + +This switches the default orphan strategy to eagerly delete entities that are no longer referenced within the catalog. + +This flag has been suggested many times as a way to clean up entities that have been moved or deleted, and we’re feeling that it’s a good time to turn this on by default. + +You can opt back into the previous behavior by supplying `orphanStrategy: keep` in config. + +#### `catalog.orphanProviderStrategy` is now `delete` by default + +This switches the default provider installation strategy to no longer keep around entities from providers that are no longer present. + +If you have had providers installed in the past that ingested entities into the catalog that you want to keep, the recommendation is to add the provider back to the catalog. If you don’t want the provider to run, you can schedule it with a very large interval. + +#### `catalog.disableRelationsCompatibility` enabled by default + +Relations compatibility is now disabled by default. It can be re-enabled with the new `enableRelationsCompatibility` flag, but be aware that this significantly reduces overall performance of the catalog. + +Relations compatibility ensures that catalog responses contain both `targetRef` and `target` within the relations objects, while if it is disabled, only `targetRef` will be present. The `target` has not been needed within the Backstage ecosystem for a long time, as the move to `targetRef` was rolled out many years ago. Therefore, only bespoke external consumers of the catalog should be affected by this change. + +#### `catalog.stitchingStrategy` is now `{ mode: 'deferred' }` by default + +Stitching is the final phase of ingesting entities into the catalog. It collects all information related to an individual entity, generates the full output version of the entity, and writes it to the tables that power the read API. + +This switches the stitching to happen async in a background task, rather than in the main catalog processing loop. This makes stitching a lot more performant in large catalogs. + +#### `catalog.useUrlReadersSearch` always enabled + +The `catalog.useUrlReadersSearch` flag that was added in 1.36 has been removed and is now always enabled. This means that the `UrlReaderProcessor` in the catalog will always use the `search` method when reading locations, rather than `read`. This allows each individual URL reader to decide whether location refers to a single file, or if it’s a wildcard that needs searching. + +This breaking change only affects those that have installed custom URL readers for use by the catalog. If these readers already implement a search method that is able to read from exact locations, you’re all set, otherwise you will want to update it roughly as follows: + +```ts +search(url, options) { + if (!isWildcard(url)) { + return this.readUrl(url, options) + } + // existing search logic, or throw if wildcards aren’t supported +} +``` + +Contributed by [@tcardonne](https://github.com/tcardonne) in [#29788](https://github.com/backstage/backstage/pull/29788) + +### BREAKING: Scaffolder Permissions Changes + +Some of the `alpha` permissions rules in the scaffolder have had some potentially breaking changes. + +Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + +And we’ve converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + +Contributed by [@04kash](https://github.com/04kash) in [#29202](https://github.com/backstage/backstage/pull/29202) + +### Renaming Canon to Backstage UI + +We have decided to rename Canon, the new design system for Backstage, to Backstage UI. For more context on this change, see [this comment](https://github.com/backstage/backstage/issues/27726#issuecomment-3019711177) on the design system RFC. As part of this change, `@backstage/canon` is being renamed to `@backstage/ui`, and `canon.backstage.io` is being switched to `ui.backstage.io`. + +### Auto `backstage:^` version with Backstage Yarn Plugin + +Fixed a bug that would prevent `yarn` from installing new Backstage dependencies with the `backstage:^` protocol. 🎉 + +Contributed by [@eipc16](https://github.com/eipc16) in [#30390](https://github.com/backstage/backstage/pull/30390) + +## Security Fixes + +This release does not contain any security fixes. + +## Upgrade path + +We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). + +## Links and References + +Below you can find a list of links and references to help you learn about and start using this new release. + +- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/) +- [GitHub repository](https://github.com/backstage/backstage) +- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy) +- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support +- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.41.0-changelog.md) +- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) + +Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.42.0-next.0-changelog.md b/docs/releases/v1.42.0-next.0-changelog.md new file mode 100644 index 0000000000..9a3dd201ee --- /dev/null +++ b/docs/releases/v1.42.0-next.0-changelog.md @@ -0,0 +1,353 @@ +# Release v1.42.0-next.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.42.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.42.0-next.0) + +## @backstage/ui@0.7.0-next.0 + +### Minor Changes + +- b0e47f3: **Breaking** We are upgrading our `Text` component to support all font sizes making the `Heading` component redundant. The new `Text` component introduces 4 sizes for title and 4 sizes for body text. All of these work in multiple colors and font weights. We improved the `as` prop to include all possible values. The `Link` component has also been updated to match the new `Text` component. + +### Patch Changes + +- e7ff178: Update styling of Tooltip element +- e0e886f: Adds onTabSelectionChange to ui header component. + +## @backstage/canon@0.6.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.7.0-next.0 + +## @backstage/core-compat-api@0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/create-app@0.7.2-next.0 + +### Patch Changes + +- Bumped create-app version. + +## @backstage/dev-utils@1.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/integration-react@1.2.9 + +## @backstage/frontend-app-api@0.11.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-defaults@0.2.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/frontend-defaults@0.2.5-next.0 + +### Patch Changes + +- 7adc846: Added support for passing through `allowUnknownExtensionConfig` as a flag +- Updated dependencies + - @backstage/frontend-app-api@0.11.5-next.0 + - @backstage/plugin-app@0.1.11 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/frontend-test-utils@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.11.5-next.0 + - @backstage/plugin-app@0.1.11 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-api-docs@0.12.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/plugin-catalog@1.31.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-catalog@1.31.2-next.0 + +### Patch Changes + +- f4622e8: Adding a more sensible default order to the default filters +- 77eebdc: Support multiple headers in new frontend system, and don't render a header until the entity has finished loading +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/integration-react@1.2.9 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-search-react@1.9.2 + +## @backstage/plugin-catalog-backend-module-azure@0.3.8-next.0 + +### Patch Changes + +- b3aa80e: `host` should be optional in `config` schema + +## @backstage/plugin-catalog-graph@0.4.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-catalog-import@0.13.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/integration-react@1.2.9 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-catalog-react@1.19.2-next.0 + +### Patch Changes + +- 77eebdc: Support `filter` parameter on the `EntityHeaderBlueprint` +- a3a878d: Adding `type` as an override to the `convertLegacyEntityCardExtension` +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/integration-react@1.2.9 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/frontend-test-utils@0.3.5-next.0 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-devtools@0.1.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-home@0.8.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-kubernetes@0.12.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-kubernetes-cluster@0.0.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + +## @backstage/plugin-notifications@0.5.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-org@0.6.42-next.0 + +### Patch Changes + +- 43cbb10: Added OwnershipCard, ComponentsGrid and UserProfileCard components to the `overridableComponents`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-org-react@0.1.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + +## @backstage/plugin-scaffolder@1.33.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/plugin-scaffolder-react@1.18.1-next.0 + - @backstage/integration-react@1.2.9 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-scaffolder-react@1.18.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-search@1.4.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-search-react@1.9.2 + +## @backstage/plugin-signals@0.0.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## @backstage/plugin-techdocs@1.13.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/integration-react@1.2.9 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-search-react@1.9.2 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.52-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/plugin-catalog@1.31.2-next.0 + - @backstage/plugin-techdocs@1.13.3-next.0 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-search-react@1.9.2 + +## @backstage/plugin-user-settings@0.8.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## example-app@0.2.112-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-org@0.6.42-next.0 + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/ui@0.7.0-next.0 + - @backstage/plugin-catalog@1.31.2-next.0 + - @backstage/plugin-api-docs@0.12.10-next.0 + - @backstage/plugin-catalog-graph@0.4.22-next.0 + - @backstage/plugin-catalog-import@0.13.4-next.0 + - @backstage/plugin-home@0.8.11-next.0 + - @backstage/plugin-kubernetes@0.12.10-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.28-next.0 + - @backstage/plugin-scaffolder@1.33.1-next.0 + - @backstage/plugin-scaffolder-react@1.18.1-next.0 + - @backstage/plugin-search@1.4.29-next.0 + - @backstage/plugin-techdocs@1.13.3-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26 + - @backstage/plugin-user-settings@0.8.25-next.0 + - @backstage/frontend-app-api@0.11.5-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.0 + - @backstage/plugin-devtools@0.1.30-next.0 + - @backstage/plugin-notifications@0.5.8-next.0 + - @backstage/plugin-signals@0.0.22-next.0 + - @backstage/cli@0.33.1 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-search-react@1.9.2 + +## example-app-next@0.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-org@0.6.42-next.0 + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/ui@0.7.0-next.0 + - @backstage/plugin-catalog@1.31.2-next.0 + - @backstage/frontend-defaults@0.2.5-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/plugin-api-docs@0.12.10-next.0 + - @backstage/plugin-catalog-graph@0.4.22-next.0 + - @backstage/plugin-catalog-import@0.13.4-next.0 + - @backstage/plugin-home@0.8.11-next.0 + - @backstage/plugin-kubernetes@0.12.10-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.28-next.0 + - @backstage/plugin-scaffolder@1.33.1-next.0 + - @backstage/plugin-scaffolder-react@1.18.1-next.0 + - @backstage/plugin-search@1.4.29-next.0 + - @backstage/plugin-techdocs@1.13.3-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26 + - @backstage/plugin-user-settings@0.8.25-next.0 + - @backstage/frontend-app-api@0.11.5-next.0 + - @backstage/plugin-app-visualizer@0.1.21 + - @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.0 + - @backstage/plugin-notifications@0.5.8-next.0 + - @backstage/plugin-signals@0.0.22-next.0 + - @backstage/cli@0.33.1 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-app@0.1.11 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-search-react@1.9.2 + +## e2e-test@0.2.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.2-next.0 + +## @internal/scaffolder@0.0.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.18.1-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## techdocs-cli-embedded-app@0.2.111-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.2-next.0 + - @backstage/plugin-techdocs@1.13.3-next.0 + - @backstage/cli@0.33.1 + - @backstage/integration-react@1.2.9 diff --git a/docs/releases/v1.42.0-next.1-changelog.md b/docs/releases/v1.42.0-next.1-changelog.md new file mode 100644 index 0000000000..1634530895 --- /dev/null +++ b/docs/releases/v1.42.0-next.1-changelog.md @@ -0,0 +1,2285 @@ +# Release v1.42.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.42.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.42.0-next.1) + +## @backstage/backend-openapi-utils@0.6.0-next.0 + +### Minor Changes + +- c08cbc4: Move Scaffolder API to OpenAPI + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/catalog-client@1.11.0-next.0 + +### Minor Changes + +- 6b608e7: Added the analyze-location endpoint to the CatalogClient + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/errors@1.2.7 + +## @backstage/frontend-plugin-api@0.11.0-next.0 + +### Minor Changes + +- 29786f6: **BREAKING**: The `NavLogoBlueprint` has been removed and replaced by `NavContentBlueprint`, which instead replaces the entire navbar. The default navbar has also been switched to a more minimal implementation. + + To use `NavContentBlueprint` to install new logos, you can use it as follows: + + ```tsx + NavContentBlueprint.make({ + params: { + component: ({ items }) => { + return compatWrapper( + + + + {/* Other sidebar content */} + + + {items.map((item, index) => ( + + ))} + + + {/* Other sidebar content */} + , + ); + }, + }, + }); + ``` + +- 805c298: **BREAKING**: The `ApiBlueprint` has been updated to use the new advanced type parameters through the new `defineParams` blueprint option. This is an immediate breaking change that requires all existing usages of `ApiBlueprint` to switch to the new callback format. Existing extensions created with the old format are still compatible with the latest version of the plugin API however, meaning that this does not break existing plugins. + + To update existing usages of `ApiBlueprint`, you remove the outer level of the `params` object and replace `createApiFactory(...)` with `define => define(...)`. + + For example, the following old usage: + + ```ts + ApiBlueprint.make({ + name: 'error', + params: { + factory: createApiFactory({ + api: errorApiRef, + deps: { alertApi: alertApiRef }, + factory: ({ alertApi }) => { + return ...; + }, + }) + }, + }) + ``` + + is migrated to the following: + + ```ts + ApiBlueprint.make({ + name: 'error', + params: define => + define({ + api: errorApiRef, + deps: { alertApi: alertApiRef }, + factory: ({ alertApi }) => { + return ...; + }, + }), + }) + ``` + +- 805c298: Added support for advanced parameter types in extension blueprints. The primary purpose of this is to allow extension authors to use type inference in the definition of the blueprint parameters. This often removes the need for extra imports and improves discoverability of blueprint parameters. + + This feature is introduced through the new `defineParams` option of `createExtensionBlueprint`, along with accompanying `createExtensionBlueprintParams` function to help implement the new format. + + The following is an example of how to create an extension blueprint that uses the new option: + + ```ts + const ExampleBlueprint = createExtensionBlueprint({ + kind: 'example', + attachTo: { id: 'example', input: 'example' }, + output: [exampleComponentDataRef, exampleFetcherDataRef], + defineParams(params: { + component(props: ExampleProps): JSX.Element | null; + fetcher(options: FetchOptions): Promise>; + }) { + // The returned params must be wrapped with `createExtensionBlueprintParams` + return createExtensionBlueprintParams(params); + }, + *factory(params) { + // These params are now inferred + yield exampleComponentDataRef(params.component); + yield exampleFetcherDataRef(params.fetcher); + }, + }); + ``` + + Usage of the above example looks as follows: + + ```ts + const example = ExampleBlueprint.make({ + params: define => define({ + component: ..., + fetcher: ..., + }), + }); + ``` + + This `define => define()` is also known as the "callback syntax" and is required if a blueprint is created with the new `defineParams` option. The callback syntax can also optionally be used for other blueprints too, which means that it is not a breaking change to remove the `defineParams` option, as long as the external parameter types remain compatible. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/plugin-app@0.2.0-next.0 + +### Minor Changes + +- 29786f6: Updated the `app/nav` extension to use the new `NavContentBlueprint`, and removed support for extensions created with the now removed `NavLogoBlueprint`. + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-catalog-node@1.18.0-next.0 + +### Minor Changes + +- 3f4da39: Added the analyze-location endpoint to the CatalogService + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-catalog-react@1.20.0-next.1 + +### Minor Changes + +- 3f4da39: Added the `analyzeLocation` method to `catalogApiMock` + +### Patch Changes + +- defc243: hide pagination `queryparams` if pagination mode is set to none +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/frontend-test-utils@0.3.5-next.1 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-scaffolder@1.34.0-next.1 + +### Minor Changes + +- c08cbc4: Move Scaffolder API to OpenAPI + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- b0dc9b8: differentiate between entirely and partially composite schemas in schema rendering +- c4b7c50: Export `FormField` type from `/alpha` in `-react` package, and internal refactor. +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.19.0-next.1 + - @backstage/plugin-scaffolder-common@1.7.0-next.0 + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-scaffolder-backend@2.2.0-next.0 + +### Minor Changes + +- c08cbc4: Move Scaffolder API to OpenAPI +- 812485c: Add step info to scaffolder action context to access the step id and name. + +### Patch Changes + +- caee2eb: Fixed WinstonLogger throwing when redactions were null or undefined +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/backend-openapi-utils@0.6.0-next.0 + - @backstage/plugin-scaffolder-common@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.4-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.11-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.12-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.13-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.12-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.12-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.12-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.12-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.8.2-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.1 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-scaffolder-common@1.7.0-next.0 + +### Minor Changes + +- c08cbc4: Move Scaffolder API to OpenAPI + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-scaffolder-node@0.11.0-next.0 + +### Minor Changes + +- c08cbc4: Move Scaffolder API to OpenAPI + +### Patch Changes + +- 812485c: Add step info to scaffolder action context to access the step id and name. +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.7.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-scaffolder-react@1.19.0-next.1 + +### Minor Changes + +- 4f99e10: **DEPRECATION**: The following types have been deprecated from this package and moved into `@backstage/plugin-scaffolder-common` and should be imported from there instead. + + `Action`, `ListActionsResponse`, `LogEvent`, `ScaffolderApi`, `ScaffolderDryRunOptions`, `ScaffolderDryRunResponse`, `ScaffolderGetIntegrationsListOptions`, `ScaffolderGetIntegrationsListResponse`, + `ScaffolderOutputLink`, `ScaffolderOutputText`, `ScaffolderScaffoldOptions`, `ScaffolderScaffoldResponse`, `ScaffolderStreamLogsOptions`, `ScaffolderTask`, `ScaffolderTaskOutput`, `ScaffolderTaskStatus`, + `ScaffolderUsageExample`, `TemplateFilter`, `TemplateGlobalFunction`, `TemplateGlobalValue`, `TemplateParameterSchema`. + +- c08cbc4: Move Scaffolder API to OpenAPI + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- c4b7c50: Export `FormField` type from `/alpha` in `-react` package, and internal refactor. +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.7.0-next.0 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-techdocs@1.14.0-next.1 + +### Minor Changes + +- cb0541f: Adds `additionalAllowedURIProtocols` to sanitizer config + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-search-react@1.9.3-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-auth-react@0.1.18-next.0 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/app-defaults@1.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.8-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/backend-app-api@1.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/backend-defaults@0.11.2-next.0 + +### Patch Changes + +- caee2eb: Fixed WinstonLogger throwing when redactions were null or undefined +- 3a7dad9: Updated `better-sqlite3` to v12 +- Updated dependencies + - @backstage/backend-app-api@1.2.6-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.2 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/types@1.2.1 + +## @backstage/backend-dynamic-feature-service@0.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/backend-openapi-utils@0.6.0-next.0 + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/plugin-catalog-backend@3.0.1-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-events-backend@0.5.5-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.2 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.36-next.0 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/backend-plugin-api@1.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/backend-test-utils@1.7.1-next.0 + +### Patch Changes + +- 3a7dad9: Updated `better-sqlite3` to v12 +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/backend-app-api@1.2.6-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/cli@0.33.2-next.0 + +### Patch Changes + +- a6af768: Allow js files to be processed by the nodeTransform loader +- Updated dependencies + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.2 + - @backstage/catalog-model@1.7.5 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.1.11 + - @backstage/integration@1.17.1 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.1 + +## @backstage/core-compat-api@0.4.5-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-components@0.17.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.8-next.0 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/version-bridge@1.0.11 + +## @backstage/create-app@0.7.2-next.1 + +### Patch Changes + +- d7a3d04: Created a flag for scaffolding apps using the new frontend system. + +- 2a156e0: Updated the `better-sqlite` dependency from `v9.0.0` to `v13.0.0`. You can apply this change to your instance by applying the following change to your `packages/backend/package.json` and running `yarn install`. + + ```diff + "dependencies": { + "app": "link:../app", + - "better-sqlite3": "^9.0.0", + + "better-sqlite3": "^12.0.0", + "node-gyp": "^10.0.0", + "pg": "^8.11.3" + } + ``` + +- Updated dependencies + - @backstage/cli-common@0.1.15 + +## @backstage/dev-utils@1.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/theme@0.6.8-next.0 + - @backstage/app-defaults@1.6.5-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + +## @backstage/frontend-app-api@0.11.5-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/frontend-defaults@0.2.5-next.1 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-defaults@0.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.11.5-next.1 + - @backstage/plugin-app@0.2.0-next.0 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/frontend-dynamic-feature-loader@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/config@1.3.3 + +## @backstage/frontend-test-utils@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.11.5-next.1 + - @backstage/plugin-app@0.2.0-next.0 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/test-utils@1.7.11-next.0 + - @backstage/config@1.3.3 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/repo-tools@0.15.1-next.0 + +### Patch Changes + +- 225d7c2: Added `stringEnums` to `mustache` templates +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.2 + - @backstage/catalog-model@1.7.5 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## @techdocs/cli@1.9.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-techdocs-node@1.13.6-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.3 + +## @backstage/test-utils@1.7.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.8-next.0 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/theme@0.6.8-next.0 + +### Patch Changes + +- b731527: We are introducing two new data attributes on the `body` to support Backstage UI (BUI) new theming system. + +## @backstage/ui@0.7.0-next.1 + +### Patch Changes + +- de89a3d: Fixes some styles on the Select component in BUI. +- 75fead9: Fixes a couple of small bugs in BUI including setting H1 and H2 correctly on the Header and HeaderPage. +- 2f9a084: We are motion away from `motion` to use `gsap` instead to make Backstage UI backward compatible with React 17. + +## @backstage/plugin-api-docs@0.12.10-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/plugin-catalog@1.31.2-next.1 + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-app-backend@0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config-loader@1.10.2 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.36-next.0 + +## @backstage/plugin-app-node@0.1.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config-loader@1.10.2 + +## @backstage/plugin-app-visualizer@0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + +## @backstage/plugin-auth-backend@0.25.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.3-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.3-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + +## @backstage/plugin-auth-node@0.6.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-react@0.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog@1.31.2-next.1 + +### Patch Changes + +- c0ea01b: Fix card scrolling behaviour +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.7.0-next.0 + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-search-react@1.9.3-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-catalog-backend@3.0.1-next.0 + +### Patch Changes + +- 3a7dad9: Updated `better-sqlite3` to v12 +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.0-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-catalog-backend-module-aws@0.4.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-kubernetes-common@0.9.6 + +## @backstage/plugin-catalog-backend-module-azure@0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.1 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/plugin-kubernetes-common@0.9.6 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-catalog-backend-module-github@0.10.2-next.0 + +### Patch Changes + +- d421b16: This change introduces single user versions of the user group resolution code in the multi org provider that will not page through all membership when updating a single user. +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-catalog-backend@3.0.1-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-catalog-backend-module-github@0.10.2-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.2-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-catalog-backend@3.0.1-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-catalog-backend-module-ldap@0.11.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-catalog-backend-module-logs@0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.1-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.7.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.9 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-catalog-graph@0.4.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/types@1.2.1 + +## @backstage/plugin-catalog-import@0.13.4-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + +## @backstage/plugin-config-schema@0.1.71-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-devtools@0.1.30-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.17 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-devtools-backend@0.5.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config-loader@1.10.2 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-devtools-common@0.1.17 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-events-backend@0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/types@1.2.1 + +## @backstage/plugin-events-backend-module-azure@0.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + +## @backstage/plugin-events-backend-module-gerrit@0.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + +## @backstage/plugin-events-backend-module-github@0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + +## @backstage/plugin-events-backend-module-gitlab@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-events-backend-module-kafka@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/types@1.2.1 + +## @backstage/plugin-events-backend-test-utils@0.1.47-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.14-next.0 + +## @backstage/plugin-events-node@0.4.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-gateway-backend@1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + +## @backstage/plugin-home@0.8.11-next.1 + +### Patch Changes + +- f16d380: Add the missing Visits API to the alpha plugin, fixing a crash due to the API not being installed. +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-home-react@0.1.29-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + +## @backstage/plugin-home-react@0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + +## @backstage/plugin-kubernetes@0.12.10-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-kubernetes-common@0.9.6 + - @backstage/plugin-kubernetes-react@0.5.10-next.0 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-kubernetes-backend@0.19.9-next.0 + +### Patch Changes + +- 00ebaeb: Remove usage of the deprecated `loggerToWinstonLogger` from `@backstage/backend-common`. +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-kubernetes-node@0.3.3-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.17 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.6 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-kubernetes-cluster@0.0.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-kubernetes-common@0.9.6 + - @backstage/plugin-kubernetes-react@0.5.10-next.0 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-kubernetes-node@0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.6 + +## @backstage/plugin-kubernetes-react@0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.6 + +## @backstage/plugin-mcp-actions-backend@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-notifications@0.5.8-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-signals-react@0.0.15 + +## @backstage/plugin-notifications-backend@0.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-notifications-node@0.2.18-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-signals-node@0.1.23-next.0 + +## @backstage/plugin-notifications-backend-module-email@0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-notifications-node@0.2.18-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/integration-aws-node@0.1.17 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.10 + +## @backstage/plugin-notifications-backend-module-slack@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-notifications-node@0.2.18-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.10 + +## @backstage/plugin-notifications-node@0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-signals-node@0.1.23-next.0 + +## @backstage/plugin-org@0.6.42-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-org-react@0.1.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + +## @backstage/plugin-permission-backend@0.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-permission-node@0.10.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-proxy-backend@0.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-proxy-node@0.1.7-next.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-proxy-node@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.12-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.12-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.8.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.4-next.0 + +### Patch Changes + +- df2bfab: The `description` property in `publish:gitlab:merge-request` has been made optional again to comply with the GitLab API. +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/plugin-notifications-node@0.2.18-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-notifications-common@0.0.10 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/plugin-scaffolder-node-test-utils@0.3.2-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.2-next.0 + +### Patch Changes + +- 812485c: Add step info to scaffolder action context to access the step id and name. +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-test-utils@1.7.1-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-search@1.4.29-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/plugin-search-react@1.9.3-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend@2.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/backend-openapi-utils@0.6.0-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-module-catalog@0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-module-explore@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-module-pg@0.5.47-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-module-techdocs@0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-techdocs-node@1.13.6-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-node@1.3.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-react@1.9.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-signals@0.0.22-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.15 + +## @backstage/plugin-signals-backend@0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.23-next.0 + +## @backstage/plugin-signals-node@0.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/types@1.2.1 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.52-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.2-next.1 + - @backstage/plugin-techdocs@1.14.0-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/plugin-search-react@1.9.3-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + - @backstage/test-utils@1.7.11-next.0 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + +## @backstage/plugin-techdocs-backend@2.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.5-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-techdocs-node@1.13.6-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + +## @backstage/plugin-techdocs-node@1.13.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-techdocs-react@1.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-user-settings@0.8.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.15 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.23-next.0 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.112-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.0-next.1 + - @backstage/plugin-scaffolder@1.34.0-next.1 + - @backstage/ui@0.7.0-next.1 + - @backstage/plugin-home@0.8.11-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.1 + - @backstage/frontend-app-api@0.11.5-next.1 + - @backstage/plugin-catalog-import@0.13.4-next.1 + - @backstage/plugin-notifications@0.5.8-next.1 + - @backstage/plugin-kubernetes@0.12.10-next.1 + - @backstage/plugin-api-docs@0.12.10-next.1 + - @backstage/plugin-devtools@0.1.30-next.1 + - @backstage/plugin-techdocs@1.14.0-next.1 + - @backstage/plugin-signals@0.0.22-next.1 + - @backstage/plugin-search@1.4.29-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/theme@0.6.8-next.0 + - @backstage/cli@0.33.2-next.0 + - @backstage/plugin-catalog-graph@0.4.22-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.28-next.1 + - @backstage/plugin-org@0.6.42-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.27-next.0 + - @backstage/plugin-user-settings@0.8.25-next.1 + - @backstage/plugin-search-react@1.9.3-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + - @backstage/app-defaults@1.6.5-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-auth-react@0.1.18-next.0 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search-common@1.2.19 + +## example-app-next@0.0.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.0-next.1 + - @backstage/plugin-scaffolder@1.34.0-next.1 + - @backstage/ui@0.7.0-next.1 + - @backstage/plugin-home@0.8.11-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.1 + - @backstage/frontend-app-api@0.11.5-next.1 + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-import@0.13.4-next.1 + - @backstage/plugin-notifications@0.5.8-next.1 + - @backstage/plugin-kubernetes@0.12.10-next.1 + - @backstage/plugin-api-docs@0.12.10-next.1 + - @backstage/plugin-techdocs@1.14.0-next.1 + - @backstage/plugin-signals@0.0.22-next.1 + - @backstage/plugin-search@1.4.29-next.1 + - @backstage/plugin-app@0.2.0-next.0 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/cli@0.33.2-next.0 + - @backstage/frontend-defaults@0.2.5-next.1 + - @backstage/plugin-catalog-graph@0.4.22-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.28-next.1 + - @backstage/plugin-org@0.6.42-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.27-next.0 + - @backstage/plugin-user-settings@0.8.25-next.1 + - @backstage/plugin-app-visualizer@0.1.22-next.0 + - @backstage/plugin-search-react@1.9.3-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + - @backstage/app-defaults@1.6.5-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-auth-react@0.1.18-next.0 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search-common@1.2.19 + +## app-next-example-plugin@0.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + +## example-backend@0.0.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-backend@0.19.9-next.0 + - @backstage/plugin-scaffolder-backend@2.2.0-next.0 + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-catalog-backend@3.0.1-next.0 + - @backstage/plugin-app-backend@0.5.5-next.0 + - @backstage/plugin-auth-backend@0.25.3-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.3.6-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-devtools-backend@0.5.8-next.0 + - @backstage/plugin-events-backend@0.5.5-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.3-next.0 + - @backstage/plugin-mcp-actions-backend@0.1.2-next.0 + - @backstage/plugin-notifications-backend@0.5.9-next.0 + - @backstage/plugin-permission-backend@0.7.3-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/plugin-proxy-backend@0.6.5-next.0 + - @backstage/plugin-search-backend@2.0.5-next.0 + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/plugin-signals-backend@0.3.7-next.0 + - @backstage/plugin-techdocs-backend@2.0.5-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.5-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.11-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.8.2-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.13-next.0 + - @backstage/plugin-search-backend-module-catalog@0.3.7-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.5-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.13-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.11-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.11-next.0 + - @backstage/plugin-search-backend-module-explore@0.3.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-permission-common@0.9.1 + +## @internal/frontend@0.0.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @internal/scaffolder@0.0.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.19.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + +## techdocs-cli-embedded-app@0.2.111-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.2-next.1 + - @backstage/plugin-techdocs@1.14.0-next.1 + - @backstage/theme@0.6.8-next.0 + - @backstage/cli@0.33.2-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + - @backstage/app-defaults@1.6.5-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/test-utils@1.7.11-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + +## @internal/plugin-todo-list@1.0.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + +## @internal/plugin-todo-list-backend@1.0.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 diff --git a/docs/releases/v1.42.0-next.2-changelog.md b/docs/releases/v1.42.0-next.2-changelog.md new file mode 100644 index 0000000000..eccc7a8529 --- /dev/null +++ b/docs/releases/v1.42.0-next.2-changelog.md @@ -0,0 +1,723 @@ +# Release v1.42.0-next.2 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.42.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.42.0-next.2) + +## @backstage/cli@0.34.0-next.1 + +### Minor Changes + +- 38b4243: Added plugin and module templates for the new frontend system. These templates are not included by default, but can be included by adding `@backstage/cli/templates/new-frontend-plugin` and `@backstage/cli/templates/new-frontend-plugin-module` as [custom templates](https://backstage.io/docs/tooling/cli/templates#installing-custom-templates). + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.3 + - @backstage/config-loader@1.10.2 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.1.11 + - @backstage/integration@1.17.1 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.1 + +## @backstage/core-compat-api@0.5.0-next.2 + +### Minor Changes + +- e4ddf22: **BREAKING**: The `defaultPath` override of `convertLegacyPageExtension` has been renamed to `path`, in order to align with the same update that was made to the `PageBlueprint`. + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- 5d31d66: Updated the usage of the `RouterBlueprint` and `AppRootWrapperBlueprint` to use the lowercase `component` parameter +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-plugin-api@1.10.9 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-app-api@0.12.0-next.2 + +### Minor Changes + +- df7bd3b: **BREAKING**: Removed the deprecated `FrontendFeature` type, import it from `@backstage/frontend-plugin-api` instead. + +### Patch Changes + +- d9e00e3: Add support for a new `aliasFor` option for `createRouteRef`. This allows for the creation of a new route ref that acts as an alias for an existing route ref that is installed in the app. This is particularly useful when creating modules that override existing plugin pages, without referring to the existing plugin. For example: + + ```tsx + export default createFrontendModule({ + pluginId: 'catalog', + extensions: [ + PageBlueprint.make({ + params: { + defaultPath: '/catalog', + routeRef: createRouteRef({ aliasFor: 'catalog.catalogIndex' }), + loader: () => + import('./CustomCatalogIndexPage').then(m => ( + + )), + }, + }), + ], + }); + ``` + +- 3d2499f: Moved `createSpecializedApp` options to a new `CreateSpecializedAppOptions` type. + +- Updated dependencies + - @backstage/frontend-defaults@0.3.0-next.2 + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-defaults@0.3.0-next.2 + +### Minor Changes + +- 76832a9: **BREAKING**: Removed the deprecated `CreateAppFeatureLoader` and support for it in other APIs. Switch existing usage to use the newer `createFrontendFeatureLoader` from `@backstage/frontend-plugin-api` instead. + +### Patch Changes + +- 22de964: Deprecated `createPublicSignInApp`, which has been replaced by the new `appModulePublicSignIn` from `@backstage/plugin-app/alpha` instead. +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/frontend-app-api@0.12.0-next.2 + - @backstage/plugin-app@0.2.0-next.1 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/frontend-plugin-api@0.11.0-next.1 + +### Minor Changes + +- c5f88b5: **BREAKING**: Remove deprecated `source` property from the `AppNodeSpec` type, use `AppNodeSpec.plugin` instead. + +- e4ddf22: **BREAKING**: The `defaultPath` param of `PageBlueprint` has been renamed to `path`. This change does not affect the compatibility of extensions created with older versions of this blueprint. + +- 37f2989: **BREAKING**: Removed the `routable` property from `ExtensionBoundary`. This property was never needed in practice and is instead inferred from whether or not the extension outputs a route reference. It can be safely removed. + +- 3243fa6: **BREAKING**: Removed the ability to define a default extension `name` in blueprints. This option had no practical purpose as blueprints already use the `kind` to identity the source of the extension. + +- a082429: **BREAKING**: The separate `RouteResolutionApiResolveOptions` type has been removed. + +- 5d31d66: **BREAKING**: In an attempt to align some of the API's around providing components to `Blueprints`, we've renamed the parameters for both the `RouterBlueprint` and `AppRootWrapperBlueprint` from `Component` to `component`. + + ```tsx + // old + RouterBlueprint.make({ + params: { + Component: ({ children }) =>
    {children}
    , + }, + }); + + // new + RouterBlueprint.make({ + params: { + component: ({ children }) =>
    {children}
    , + }, + }); + ``` + + ```tsx + // old + AppRootWrapperBlueprint.make({ + params: { + Component: ({ children }) =>
    {children}
    , + }, + }); + + // new + AppRootWrapperBlueprint.make({ + params: { + component: ({ children }) =>
    {children}
    , + }, + }); + ``` + + As part of this change, the type for `component` has also changed from `ComponentType>` to `(props: { children: ReactNode }) => JSX.Element | null` which is not breaking, just a little more reflective of the actual expected component. + +- 45ead4a: **BREAKING**: The `AnyRoutes` and `AnyExternalRoutes` types have been removed and their usage has been inlined instead. + + Existing usage can be replaced according to their previous definitions: + + ```ts + type AnyRoutes = { [name in string]: RouteRef | SubRouteRef }; + type AnyExternalRoutes = { [name in string]: ExternalRouteRef }; + ``` + +- 121899a: **BREAKING**: The `element` param for `AppRootElementBlueprint` no longer accepts a component. If you are currently passing a component such as `element: () => ` or `element: MyComponent`, simply switch to `element: `. + +- a321f3b: **BREAKING**: The `CommonAnalyticsContext` has been removed, and inlined into `AnalyticsContextValue` instead. + +### Patch Changes + +- d9e00e3: Add support for a new `aliasFor` option for `createRouteRef`. This allows for the creation of a new route ref that acts as an alias for an existing route ref that is installed in the app. This is particularly useful when creating modules that override existing plugin pages, without referring to the existing plugin. For example: + + ```tsx + export default createFrontendModule({ + pluginId: 'catalog', + extensions: [ + PageBlueprint.make({ + params: { + defaultPath: '/catalog', + routeRef: createRouteRef({ aliasFor: 'catalog.catalogIndex' }), + loader: () => + import('./CustomCatalogIndexPage').then(m => ( + + )), + }, + }), + ], + }); + ``` + +- 93b5e38: Plugins should now use the new `AnalyticsImplementationBlueprint` to define and provide concrete analytics implementations. For example: + + ```ts + import { AnalyticsImplementationBlueprint } from '@backstage/frontend-plugin-api'; + + const AcmeAnalytics = AnalyticsImplementationBlueprint.make({ + name: 'acme-analytics', + params: define => + define({ + deps: { config: configApiRef }, + factory: ({ config }) => AcmeAnalyticsImpl.fromConfig(config), + }), + }); + ``` + +- 948de17: Tweaked the return types from `createExtension` and `createExtensionBlueprint` to avoid the forwarding of `ConfigurableExtensionDataRef` into exported types. + +- 147482b: Updated the recommended naming of the blueprint param callback from `define` to `defineParams`, making the syntax `defineParams => defineParams(...)`. + +- 3c3c882: Added added defaults for all type parameters of `ExtensionDataRef` and deprecated `AnyExtensionDataRef`, as it is now redundant. + +- Updated dependencies + - @backstage/core-components@0.17.5-next.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/plugin-app@0.2.0-next.1 + +### Minor Changes + +- 121899a: **BREAKING**: The `app-root-element` extension now only accepts `JSX.Element` in its `element` param, meaning overrides need to be updated. + +### Patch Changes + +- a08f95f: Added a new module for implementing public sign-in apps, exported as `appModulePublicSignIn` via the `/alpha` sub-path export. This replaces the `createPublicSignInApp` export from `@backstage/frontend-defaults`, which is now deprecated. +- 5d31d66: Updated the usage of the `RouterBlueprint` and `AppRootWrapperBlueprint` to use the lowercase `component` parameter +- 93b5e38: The default implementation of the Analytics API now collects and instantiates analytics implementations exposed via `AnalyticsImplementationBlueprint` extensions. If no such extensions are discovered, the API continues to do nothing with analytics events fired within Backstage. If multiple such extensions are discovered, every discovered implementation automatically receives analytics events. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-components@0.17.5-next.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/theme@0.6.8-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-catalog-react@1.20.0-next.2 + +### Minor Changes + +- e4ddf22: **BREAKING ALPHA**: The `defaultPath`, `defaultTitle`, and `defaultGroup` params of `PageBlueprint` has been renamed to `path`, `title`, and `group`. The `convertLegacyEntityContentExtension` utility has also received the same change. This change does not affect the compatibility of extensions created with older versions of this blueprint. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/frontend-test-utils@0.3.5-next.2 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/core-components@0.17.5-next.1 + +### Patch Changes + +- 5563605: Added `FavoriteToggleProps`. +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.8-next.0 + - @backstage/version-bridge@1.0.11 + +## @backstage/create-app@0.7.2-next.2 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.15 + +## @backstage/frontend-test-utils@0.3.5-next.2 + +### Patch Changes + +- df7bd3b: Updated import of the `FrontendFeature` type. +- 5d31d66: Updated the usage of the `RouterBlueprint` and `AppRootWrapperBlueprint` to use the lowercase `component` parameter +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/frontend-app-api@0.12.0-next.2 + - @backstage/plugin-app@0.2.0-next.1 + - @backstage/config@1.3.3 + - @backstage/test-utils@1.7.11-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/ui@0.7.0-next.2 + +### Patch Changes + +- d4e603e: Updated Menu component in Backstage UI to use useId() from React Aria instead of React to support React 17. + +## @backstage/plugin-api-docs@0.12.10-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-catalog@1.31.2-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-app-visualizer@0.1.22-next.1 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-components@0.17.5-next.1 + - @backstage/core-plugin-api@1.10.9 + +## @backstage/plugin-catalog@1.31.2-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-search-react@1.9.3-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-scaffolder-common@1.7.0-next.0 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + +## @backstage/plugin-catalog-graph@0.4.22-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/types@1.2.1 + +## @backstage/plugin-catalog-import@0.13.4-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + +## @backstage/plugin-devtools@0.1.30-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.17 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-home@0.8.11-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- 121899a: **BREAKING ALPHA**: The `app-root-element` extension now only accepts `JSX.Element` in its `element` param, meaning overrides need to be updated. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.8-next.0 + - @backstage/plugin-home-react@0.1.29-next.0 + +## @backstage/plugin-kubernetes@0.12.10-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-kubernetes-common@0.9.6 + - @backstage/plugin-kubernetes-react@0.5.10-next.0 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-notifications@0.5.8-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.8-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-signals-react@0.0.15 + +## @backstage/plugin-org@0.6.42-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-scaffolder@1.34.0-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-scaffolder-common@1.7.0-next.0 + - @backstage/plugin-scaffolder-react@1.19.0-next.1 + +## @backstage/plugin-search@1.4.29-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-search-react@1.9.3-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-module-catalog@0.3.7-next.1 + +### Patch Changes + +- d9bda0f: Allow filter to be an array in config schema +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-react@1.9.3-next.1 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-components@0.17.5-next.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.8-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-signals@0.0.22-next.2 + +### Patch Changes + +- 121899a: **BREAKING ALPHA**: The `app-root-element` extension now only accepts `JSX.Element` in its `element` param, meaning overrides need to be updated. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.8-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.15 + +## @backstage/plugin-techdocs@1.14.0-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-search-react@1.9.3-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/theme@0.6.8-next.0 + - @backstage/plugin-auth-react@0.1.18-next.0 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + +## @backstage/plugin-techdocs-backend@2.0.5-next.1 + +### Patch Changes + +- 484e500: Updated CachedEntityLoader to use BackstageCredentials instead of raw tokens for cache key generation. It now uses principal-based identification (user entity ref for users, subject for services) instead of token-based keys, providing more consistent caching behavior. +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.5-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-node@1.13.6-next.0 + +## @backstage/plugin-user-settings@0.8.25-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.8-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.15 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.112-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.12.0-next.2 + - @backstage/cli@0.34.0-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.2 + - @backstage/plugin-catalog-import@0.13.4-next.2 + - @backstage/plugin-catalog-graph@0.4.22-next.2 + - @backstage/plugin-notifications@0.5.8-next.2 + - @backstage/plugin-user-settings@0.8.25-next.2 + - @backstage/plugin-search-react@1.9.3-next.1 + - @backstage/plugin-kubernetes@0.12.10-next.2 + - @backstage/plugin-scaffolder@1.34.0-next.2 + - @backstage/plugin-api-docs@0.12.10-next.2 + - @backstage/plugin-devtools@0.1.30-next.2 + - @backstage/plugin-techdocs@1.14.0-next.2 + - @backstage/plugin-catalog@1.31.2-next.2 + - @backstage/plugin-search@1.4.29-next.2 + - @backstage/plugin-home@0.8.11-next.2 + - @backstage/ui@0.7.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/plugin-signals@0.0.22-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/app-defaults@1.6.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/theme@0.6.8-next.0 + - @backstage/plugin-auth-react@0.1.18-next.0 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-kubernetes-cluster@0.0.28-next.1 + - @backstage/plugin-org@0.6.42-next.2 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-scaffolder-react@1.19.0-next.1 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.27-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + +## example-app-next@0.0.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-defaults@0.3.0-next.2 + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/frontend-app-api@0.12.0-next.2 + - @backstage/cli@0.34.0-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.2 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-app-visualizer@0.1.22-next.1 + - @backstage/plugin-catalog-import@0.13.4-next.2 + - @backstage/plugin-catalog-graph@0.4.22-next.2 + - @backstage/plugin-notifications@0.5.8-next.2 + - @backstage/plugin-user-settings@0.8.25-next.2 + - @backstage/plugin-search-react@1.9.3-next.1 + - @backstage/plugin-kubernetes@0.12.10-next.2 + - @backstage/plugin-scaffolder@1.34.0-next.2 + - @backstage/plugin-api-docs@0.12.10-next.2 + - @backstage/plugin-techdocs@1.14.0-next.2 + - @backstage/plugin-catalog@1.31.2-next.2 + - @backstage/plugin-search@1.4.29-next.2 + - @backstage/plugin-home@0.8.11-next.2 + - @backstage/plugin-app@0.2.0-next.1 + - @backstage/ui@0.7.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/plugin-signals@0.0.22-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/app-defaults@1.6.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/theme@0.6.8-next.0 + - @backstage/plugin-auth-react@0.1.18-next.0 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-kubernetes-cluster@0.0.28-next.1 + - @backstage/plugin-org@0.6.42-next.2 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-scaffolder-react@1.19.0-next.1 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.27-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + +## techdocs-cli-embedded-app@0.2.111-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.34.0-next.1 + - @backstage/plugin-techdocs@1.14.0-next.2 + - @backstage/plugin-catalog@1.31.2-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/app-defaults@1.6.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/test-utils@1.7.11-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 diff --git a/docs/releases/v1.42.0-next.3-changelog.md b/docs/releases/v1.42.0-next.3-changelog.md new file mode 100644 index 0000000000..b05ca6021f --- /dev/null +++ b/docs/releases/v1.42.0-next.3-changelog.md @@ -0,0 +1,435 @@ +# Release v1.42.0-next.3 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.42.0-next.3](https://backstage.github.io/upgrade-helper/?to=1.42.0-next.3) + +## @backstage/cli@0.34.0-next.2 + +### Minor Changes + +- 923ceb2: **BREAKING**: The new app build based on [Rspack](https://rspack.dev/) is now the default, and the `EXPERIMENTAL_RSPACK` flag has been removed. To revert to the old behavior, set the `LEGACY_WEBPACK_BUILD` environment flag and install the following optional dependencies: + + ```json + { + "dependencies": { + "@module-federation/enhanced": "^0.9.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", + "esbuild-loader": "^4.0.0", + "eslint-webpack-plugin": "^4.2.0", + "fork-ts-checker-webpack-plugin": "^9.0.0", + "mini-css-extract-plugin": "^2.4.2", + "terser-webpack-plugin": "^5.1.3", + "webpack": "^5.96.0", + "webpack-dev-server": "^5.0.0" + } + } + ``` + + If you do encounter a blocking issue that forces you to use the old WebPack build, please [open an issue](https://github.com/backstage/backstage/issues) explaining the problem. The WebPack build will be removed in a future release. + +- eda80c7: **BREAKING**: Removed support for `.icon.svg` imports, which have been deprecated since the 1.19 release. + +### Patch Changes + +- 8b1bf6e: Deprecated new frontend system config setting `app.experimental.packages` to just `app.packages`. The old config will continue working for the time being, but may be removed in a future release. +- ead626f: The Node.js transform in `@backstage/cli/config/nodeTransformHooks.mjs` now supports the built-in type stripping in Node.js, which is enabled by default from v22.18.0. + +## @backstage/frontend-app-api@0.12.0-next.3 + +### Minor Changes + +- 8e21c4d: Use an app plugin for built-in extension app node specs. +- 8e21c4d: The `AppNodeSpec.plugin` property is now required. +- 5e12252: **BREAKING**: Restructured some of option fields of `createApp` and `createSpecializedApp`. + + - For `createApp`, all option fields _except_ `features` and `bindRoutes` have been moved into a new `advanced` object field. + - For `createSpecializedApp`, all option fields _except_ `features`, `config`, and `bindRoutes` have been moved into a new `advanced` object field. + + This helps highlight that some options are meant to rarely be needed or used, and simplifies the usage of those options that are almost always required. + + As an example, if you used to supply a custom config loader, you would update your code as follows: + + ```diff + createApp({ + features: [...], + - configLoader: new MyCustomLoader(), + + advanced: { + + configLoader: new MyCustomLoader(), + + }, + }) + ``` + +### Patch Changes + +- f3f9d57: Renaming the `getNodesByRoutePath` parameter from `sourcePath` to `routePath` +- 8b1bf6e: Deprecated new frontend system config setting `app.experimental.packages` to just `app.packages`. The old config will continue working for the time being, but may be removed in a future release. +- fda1bbc: Added a default implementation of the `SwappableComponentsApi` and removing the legacy `ComponentsApi` implementation +- 1c2cc37: Improved runtime error message clarity when extension factories don't return an iterable object. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.2 + - @backstage/frontend-defaults@0.3.0-next.3 + +## @backstage/frontend-defaults@0.3.0-next.3 + +### Minor Changes + +- 5e12252: **BREAKING**: Restructured some of option fields of `createApp` and `createSpecializedApp`. + + - For `createApp`, all option fields _except_ `features` and `bindRoutes` have been moved into a new `advanced` object field. + - For `createSpecializedApp`, all option fields _except_ `features`, `config`, and `bindRoutes` have been moved into a new `advanced` object field. + + This helps highlight that some options are meant to rarely be needed or used, and simplifies the usage of those options that are almost always required. + + As an example, if you used to supply a custom config loader, you would update your code as follows: + + ```diff + createApp({ + features: [...], + - configLoader: new MyCustomLoader(), + + advanced: { + + configLoader: new MyCustomLoader(), + + }, + }) + ``` + +### Patch Changes + +- 8b1bf6e: Deprecated new frontend system config setting `app.experimental.packages` to just `app.packages`. The old config will continue working for the time being, but may be removed in a future release. +- e5a0a99: **BREAKING**: The `loadingComponent` option has been renamed to `loadingElement`, which is now found under `advanced.loadingElement`. The default loading element has also been switched to `` from `@backstage/core-components`. This is of course an improvement over the previous `"Loading..."` text, but also helps prevent flicker when the app loading is fast. +- Updated dependencies + - @backstage/plugin-app@0.2.0-next.2 + - @backstage/frontend-plugin-api@0.11.0-next.2 + - @backstage/frontend-app-api@0.12.0-next.3 + - @backstage/core-components@0.17.5-next.2 + +## @backstage/frontend-plugin-api@0.11.0-next.2 + +### Minor Changes + +- fda1bbc: **BREAKING**: The component system has been overhauled to use `SwappableComponent` instead of `ComponentRef`. Several APIs have been removed and replaced: + + - Removed: `createComponentRef`, `createComponentExtension`, `ComponentRef`, `ComponentsApi`, `componentsApiRef`, `useComponentRef`, `coreComponentRefs` + - Added: `createSwappableComponent`, `SwappableComponentBlueprint`, `SwappableComponentRef`, `SwappableComponentsApi`, `swappableComponentsApiRef` + + **BREAKING**: The default `componentRefs` and exported `Core*Props` have been removed and have replacement `SwappableComponents` and revised type names instead. + + - The `errorBoundaryFallback` component and `CoreErrorBoundaryFallbackProps` type have been replaced with `ErrorDisplay` swappable component and `CoreErrorDisplayProps` respectively. + - The `progress` component and `CoreProgressProps` type have been replaced with `Progress` swappable component and `ProgressProps` respectively. + - The `notFoundErrorPage` component and `CoreNotFoundErrorPageProps` type have been replaced with `NotFoundErrorPage` swappable component and `NotFoundErrorPageProps` respectively. + + **Migration for creating swappable components:** + + ```tsx + // OLD: Using createComponentRef and createComponentExtension + import { + createComponentRef, + createComponentExtension, + } from '@backstage/frontend-plugin-api'; + + const myComponentRef = createComponentRef<{ title: string }>({ + id: 'my-plugin.my-component', + }); + + const myComponentExtension = createComponentExtension({ + ref: myComponentRef, + loader: { + lazy: () => import('./MyComponent').then(m => m.MyComponent), + }, + }); + + // NEW: Using createSwappableComponent and SwappableComponentBlueprint + import { + createSwappableComponent, + SwappableComponentBlueprint, + } from '@backstage/frontend-plugin-api'; + + const MySwappableComponent = createSwappableComponent({ + id: 'my-plugin.my-component', + loader: () => import('./MyComponent').then(m => m.MyComponent), + }); + + const myComponentExtension = SwappableComponentBlueprint.make({ + name: 'my-component', + params: { + component: MySwappableComponent, + loader: () => import('./MyComponent').then(m => m.MyComponent), + }, + }); + ``` + + **Migration for using components:** + + ```tsx + // OLD: Using ComponentsApi and useComponentRef + import { + useComponentRef, + componentsApiRef, + useApi, + coreComponentRefs, + } from '@backstage/frontend-plugin-api'; + + const MyComponent = useComponentRef(myComponentRef); + const ProgressComponent = useComponentRef(coreComponentRefs.progress); + ``` + +// NEW: Direct component usage +import { Progress } from '@backstage/frontend-plugin-api'; + +// Use directly as React Component + + + +```` + +**Migration for core component references:** + +```tsx +// OLD: Core component refs +import { coreComponentRefs } from '@backstage/frontend-plugin-api'; + +coreComponentRefs.progress +coreComponentRefs.notFoundErrorPage +coreComponentRefs.errorBoundaryFallback + +// NEW: Direct swappable component imports +import { Progress, NotFoundErrorPage, ErrorDisplay } from '@backstage/frontend-plugin-api'; + +// Use directly as React components + + + +```` + +- 6a75e00: **BREAKING**: Removed the deprecated `createFrontendPlugin` variant where the plugin ID is passed via an `id` option. To update existing code, switch to using the `pluginId` option instead. +- 1e6410b: **BREAKING**: The `ResolveInputValueOverrides` type is no longer exported. + +### Patch Changes + +- 9831f4e: Adjusted the dialog API types to have more sensible defaults +- 1c2cc37: Improved runtime error message clarity when extension factories don't return an iterable object. +- 24558f0: Added inline documentation for `createExtension`, `createExtensionBlueprint`, `createFrontendPlugin`, and `createFrontendModule`. +- Updated dependencies + - @backstage/core-components@0.17.5-next.2 + +## @backstage/ui@0.7.0-next.3 + +### Minor Changes + +- 0615e54: We are moving our DataTable component to React Aria. We removed our DataTable to only use Table as a single and opinionated option for tables. This new structure is made possible by using React Aria under the hood. + +### Patch Changes + +- 230b410: **Breaking change** Move breadcrumb to fit in the `HeaderPage` instead of the `Header` in Backstage UI. +- 8bdc491: Remove stylesheet import from Select component. +- 404b426: Add `startCollapsed` prop on the `SearchField` component in BUI. + +## @backstage/plugin-app@0.2.0-next.2 + +### Minor Changes + +- fda1bbc: **BREAKING**: The `componentsApi` implementation has been removed from the plugin and replaced with the new `SwappableComponentsApi` instead. + + If you were overriding the `componentsApi` implementation, you can now use the new `SwappableComponentsApi` instead. + + ```ts + // old + appPlugin.getExtension('api:app/components').override(...) + + // new + appPlugin.getExtension('api:app/swappable-components').override(...) + ``` + +### Patch Changes + +- 91cbdf4: Log a warning when `SwappableComponent` extensions are installed outside of using the `app` plugin + +- fda1bbc: Default implementations of core components are now provided by this package. + + A backwards compatible `componentsApi` implementation is also provided from this package which uses the `SwappableComponentsApi` as the implementation. This backwards compatible wrapper will be removed in the future. + +- 9831f4e: Adjusted the dialog API types to have more sensible defaults + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.2 + - @backstage/core-components@0.17.5-next.2 + +## @backstage/plugin-kubernetes-backend@0.20.0-next.1 + +### Minor Changes + +- 759568d: **BREAKING CHANGE**: Removed support for the legacy backend system. This means that the deprecated `createRouter` and `KubernetesBuilder` and related types have been removed. Please refer to the [relevant documentation](https://backstage.io/docs/features/kubernetes/installation/#adding-kubernetes-backend-plugin) to configure the Kubernetes plugin. + + **BREAKING CHANGE**: The deprecated types `AuthenticationStrategy`, `AuthMetadata`, `ClusterDetails`, `CustomResource`, `CustomResourcesByEntity`, `FetchResponseWrapper`, `KubernetesBuilder`, `KubernetesBuilderReturn`, `KubernetesClustersSupplier`, `KubernetesCredential`, `KubernetesEnvironment`, `KubernetesFetcher`, `KubernetesObjectsProvider`, `KubernetesObjectTypes`, `KubernetesServiceLocator`,`ObjectFetchParams`, `ObjectToFetch`,`RouterOptions` and `ServiceLocatorRequestContext` should all now be imported from `@backstage/plugin-kubernetes-node`. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-node@0.3.3-next.0 + +## @backstage/plugin-scaffolder@1.34.0-next.3 + +### Minor Changes + +- b1c0696: Add resizable panels width for the editor and preview panels in the template editor and template form playground layouts. Users can now resize these panels by dragging the divider between the two areas. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.2 + - @backstage/core-compat-api@0.5.0-next.3 + - @backstage/core-components@0.17.5-next.2 + +## @backstage/plugin-scaffolder-backend@2.2.0-next.1 + +### Minor Changes + +- 2032660: Fixed fs:readdir action example +- 11dc90f: Implement max length for scaffolder auditor audit logging with default of 256 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.4-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.8.2-next.1 + +## @backstage/backend-openapi-utils@0.6.0-next.1 + +### Patch Changes + +- 3760352: Update `express-openapi-validator` to 5.5.8 to fix security vulnerability in transitive dependency `multer` + +## @backstage/core-compat-api@0.5.0-next.3 + +### Patch Changes + +- fda1bbc: The `compatWrapper` has been switched to use the new `SwappableComponentsApi` instead of the old `ComponentsApi` in its bridging to the old frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.2 + +## @backstage/core-components@0.17.5-next.2 + +### Patch Changes + +- 77467bb: Updated dependency `linkifyjs` to `4.3.2`. + +## @backstage/create-app@0.7.2-next.3 + +### Patch Changes + +- 8b1bf6e: Updated the `app.packages` config setting now that it no longer is experimental + +## @backstage/repo-tools@0.15.1-next.1 + +### Patch Changes + +- 33060b5: Removed build-in ignore of the `packages/canon` package for knip reports. + +## @backstage/plugin-api-docs@0.12.10-next.3 + +### Patch Changes + +- 8b1bf6e: Updated README instructions for the new frontend system +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.2 + - @backstage/core-compat-api@0.5.0-next.3 + - @backstage/core-components@0.17.5-next.2 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.6-next.1 + +### Patch Changes + +- 94476d2: Updated dependency `@davidzemon/passport-okta-oauth` to `^0.0.7`. + +## @backstage/plugin-catalog-backend@3.0.1-next.1 + +### Patch Changes + +- 1752be6: Attempt to circumvent event listener memory leak in compression middleware + +- 9dd213c: Make the processing hash calculation not care about the order of the processors. + + This change does not affect the behavior of the catalog, but it will make the processing + hash calculation more robust against changes in the order of processors. This should lead to + more stable processing hashes, which in turn should lead to fewer unnecessary reprocessing + of entities. + + After deploying this fix, you may see a period of increased processing and stitching, but + this should stabilize over time as the processing hashes become more consistent. + +- fa6fa60: Fixed getLocationByEntity to use `original_value` instead of `value` when querying search table + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.0-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.10.2-next.1 + +### Patch Changes + +- f6c64d1: Fix GitHub catalog entity provider branch matching on event processing. +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.1-next.1 + +## @backstage/plugin-catalog-graph@0.4.22-next.3 + +### Patch Changes + +- 8b1bf6e: Updated README instructions for the new frontend system +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.2 + - @backstage/core-compat-api@0.5.0-next.3 + - @backstage/core-components@0.17.5-next.2 + +## @backstage/plugin-events-backend-module-kafka@0.1.2-next.1 + +### Patch Changes + +- 0d38009: Remove luxon dependency and minor internal improvements + +## @backstage/plugin-kubernetes@0.12.10-next.3 + +### Patch Changes + +- 3025cf5: Removed the kubernetes content padding to avoid double padding on k8s entity page +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.2 + - @backstage/core-compat-api@0.5.0-next.3 + - @backstage/core-components@0.17.5-next.2 + +## @backstage/plugin-org@0.6.42-next.3 + +### Patch Changes + +- 8b1bf6e: Updated README instructions for the new frontend system +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.2 + - @backstage/core-compat-api@0.5.0-next.3 + - @backstage/core-components@0.17.5-next.2 + +## @backstage/plugin-proxy-backend@0.6.5-next.1 + +### Patch Changes + +- 0810cd8: correct rewrite rule to avoid extra subpath in proxy path + +## @backstage/plugin-scaffolder-backend-module-github@0.8.2-next.1 + +### Patch Changes + +- a22cce0: Fixed bug in the `customProperties` type which was preventing it being used to set a list of values against a key (e.g. for multi-select fields) + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.4-next.1 + +### Patch Changes + +- 5bb870b: Show additional information about the cause in error messages from GitLab + +## example-backend@0.0.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.1-next.1 + - @backstage/plugin-kubernetes-backend@0.20.0-next.1 + - @backstage/plugin-scaffolder-backend@2.2.0-next.1 + - @backstage/plugin-proxy-backend@0.6.5-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.8.2-next.1 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/configuring-plugin-databases.md b/docs/tutorials/configuring-plugin-databases.md index c6d9869d38..2bbe83557c 100644 --- a/docs/tutorials/configuring-plugin-databases.md +++ b/docs/tutorials/configuring-plugin-databases.md @@ -1,7 +1,6 @@ --- id: configuring-plugin-databases title: Configuring Plugin Databases -# prettier-ignore description: Guide on how to configure Backstage databases. --- diff --git a/docs/tutorials/manual-knex-rollback.md b/docs/tutorials/manual-knex-rollback.md index 14e00d6659..b0f67ba3e0 100644 --- a/docs/tutorials/manual-knex-rollback.md +++ b/docs/tutorials/manual-knex-rollback.md @@ -1,7 +1,6 @@ --- id: manual-knex-rollback title: Manual Rollback using Knex -# prettier-ignore description: Guide on how to rollback Knex migrations. --- diff --git a/docs/tutorials/setup-opentelemetry.md b/docs/tutorials/setup-opentelemetry.md index 23cd71b10f..4803933736 100644 --- a/docs/tutorials/setup-opentelemetry.md +++ b/docs/tutorials/setup-opentelemetry.md @@ -4,9 +4,9 @@ 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/). +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/). This tutorial also includes exporting traces using the JSON/HTTP exporter with Jaeger being the ideal target, but this too can be adjusted to fit your needs by seeing the supported tooling in the [OTLP exporters](https://opentelemetry.io/docs/instrumentation/js/exporters/) documentation. ## Install dependencies @@ -19,14 +19,15 @@ The `auto-instrumentations-node` will automatically create spans for code called yarn --cwd packages/backend add \ @opentelemetry/sdk-node \ @opentelemetry/auto-instrumentations-node \ - @opentelemetry/exporter-prometheus + @opentelemetry/exporter-prometheus \ + @opentelemetry/exporter-trace-otlp-http ``` ## Configure 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'); @@ -36,12 +37,20 @@ if (isMainThread) { getNodeAutoInstrumentations, } = require('@opentelemetry/auto-instrumentations-node'); const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus'); + const { + OTLPTraceExporter, + } = require('@opentelemetry/exporter-trace-otlp-http'); // By default exports the metrics on localhost:9464/metrics - const prometheus = new PrometheusExporter(); + const prometheusExporter = new PrometheusExporter(); + // We post the traces to localhost:4318/v1/traces + const otlpTraceExporter = new OTLPTraceExporter({ + // Default Jaeger URL trace endpoint. + url: 'http://localhost:4318/v1/traces', + }); const sdk = new NodeSDK({ - // You can add a traceExporter field here too - metricReader: prometheus, + metricReader: prometheusExporter, + traceExporter: otlpTraceExporter, instrumentations: [getNodeAutoInstrumentations()], }); @@ -52,6 +61,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 +112,35 @@ 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: + +### Troubleshooting + +If you are having issues getting metrics or traces working there are some helpful diagnostic tools from OpenTelemetry you can use that can help. + +First we need to the `@opentelemetry/api` package: + +```bash +yarn --cwd packages/backend add @opentelemetry/api +``` + +Then we want to add the following snippet before the `sdk.start()` call: + +```js +const { diag, DiagConsoleLogger, DiagLogLevel } = require('@opentelemetry/api'); + +diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); +``` + +This will then add OpenTelemetry debug logs that you can then look at to help get a better idea of why something may not be working as expected. + +We don't recommend shipping this in production because of the log density. ## Production Setup In your `.dockerignore`, add this line: -``` +```text !packages/backend/src/instrumentation.js ``` diff --git a/docs/tutorials/switching-sqlite-postgres.md b/docs/tutorials/switching-sqlite-postgres.md index aec9ca2ee9..4fc405c61c 100644 --- a/docs/tutorials/switching-sqlite-postgres.md +++ b/docs/tutorials/switching-sqlite-postgres.md @@ -1,7 +1,6 @@ --- id: switching-sqlite-postgres title: Switching Backstage from SQLite to PostgreSQL -# prettier-ignore description: How to get ready for deploying Backstage to production with PostgreSQL --- diff --git a/docs/tutorials/using-backstage-proxy-within-plugin.md b/docs/tutorials/using-backstage-proxy-within-plugin.md index f171f951c4..58e973d891 100644 --- a/docs/tutorials/using-backstage-proxy-within-plugin.md +++ b/docs/tutorials/using-backstage-proxy-within-plugin.md @@ -1,7 +1,6 @@ --- id: using-backstage-proxy-within-plugin title: Using the Backstage Proxy from Within a Plugin -# prettier-ignore description: Guide on how to create a set of API bindings that interface with a backend via the backstage proxy --- @@ -22,10 +21,11 @@ If your plugin requires access to an API, backstage offers - [Setting up the backstage proxy](#setting-up-the-backstage-proxy) - [Calling an API using the backstage proxy](#calling-an-api-using-the-backstage-proxy) - - [Defining the API client interface](#defining-the-api-client-interface) - - [Creating the API client](#creating-the-api-client) - - [Bundling your ApiRef with your plugin](#bundling-your-apiref-with-your-plugin) - - [Using the API in your components](#using-your-plugin-in-your-components) + - [Option 1: Calling the proxy directly from the frontend plugin](#option-1-calling-the-proxy-directly-from-the-frontend-plugin) + - [Option 2: Defining the API client interface](#defining-the-api-client-interface) + - [Creating the API client](#creating-the-api-client) + - [Bundling your ApiRef with your plugin](#bundling-your-apiref-with-your-plugin) + - [Using the API in your components](#using-your-plugin-in-your-components) ## Setting up the backstage proxy @@ -55,9 +55,44 @@ the proxy. Backstage is structured in such a way that you could run the backstage frontend independently of the backend. So when calling your API you need to prepend the backend URL to your http call. -The recommended pattern for calling out to services is to wrap your calls in a -[Utility API](../api/utility-apis.md). This section describes the steps to wrap -your API client in a Utility API, which are: +There are two recommended patterns for calling out to services: using +`discoveryApi` and +`fetchApi` directly from your frontend plugin or wrapping your calls in a [Utility API](../api/utility-apis.md). + +## Option 1: Calling the proxy directly from the frontend plugin + +From you frontend plugin use the `fetchApi` and `discoveryApi` to call the proper +proxy endpoint: + +```tsx title="plugins/my-awesome-plugin/src/components/AwesomeUsersTable.tsx" +import { + useApi, + discoveryApiRef, + fetchApiRef, +} from '@backstage/core-plugin-api'; +import { Progress, Alert } from '@backstage/core-components'; +import useAsync from 'react-use/esm/useAsync'; +import { myAwesomeApiRef } from '../../api'; + +export const AwesomeUsersTable = () => { + const fetchApi = useApi(fetchApiRef); + const discoveryApi = useApi(discoveryApiRef); + + const { value, loading, error } = useAsync(async () => { + const baseUrl = await discoveryApi.getBaseUrl('proxy'); + // As configured previously for the backend proxy + const resp = await fetchApi.fetch(`${baseUrl}/`); + if (!resp.ok) throw new Error(resp.statusText); + return resp.json(); + }, [fetchApi, discoveryApi]); + + // ... +}; +``` + +## Option 2: Defining the API client interface + +This section describes the steps to wrap your API client in a [Utility API](../api/utility-apis.md), which are: - use [`createApiRef`](../reference/core-plugin-api.createapiref.md) to create a new [`ApiRef`](../reference/core-plugin-api.apiref.md) @@ -69,7 +104,7 @@ your API client in a Utility API, which are: - finally, you can use your API in your components by calling [`useApi`](../reference/core-plugin-api.useapi.md) -## Defining the API client interface +### Defining the API client interface Continuing from the previous example, let's assume that _https://api.myawesomeservice.com/v1_ has the following endpoints: @@ -103,7 +138,7 @@ export const myAwesomeApiRef = createApiRef({ }); ``` -## Creating the API client +### Creating the API client The `myAwesomeApiRef` is what you will use within backstage to reference the API client in your plugin. The API ref itself is a global singleton object that @@ -155,7 +190,7 @@ export class MyAwesomeApiClient implements MyAwesomeApi { > [DiscoveryApi](../reference/core-plugin-api.discoveryapi.md) or the > [FetchApi](../reference/core-plugin-api.fetchapi.md) -## Bundling your ApiRef with your plugin +### Bundling your ApiRef with your plugin The final piece in the puzzle is bundling the `myAwesomeApiRef` with a factory for `MyAwesomeApiClient` objects. This is usually done in the `plugin.ts` file @@ -195,22 +230,24 @@ export const myCustomPlugin = createPlugin({ }); ``` -## Using the API in your components +### Using the API in your components Now you should be able to access your API using the backstage hook [`useApi`](../reference/core-plugin-api.useapi.md) from within your plugin code. -```ts -/* plugins/my-awesome-plugin/src/components/AwesomeUsersTable.tsx */ +```ts title="plugins/my-awesome-plugin/src/components/AwesomeUsersTable.tsx" import { useApi } from '@backstage/core-plugin-api'; import { myAwesomeApiRef } from '../../api'; +import useAsync from 'react-use/esm/useAsync'; export const AwesomeUsersTable = () => { const apiClient = useApi(myAwesomeApiRef); - apiClient.listUsers() - .then( - ... - ) -} + const { value, loading, error } = useAsync(async () => { + const users = await apiClient.listUsers(); + return users; + }, [apiClient]); + + // ... +}; ``` diff --git a/microsite/blog/2020-05-22-phase-2-service-catalog.mdx b/microsite/blog/2020-05-22-phase-2-service-catalog.mdx index 81c99c7c07..9e681b9da3 100644 --- a/microsite/blog/2020-05-22-phase-2-service-catalog.mdx +++ b/microsite/blog/2020-05-22-phase-2-service-catalog.mdx @@ -1,6 +1,5 @@ --- -# prettier-ignore -title: "Starting Phase 2: The Service Catalog" +title: 'Starting Phase 2: The Service Catalog' author: Stefan Ålund, Spotify authorURL: http://twitter.com/stalund authorImageURL: https://pbs.twimg.com/profile_images/121166861/6919c047c0d0edaace78c3009b28e917-user-full-200-130.generated_400x400.jpg diff --git a/microsite/blog/2020-09-08-announcing-tech-docs.mdx b/microsite/blog/2020-09-08-announcing-tech-docs.mdx index 40ab40e5f0..9235eeb401 100644 --- a/microsite/blog/2020-09-08-announcing-tech-docs.mdx +++ b/microsite/blog/2020-09-08-announcing-tech-docs.mdx @@ -1,6 +1,5 @@ --- -# prettier-ignore -title: "Announcing TechDocs: Spotify’s docs-like-code plugin for Backstage" +title: 'Announcing TechDocs: Spotify’s docs-like-code plugin for Backstage' author: Gary Niemen, Spotify authorURL: https://github.com/garyniemen --- @@ -118,7 +117,7 @@ Code-like-docs, what? Okay, it’s just my little play on words. This is what I ### Golden Paths -At Spotify, we have the concept of [Golden Paths](https://engineering.atspotify.com/2020/08/17/how-we-use-golden-paths-to-solve-fragmentation-in-our-software-ecosystem/) — one for each engineering discipline. My favourite definition of Golden Path is that it is the “opinionated and supported path”. Each Golden Path has an accompanying Golden Path tutorial that walks you through the opinionated and supported path. +At Spotify, we have the concept of [Golden Paths](https://engineering.atspotify.com/2020/08/how-we-use-golden-paths-to-solve-fragmentation-in-our-software-ecosystem/) — one for each engineering discipline. My favourite definition of Golden Path is that it is the “opinionated and supported path”. Each Golden Path has an accompanying Golden Path tutorial that walks you through the opinionated and supported path. The Golden Path tutorials are Spotify’s most used and important documents and have shown themselves to be the most challenging to manage within a docs-like-code environment. One reason for this is that they are long, divided into many parts, and ownership is typically spread among many different teams. We have had to make use of GitHub codeowners to handle ownership and had to create datasets and data pipelines to be able to attach GitHub Issues to the specific parts or files that a team owns. Another challenge of the Golden Path tutorials is that parts are often dependent on other parts. We are just starting to look into how we can solve these dependency challenges in order to remove friction for engineers writing tutorial documentation. diff --git a/microsite/blog/2020-10-22-cost-insights-plugin.mdx b/microsite/blog/2020-10-22-cost-insights-plugin.mdx index 1ffddd3303..293cd1c927 100644 --- a/microsite/blog/2020-10-22-cost-insights-plugin.mdx +++ b/microsite/blog/2020-10-22-cost-insights-plugin.mdx @@ -1,6 +1,5 @@ --- -# prettier-ignore -title: "New Cost Insights plugin: The engineer’s solution to taming cloud costs" +title: 'New Cost Insights plugin: The engineer’s solution to taming cloud costs' author: Janisa Anandamohan, Spotify authorURL: https://twitter.com/janisa_a --- diff --git a/microsite/blog/2021-01-12-new-backstage-feature-kubernetes-for-service-owners.mdx b/microsite/blog/2021-01-12-new-backstage-feature-kubernetes-for-service-owners.mdx index c1232a5f12..1303f0dd15 100644 --- a/microsite/blog/2021-01-12-new-backstage-feature-kubernetes-for-service-owners.mdx +++ b/microsite/blog/2021-01-12-new-backstage-feature-kubernetes-for-service-owners.mdx @@ -1,6 +1,5 @@ --- -# prettier-ignore -title: "New Backstage feature: Kubernetes for Service owners" +title: 'New Backstage feature: Kubernetes for Service owners' author: Matthew Clarke, Spotify authorURL: https://github.com/mclarke47 --- diff --git a/microsite/blog/2021-05-20-adopting-backstage.mdx b/microsite/blog/2021-05-20-adopting-backstage.mdx index 9fc1e36618..6d4204633c 100644 --- a/microsite/blog/2021-05-20-adopting-backstage.mdx +++ b/microsite/blog/2021-05-20-adopting-backstage.mdx @@ -14,7 +14,7 @@ One of the greatest strengths of Backstage also presents a never-ending challeng Before providing recommendations on getting started with Backstage, it’s helpful to have a bit of context as to why Spotify made the design decisions we did. No two companies are identical — and thus, no two Backstage implementations are the same. -Rolling back the clock just a few years, [Spotify was challenged](https://engineering.atspotify.com/2021/05/18/a-product-story-the-lessons-of-backstage-and-spotifys-autonomous-culture/) to continue to scale our engineering team (and the number of features and components built) but retain the speed of product development. Some user research with Spotify developers highlighted a clear problem: there was simply too much non-documented institutional knowledge needed to get things done. No one could find anything and everyone was interrupting everyone else trying to figure things out. +Rolling back the clock just a few years, [Spotify was challenged](https://engineering.atspotify.com/2021/05/a-product-story-the-lessons-of-backstage-and-spotifys-autonomous-culture/) to continue to scale our engineering team (and the number of features and components built) but retain the speed of product development. Some user research with Spotify developers highlighted a clear problem: there was simply too much non-documented institutional knowledge needed to get things done. No one could find anything and everyone was interrupting everyone else trying to figure things out. Spotify’s developers were facing three big challenges on a daily basis: diff --git a/microsite/blog/2021-06-22-spotify-backstage-is-growing.mdx b/microsite/blog/2021-06-22-spotify-backstage-is-growing.mdx index 308effaeab..8c6ee78671 100644 --- a/microsite/blog/2021-06-22-spotify-backstage-is-growing.mdx +++ b/microsite/blog/2021-06-22-spotify-backstage-is-growing.mdx @@ -7,7 +7,7 @@ authorURL: https://www.linkedin.com/in/austinlamon [![The Backstage community is growing! In just over a year, Backstage has gone from a few open source building blocks to a thriving platform used by engineering orgs with thousands of developers. But even with 30+ adopting companies and 400+ contributors, we are still in the very early stages of reaching the platform’s potential.](assets/21-06-22/spotify-backstage-header.gif)](https://backstage.spotify.com/) _[backstage.spotify.com](https://backstage.spotify.com)_ -The Backstage community is growing! In just over [a year](https://engineering.atspotify.com/2021/03/16/happy-birthday-backstage-spotifys-biggest-open-source-project-grows-up-fast/), Backstage has gone from a few open source building blocks to a thriving platform used by engineering orgs with thousands of developers. But even with 30+ [adopting companies](https://github.com/backstage/backstage/blob/master/ADOPTERS.md) and 400+ contributors, we are still in the very early stages of reaching the platform’s potential. +The Backstage community is growing! In just over [a year](https://engineering.atspotify.com/2021/03/happy-birthday-backstage-spotifys-biggest-open-source-project-grows-up-fast/), Backstage has gone from a few open source building blocks to a thriving platform used by engineering orgs with thousands of developers. But even with 30+ [adopting companies](https://github.com/backstage/backstage/blob/master/ADOPTERS.md) and 400+ contributors, we are still in the very early stages of reaching the platform’s potential. In order to grow Backstage further, Spotify is increasing the support we provide both adopters (the people integrating Backstage into their organizations) and contributors (the people building features and improving the code). The more companies that adopt Backstage, the more support the project gets, the stronger the platform becomes for everyone. diff --git a/microsite/blog/2021-06-24-announcing-backstage-search-platform.mdx b/microsite/blog/2021-06-24-announcing-backstage-search-platform.mdx index bb30a3b9ec..8ac14b14d4 100644 --- a/microsite/blog/2021-06-24-announcing-backstage-search-platform.mdx +++ b/microsite/blog/2021-06-24-announcing-backstage-search-platform.mdx @@ -1,6 +1,5 @@ --- -# prettier-ignore -title: "Announcing the Backstage Search platform: a customizable search tool built just for you" +title: 'Announcing the Backstage Search platform: a customizable search tool built just for you' author: Emma Indal, Spotify authorURL: https://www.linkedin.com/in/emma-indal --- diff --git a/microsite/blog/2022-03-17-backstage-1.0.mdx b/microsite/blog/2022-03-17-backstage-1.0.mdx index 6f9dd3ee09..3cade0d79f 100644 --- a/microsite/blog/2022-03-17-backstage-1.0.mdx +++ b/microsite/blog/2022-03-17-backstage-1.0.mdx @@ -1,6 +1,5 @@ --- -# prettier-ignore -title: "New release: Backstage 1.0" +title: 'New release: Backstage 1.0' author: Francesco Corti, Spotify authorURL: https://github.com/fcorti/ authorImageURL: https://avatars.githubusercontent.com/u/6010860?v=4 diff --git a/microsite/blog/2022-05-13-techdocs-addon-framework.mdx b/microsite/blog/2022-05-13-techdocs-addon-framework.mdx index 7f2a588834..37d746b3c2 100644 --- a/microsite/blog/2022-05-13-techdocs-addon-framework.mdx +++ b/microsite/blog/2022-05-13-techdocs-addon-framework.mdx @@ -1,6 +1,5 @@ --- -# prettier-ignore -title: "Introducing the TechDocs Addon Framework" +title: 'Introducing the TechDocs Addon Framework' author: Gary Niemen, Spotify authorURL: https://github.com/garyniemen/ authorImageURL: https://avatars.githubusercontent.com/u/65337273?v=4 diff --git a/microsite/blog/2022-07-19-releasing-backstage-search-1.0.mdx b/microsite/blog/2022-07-19-releasing-backstage-search-1.0.mdx index 1936dd4fc3..9ff3539b0d 100644 --- a/microsite/blog/2022-07-19-releasing-backstage-search-1.0.mdx +++ b/microsite/blog/2022-07-19-releasing-backstage-search-1.0.mdx @@ -1,5 +1,4 @@ --- -# prettier-ignore title: Releasing Backstage Search 1.0 author: Emma Indal, Spotify authorURL: https://www.linkedin.com/in/emma-indal diff --git a/microsite/blog/2022-08-23-backstage-security-audit.mdx b/microsite/blog/2022-08-23-backstage-security-audit.mdx index 6b9f2f6463..9c6de6eeeb 100644 --- a/microsite/blog/2022-08-23-backstage-security-audit.mdx +++ b/microsite/blog/2022-08-23-backstage-security-audit.mdx @@ -1,5 +1,4 @@ --- -# prettier-ignore title: Backstage Security Audit & Updates author: Patrik Oldsberg, Spotify authorURL: https://github.com/Rugvip diff --git a/microsite/blog/2022-09-08-fyi-plugin-analytics-api.mdx b/microsite/blog/2022-09-08-fyi-plugin-analytics-api.mdx index aff30752ec..b07dfffc06 100644 --- a/microsite/blog/2022-09-08-fyi-plugin-analytics-api.mdx +++ b/microsite/blog/2022-09-08-fyi-plugin-analytics-api.mdx @@ -1,5 +1,4 @@ --- -# prettier-ignore title: FYI 📣 The Plugin Analytics API author: Eric Peterson, Spotify authorURL: https://github.com/iamEAP diff --git a/microsite/blog/2023-07-08-stash-adopter-post.mdx b/microsite/blog/2023-07-08-stash-adopter-post.mdx index 6c1edea764..c5fe9e03b4 100644 --- a/microsite/blog/2023-07-08-stash-adopter-post.mdx +++ b/microsite/blog/2023-07-08-stash-adopter-post.mdx @@ -1,6 +1,5 @@ --- -# prettier-ignore -title: "Adopter Spotlight: How Stash simplified monitoring, ownership and true app health with microservices" +title: 'Adopter Spotlight: How Stash simplified monitoring, ownership and true app health with microservices' author: Taylor Webber, Staff Engineer, Stash --- diff --git a/microsite/blog/2023-07-28-frontside-adopter-post.mdx b/microsite/blog/2023-07-28-frontside-adopter-post.mdx index c6f3cba403..5ea73fb089 100644 --- a/microsite/blog/2023-07-28-frontside-adopter-post.mdx +++ b/microsite/blog/2023-07-28-frontside-adopter-post.mdx @@ -1,6 +1,5 @@ --- -# prettier-ignore -title: "Five common traits of successful Backstage adopters" +title: 'Five common traits of successful Backstage adopters' author: Taras Mankovski, CXO, Frontside --- diff --git a/microsite/blog/2023-08-17-expedia-proof-of-value-metrics-2.mdx b/microsite/blog/2023-08-17-expedia-proof-of-value-metrics-2.mdx index 9c6e67fcf1..f7f6ddfe77 100644 --- a/microsite/blog/2023-08-17-expedia-proof-of-value-metrics-2.mdx +++ b/microsite/blog/2023-08-17-expedia-proof-of-value-metrics-2.mdx @@ -1,6 +1,5 @@ --- -# prettier-ignore -title: "Expedia Group shares Backstage proof of value metrics 2.0" +title: 'Expedia Group shares Backstage proof of value metrics 2.0' author: Guillermo Manzo, Expedia Group authorURL: https://www.linkedin.com/in/guillermomanzo --- diff --git a/microsite/blog/2023-09-29-chicago-traiding-company-adopter-spotlight.mdx b/microsite/blog/2023-09-29-chicago-traiding-company-adopter-spotlight.mdx index 8b4f332d54..bd479a6a86 100644 --- a/microsite/blog/2023-09-29-chicago-traiding-company-adopter-spotlight.mdx +++ b/microsite/blog/2023-09-29-chicago-traiding-company-adopter-spotlight.mdx @@ -1,6 +1,5 @@ --- -# prettier-ignore -title: "Adopter Spotlight: How Chicago Trading Company saved 18 months of developer effort with Backstage" +title: 'Adopter Spotlight: How Chicago Trading Company saved 18 months of developer effort with Backstage' author: Tiffany Cox, Spotify --- diff --git a/microsite/blog/2023-11-15-backstagecon-kubecon-23.mdx b/microsite/blog/2023-11-15-backstagecon-kubecon-23.mdx index 7b7f7c0f0b..cdb2d9e582 100644 --- a/microsite/blog/2023-11-15-backstagecon-kubecon-23.mdx +++ b/microsite/blog/2023-11-15-backstagecon-kubecon-23.mdx @@ -1,6 +1,5 @@ --- -# prettier-ignore -title: "Wrap up: BackstageCon ‘23, KubeCon + Cloud Native NA" +title: 'Wrap up: BackstageCon ‘23, KubeCon + Cloud Native NA' author: Jorge Lainfiesta, Spotify authorURL: https://www.linkedin.com/in/jrlainfiesta/ authorImageURL: https://avatars.githubusercontent.com/u/4451393?v=4 diff --git a/microsite/blog/2024-03-27-backstagecon-kubecon-24.mdx b/microsite/blog/2024-03-27-backstagecon-kubecon-24.mdx index 2164a8732e..6dd8691d82 100644 --- a/microsite/blog/2024-03-27-backstagecon-kubecon-24.mdx +++ b/microsite/blog/2024-03-27-backstagecon-kubecon-24.mdx @@ -1,6 +1,5 @@ --- -# prettier-ignore -title: "Backstage in Paris: Wrapping up BackstageCon and KubeCon + CloudNativeCon Europe 2024" +title: 'Backstage in Paris: Wrapping up BackstageCon and KubeCon + CloudNativeCon Europe 2024' author: André Wanlin, Spotify authorURL: https://github.com/awanlin authorImageURL: https://avatars.githubusercontent.com/u/67169551?v=4 diff --git a/microsite/blog/2024-04-19-community-plugins.mdx b/microsite/blog/2024-04-19-community-plugins.mdx index 49d1765bce..801a2f7437 100644 --- a/microsite/blog/2024-04-19-community-plugins.mdx +++ b/microsite/blog/2024-04-19-community-plugins.mdx @@ -1,6 +1,5 @@ --- -# prettier-ignore -title: "Introducing the Backstage Community Plugins Repository" +title: 'Introducing the Backstage Community Plugins Repository' author: Ben Lambert, Spotify authorURL: https://github.com/benjdlambert authorImageURL: https://avatars.githubusercontent.com/u/3645856?v=4 diff --git a/microsite/blog/2024-06-27-backstage-engineer-journey.mdx b/microsite/blog/2024-06-27-backstage-engineer-journey.mdx index b9bfc20e4e..9180a4b6d7 100644 --- a/microsite/blog/2024-06-27-backstage-engineer-journey.mdx +++ b/microsite/blog/2024-06-27-backstage-engineer-journey.mdx @@ -1,12 +1,11 @@ --- -# prettier-ignore -title: "1,186 days with Backstage: A journey through the eyes of a developer" +title: '1,186 days with Backstage: A journey through the eyes of a developer' author: Camila Loiola, Spotify authorURL: https://github.com/camilaibs authorImageURL: https://avatars.githubusercontent.com/u/6290749?v=4 --- -**TL;DR** There is no shortage of content that discusses the benefits that Backstage brings to organizations (and if you want to read more on that, you can just click [here](https://engineering.atspotify.com/category/backstage/), [here](https://backstage.io/blog), or [here](https://backstage.spotify.com/discover/blog/)). But I want to share what it did for me, a developer here at Spotify. And not just what it did, but how I’ve used Backstage to help me achieve goals at each stage of my journey at Spotify — from my first day as a brand new employee to today, my 1,186th day as a contributor. +**TL;DR** There is no shortage of content that discusses the benefits that Backstage brings to organizations (and if you want to read more on that, you can just click [here](https://engineering.atspotify.com/?c=backstage), [here](https://backstage.io/blog), or [here](https://backstage.spotify.com/discover/blog/)). But I want to share what it did for me, a developer here at Spotify. And not just what it did, but how I’ve used Backstage to help me achieve goals at each stage of my journey at Spotify — from my first day as a brand new employee to today, my 1,186th day as a contributor. ![Backstage Engineer Journey](assets/2024-06-27/backstage-engineer-journey.svg) diff --git a/microsite/blog/2024-12-09-kubecon-slc-24.mdx b/microsite/blog/2024-12-09-kubecon-slc-24.mdx index c3fa2f70a8..c77d82d1aa 100644 --- a/microsite/blog/2024-12-09-kubecon-slc-24.mdx +++ b/microsite/blog/2024-12-09-kubecon-slc-24.mdx @@ -1,6 +1,5 @@ --- -# prettier-ignore -title: "Backstage in Salt Lake City: Highlights from BackstageCon and KubeCon + CloudNativeCon North America 2024" +title: 'Backstage in Salt Lake City: Highlights from BackstageCon and KubeCon + CloudNativeCon North America 2024' author: André Wanlin, Spotify & Camila Loiola, Spotify --- diff --git a/microsite/blog/2024-12-17-backstage-security-audit-2024.mdx b/microsite/blog/2024-12-17-backstage-security-audit-2024.mdx index f9a1afef9e..5d4294b6c6 100644 --- a/microsite/blog/2024-12-17-backstage-security-audit-2024.mdx +++ b/microsite/blog/2024-12-17-backstage-security-audit-2024.mdx @@ -1,5 +1,4 @@ --- -# prettier-ignore title: The 2024 Backstage Security Audit author: Patrik Oldsberg, Spotify authorURL: https://github.com/Rugvip diff --git a/microsite/blog/2024-12-18-backstage-wrapped-2024.mdx b/microsite/blog/2024-12-18-backstage-wrapped-2024.mdx index a136d644b5..f0e4eb8a05 100644 --- a/microsite/blog/2024-12-18-backstage-wrapped-2024.mdx +++ b/microsite/blog/2024-12-18-backstage-wrapped-2024.mdx @@ -1,5 +1,4 @@ --- -# prettier-ignore title: "Backstage Wrapped 2024: The year's Top 5 milestones and highlights" author: Patrik Oldsberg, Spotify & Ben Lambert, Spotify --- diff --git a/microsite/blog/2025-04-29-backstagecon-kubecon-25-london.mdx b/microsite/blog/2025-04-29-backstagecon-kubecon-25-london.mdx index 99da880900..3eca9563a9 100644 --- a/microsite/blog/2025-04-29-backstagecon-kubecon-25-london.mdx +++ b/microsite/blog/2025-04-29-backstagecon-kubecon-25-london.mdx @@ -1,6 +1,5 @@ --- -# prettier-ignore -title: "Backstage in London: Highlights from BackstageCon and KubeCon + CloudNativeCon Europe 2025" +title: 'Backstage in London: Highlights from BackstageCon and KubeCon + CloudNativeCon Europe 2025' author: André Wanlin, Spotify authorURL: https://github.com/awanlin authorImageURL: https://avatars.githubusercontent.com/u/67169551?v=4 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/2025-08-08-migrating-legacy-services-to-a-modern-developer-portal.mdx b/microsite/blog/2025-08-08-migrating-legacy-services-to-a-modern-developer-portal.mdx new file mode 100644 index 0000000000..268fd4933b --- /dev/null +++ b/microsite/blog/2025-08-08-migrating-legacy-services-to-a-modern-developer-portal.mdx @@ -0,0 +1,185 @@ +--- +title: 'Migrating legacy services to a modern developer portal: A technical guide to Backstage integration' +author: Damola Obaleke +authorURL: https://github.com/damolaobaleke +authorImageURL: https://avatars.githubusercontent.com/u/45045727?v=4 +--- + +**TL;DR:** As part of our efforts to streamline service management and improve developer experience, our engineering org migrated from a legacy service catalogue to the Backstage Software Catalog. In this post, I'll share what we learned from that journey --- including how to build an automated script to migrate services from an existing internal service catalogue to a modern internal developer portal (IDP) like Backstage. I'll also outline strategies to help drive adoption across your organization, along with tips for addressing both the cultural and technical roadblocks you might encounter along the way. + +![migration flowchart](assets/2025-08-08/image01.png) +{/* truncate */} + +## Why and How a Modern Developer Portal Was Rolled Out + +Historically, many organizations utilized internally built software tools for managing their service inventories. While these tools served their purpose, there's a growing need for more robust service +catalogues that require less maintenance and reduce over-engineered tooling. Frameworks like Backstage have emerged as powerful solutions. + +The decision is often made to establish a single source of truth for services and engineering teams. The vision is typically for reliability or platform teams to envision a world where every software development +team, regardless of size or domain, can efficiently manage projects, access tools and resources, and foster a collaborative culture. A modern developer portal aims to become the go-to platform for developers, infrastructure teams, and product managers to collaborate, accelerate development cycles, and deliver high-quality software. It serves as a centralized hub, consolidating all necessary information, tools, and services that developers require throughout the software development lifecycle. + +## Internal Services Landscape + +![internal services](assets/2025-08-08/image02.png) + +In large organizations, it's common to manage thousands of GitHub repositories, encompassing a wide array of assets such as front end web applications, backend web services, mobile applications, and APIs. For instance, a typical enterprise might have: + +- Hundreds or thousands of APIs +- Hundreds or thousands Services +- Dozens of Web applications +- Various libraries, infrastructure components, etc. + +Migrating these internal services from various engineering teams requires a systematic approach to retrieve the existing service definitions and transform them into a format compatible with the new +developer portal. This often involves automated scripts to ensure efficient ingestion. For example, successfully migrating hundreds of services into a new portal is a common achievement in such initiatives. + +## GitHub Requests Architecture for Migration + +**Using an Entity Provider and a Scripted Migration** + +In our Backstage setup, we took a hybrid approach: we used an automated script to generate and submit `catalog-info.yaml` files to service repositories, and relied on the GitHub Entity Provider defined in our +`app-config.yaml` to ingest them into the catalog. This gave us the best of both worlds. + +The script handled the heavy lifting of migrating metadata from for example legacy `(.appcatalog.yaml)` files, transforming them into the modern format expected by Backstage. It also empowered teams by +submitting pull requests they could review and enrich adding ownership metadata, documentation links, or system context before merging. Once merged, the GitHub Entity Provider took over, ingesting the files on a +scheduled basis and ensuring Backstage stayed up to date. This pattern reduced manual overhead, maintained decentralization, and eliminated the need for a persistent integration with the legacy system. Since the old catalog was being decommissioned, this also aligned with our goal of moving forward cleanly without syncing two sources of truth. + +![api orchestration](assets/2025-08-08/image03.png) + +Several technical steps are typically involved in retrieving existing service definition files `(e.g., appcatalog.yaml)`, an example yaml file, and then converting them into the modern developer portal\'s preferred +format `(e.g.catalog-info.yaml` for Backstage). Rather than manually creating new definition files, an automated Python script can be developed to generate and add them to the respective repositories or services. Without creating and using these automated scripts, doing these tasks manually can take several hours and could become extremely tedious. For example, in the case of Backstage, it would mean having to explicitly create the `catalog-info.yaml` hundreds of times in hundreds of repositories which can also result in hundreds of errors and manual lift. + +**Requirement One: GitHub Apps** + +A dedicated GitHub App can be utilized to extend functionality, granting the necessary permissions to submit pull requests across multiple repositories. GitHub Apps are integrations that extend GitHub's functionality and can automate tasks, interacting with and extending GitHub's features. + +A private key `(.pem)` is retrieved for the GitHub App, and a JSON Web Token (JWT) is created. These two keys are combined to generate an Authentication Token, which is an encoded JSON Web Token. + +**Requirement Two: An Access Token and Bearer Token** + +The Authentication Token (encoded JWT) is used to make a `GET` request to the GitHub API endpoint `/app/installations` at `api.github.com.` This request retrieves an access token URL. Once that access token URL is received, another request is made to this URL to obtain the Bearer token. This Bearer token is then used for major requests to GitHub API endpoints, such as `https://api.github.com/repos/{owner}/{repo}/contents/{path}.` + +**Base64 and YAML Processing** + +With the Bearer token, a request can be made to retrieve the contents of GitHub repositories containing the legacy service definition files `(e.g. .appcatalog.yaml)`. GitHub typically returns these contents in +Base64 format, a binary-to-text encoding scheme that converts binary data into an ASCII string, often used for secure data transmission over HTTP. + +Upon receiving the encoded Base64 content, it must be decoded to YAML. The decoded YAML is then compared to the desired key-value pairings for the new catalog-info.yaml file. This is where a custom Mapper comes into +play. A template for catalog-info.yaml is created following the developer portal\'s guidelines. The key-value pairs from this template are then mapped to the values received from the legacy service +definition file. For example: + +```python +results['metadata']['title'] = data['appInfo']['appName'] +# In the new format: metadata.title +# From the legacy file: appInfo.appName +``` + +After the comparisons and correct mapping for the new format, the data is dumped into the `catalog-info.yaml` file and finally encoded back to Base64 for submission to the GitHub repository. + +An example excerpt code of encoding YAML file to base64 + +```python +def encode_to_base64(file): + catalog_info_data = load_yaml(file) + print(catalog_info_data) #json + catalog_yaml = yaml.dump_all(catalog_info_data) #yaml + print('\n') + print(catalog_yaml) + + logger({'data': catalog_yaml,'method':'encode_to_base64','level': 'debug'}) + + #yaml k:v (dict) to string + dict_to_string = str(catalog_yaml) + encoded = base64.b64encode(bytes(dict_to_string, 'utf-8')) # bytes + print("\n") + #decode to convert into to explicit string and remove b prefix in encoded + enc_dec = encoded.decode('utf-8') + print(enc_dec) + return enc_dec +``` + +**Submitting Changes to the GitHub Repository** +Submitting the file back to the GitHub repository involves a few more steps. A `GET` request is made to the Git branches endpoint, `/repos/{owner}/{repo}/branches/{branch}`, to check if a new branch `(e.g. +update/developer-portal-opt-in)` already exists. + +If the branch does not exist, it is created from the main or master branch, depending on which one is present in the repository, using `refs/heads/{branch_name}\'.format(\*\*locals()).` Finally, the pull +request with the encoded Base64 YAML is submitted to the branch using the GitHub endpoint `/repos/{owner}/{repo}/pulls.` + +## Adoption and Impact + +![adoption challenges](assets/2025-08-08/image04.png) + +Initially, the adoption of a new developer portal can face challenges +for several reasons: + +- Engineering teams may be hesitant to merge unfamiliar pull requests into their repositories or services. +- Some teams or engineers might lack a robust understanding of the new portal's purpose or value. +- The new tooling might not be a strictly defined, top-down mandated requirement from leadership. +- Access requests might still be required, even for a centralized portal. +- Customer adoption can be slow, sometimes requiring individual demonstrations even after widespread presentations. +- Some individuals might understand what the portal is but not its full capabilities or limitations. + +**Gaining Traction and Growth** + +Despite initial hurdles, a new developer portal can gain immense traction and adoption after numerous pitches to various teams. A key factor in widespread adoption often includes contributions from various engineering teams beyond the initial implementation group. + +The transformation can primarily be driven by a concerted effort to understand team needs, strategically "sell" the platform's value, and address early friction points. The key to this success lies in demonstrating tangible benefits and integrating features that directly solved developer pain points. + +#### Understanding Team Needs + +During the discovery and initial implementation phases, it became clear what capabilities teams truly desired from a developer portal: + +- **Visibility into Ownership and Relationships:** Teams were highly interested in features like a **dependency graph** or **organizational chart**. They wanted clear visibility into service ownership and relationships between different services, enabling them to quickly identify whom to contact for specific issues or collaborations. +- **Service Discoverability:** Once they grasped the concept and value of having their services represented in a central catalogue, teams actively sought to have their services migrated or ingested using the scaffolder. Their motivation was clear: they wanted other engineers to easily find and understand their services within the portal. + +## Strategies for Selling the Idea + +A combined approach was crucial to effectively "sell" the developer portal idea to various teams: + +1. **Clear Roadmap:** Presenting a comprehensive roadmap that outlined the portal's current state and its future potential helped teams envision its long-term value. +2. **Engaging Presentations and RFCs:** Conducting engineering presentations to numerous teams and sharing Requests for Comments (RFCs) allowed for broader review, gathered valuable insights, and fostered a sense of community involvement. +3. **Highlighting Discoverability:** Emphasizing that their services would be easily discoverable by other teams and that the portal would serve as a single source of truth for all component information related to their services was a powerful selling point. +4. **Personalized Demos:** Conducting 1:1 Slack huddles with individual engineers to demo the platform provided a personalized experience and allowed for direct addressing of questions. +5. **Visual Aids:** Utilizing diagrams of various components and illustrating their benefits helped simplify complex concepts and showcase the portal's advantages. +6. **Addressing Codebase Concerns:** When submitting the automated pull requests with the migration script, it was crucial to explain to teams that these changes would not result in any loss of functionality, nor would they have any adverse effects or modifications to their existing codebase. + +### Addressing Early Friction + +Despite these efforts, initial friction was inevitable: + +1. **Pushback on Automated PRs:** There was initial pushback regarding the automated pull requests submitted to their repositories. +2. **Reluctance to Merge:** Engineering teams were hesitant to merge a "random" PR into their repositories, especially without a full understanding of its purpose. +3. **Lack of Understanding:** Some teams simply didn\'t have a robust understanding of what the portal was or its value, leading them to either not merge the `catalog-info.yaml` PR or, in some cases, + even delete it. + +Through persistent engagement, clear communication, and demonstrating tangible value, these initial resistances can be gradually overcome. The strategic addition of features and the active involvement of other +engineering teams, particularly those focused on reliability platforms and SRE, proved instrumental in demonstrating the platform's utility and appeal. + +## Feature Additions Driving Adoption + +![plugin ecosystem](assets/2025-08-08/image06.png) + +The addition of components like a [Tech Radar](https://www.thoughtworks.com/en-us/radar) can significantly increase engineer interest. A Tech Radar is typically implemented to help engineers and cross-functional teams understand the "paved road" technology stack, what should be used for new systems andwhat existing systems should aspire to move towards. Thoughtworks created this system and can be seen here: +[https://www.thoughtworks.com/en-us/radar](https://www.thoughtworks.com/en-us/radar) + +Some other [plugins](https://backstage.io/plugins/) that +can increase widespread adoption of a developer portal: + +1. Q/A Plugin +2. Groups or teams plugins +3. Kubernetes plugin +4. Managing External services + +A critical component that contributes to widespread adoption is the **linter plugin**. For developers migrating services to the new developer portal, ensuring the `catalog-info.yaml.` file is correct and accurate is paramount. The linter plugin, often integrated with an external API, provides an invaluable and accurate way to validate these files, making the process of creating and maintaining them much easier and less error-prone for developers. One of the most accurate ways to implement the linter plugin is making a request to [Entity validator](https://github.com/RoadieHQ/backstage-entity-validator) to make sure it is correct. + +Complementing this, a **Scaffolder** feature also plays a vital role. The Scaffolder allows developers to generate new service or component `catalog-info.yaml` files (and often entire project structures) from +pre-defined templates. This capability effectively eliminates the need for developers to manually create these files from scratch or worry about linting them, as the generated output is inherently compliant with the portal's schema. + +Ultimately, such efforts can successfully ingest hundreds of services, leading to a significant number of components residing within the new developer portal. + +## Conclusion + +The migration of legacy internal services to a modern developer portal like Backstage represents a significant leap forward in standardizing and streamlining developer experiences. By transitioning from an internally maintained "App Catalogue" to a centralized, community-driven "Developer Portal," organizations can lay the groundwork for enhanced collaboration, improved resource accessibility, and accelerated development cycles. Despite initial adoption challenges, the strategic implementation of features like the Tech Radar and the linter plugin, coupled with persistent outreach and contributions from various engineering teams, proves instrumental in demonstrating the inherent value of such a platform. The successful ingestion of hundreds of services stands as a testament to the effectiveness of automated migration scripts and the dedication of the teams involved. This endeavor not only provides a robust and less over-engineered service catalogue but also fosters a culture of shared ownership and continuous improvement within the engineering organization. + +## What's Next + +My involvement as a software engineer with open source tools and developer portal projects like Backstage, including implementation and service migration, has provided me valuable opportunities to contribute to their open-source documentation and repositories. This can be seen in contributions such as [Update app-custom-theme.md](https://github.com/backstage/backstage/pull/12863) and [Update getting-started.md](https://github.com/backstage/backstage/pull/12714), which deepen understanding of custom themes and style components. I’m now looking at future efforts focusing on contributing more to the open-source project (e.g., Model context protocol with A.I (MCP) at [RFC: Distributed Actions with MCP in Backstage](https://github.com/backstage/backstage/issues/30218)) and continuing to improve and scale this critical developer portal for engineers across the world. 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/blog/assets/2025-08-08/image01.png b/microsite/blog/assets/2025-08-08/image01.png new file mode 100644 index 0000000000..88fcc53ec0 Binary files /dev/null and b/microsite/blog/assets/2025-08-08/image01.png differ diff --git a/microsite/blog/assets/2025-08-08/image02.png b/microsite/blog/assets/2025-08-08/image02.png new file mode 100644 index 0000000000..29137f7ae0 Binary files /dev/null and b/microsite/blog/assets/2025-08-08/image02.png differ diff --git a/microsite/blog/assets/2025-08-08/image03.png b/microsite/blog/assets/2025-08-08/image03.png new file mode 100644 index 0000000000..bdbf588533 Binary files /dev/null and b/microsite/blog/assets/2025-08-08/image03.png differ diff --git a/microsite/blog/assets/2025-08-08/image04.png b/microsite/blog/assets/2025-08-08/image04.png new file mode 100644 index 0000000000..f667020419 Binary files /dev/null and b/microsite/blog/assets/2025-08-08/image04.png differ diff --git a/microsite/blog/assets/2025-08-08/image06.png b/microsite/blog/assets/2025-08-08/image06.png new file mode 100644 index 0000000000..15fb78fa56 Binary files /dev/null and b/microsite/blog/assets/2025-08-08/image06.png differ diff --git a/microsite/data/plugins/analytics-module-ga.yaml b/microsite/data/plugins/analytics-module-ga.yaml index 968b1831ab..3dcb27bcd5 100644 --- a/microsite/data/plugins/analytics-module-ga.yaml +++ b/microsite/data/plugins/analytics-module-ga.yaml @@ -3,8 +3,8 @@ title: 'Analytics Module: Google Analytics' author: Spotify authorUrl: https://github.com/spotify category: Monitoring -description: Track usage of your Backstage instance using Google Analytics. -documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-ga/README.md +description: Track usage of your Backstage instance using Google Analytics 4. +documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/analytics/plugins/analytics-module-ga4#readme iconUrl: /img/ga-icon.png -npmPackageName: '@backstage/plugin-analytics-module-ga' +npmPackageName: '@backstage/plugin-analytics-module-ga4' addedDate: '2021-10-07' diff --git a/microsite/data/plugins/buildkite.yaml b/microsite/data/plugins/buildkite.yaml index 5713b6b9e4..48754f6ae6 100644 --- a/microsite/data/plugins/buildkite.yaml +++ b/microsite/data/plugins/buildkite.yaml @@ -1,13 +1,10 @@ --- title: Buildkite -author: roadie.io -authorUrl: https://roadie.io/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=buildkite +author: Buildkite +authorUrl: https://buildkite.com category: CI/CD -description: View Buildkite CI builds for your service in Backstage. -documentation: https://roadie.io/backstage/plugins/buildkite/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=buildkite -iconUrl: https://roadie.io/images/logos/buildkite.png -npmPackageName: '@roadiehq/backstage-plugin-buildkite' -tags: - - ci - - cd -addedDate: '2021-04-20' +description: View Buildkite CI builds from within Backstage +documentation: https://github.com/buildkite/backstage-plugin +iconUrl: /img/buildkite.svg +npmPackageName: '@buildkite/backstage-plugin-buildkite' +addedDate: '2025-04-12' 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/hetzner-cloud.yaml b/microsite/data/plugins/hetzner-cloud.yaml new file mode 100644 index 0000000000..6f1fbe3f25 --- /dev/null +++ b/microsite/data/plugins/hetzner-cloud.yaml @@ -0,0 +1,10 @@ +--- +title: Hetzner Cloud +author: Gluo NV +authorUrl: https://github.com/gluobe +category: Infrastructure +description: Integrate Hetzner Cloud resources into Backstage. +documentation: https://github.com/gluobe/hetzner-backstage-plugin/blob/main/README.md +iconUrl: https://avatars.githubusercontent.com/u/18439789?s=200&v=4 +npmPackageName: '@gluo-nv/backstage-plugin-hetzner' +addedDate: '2025-06-03' diff --git a/microsite/data/plugins/parseable-dataset.yaml b/microsite/data/plugins/parseable-dataset.yaml new file mode 100644 index 0000000000..4624d3429f --- /dev/null +++ b/microsite/data/plugins/parseable-dataset.yaml @@ -0,0 +1,11 @@ +title: Parseable Dataset Plugin +author: Parseable +authorUrl: https://parseable.com +category: Monitoring +description: View and explore telemetry datasets from Parseable directly inside Backstage. This plugin enables teams to query, visualize, and troubleshoot logs and metrics from Parseable without leaving your internal developer portal. +documentation: https://github.com/parseablehq/backstage-plugin/blob/main/parseable-backstage-plugin/plugins/parseable-logstream/README.md +iconUrl: https://raw.githubusercontent.com/parseablehq/.github/main/images/logo.svg +npmPackageName: '@parseable/backstage-plugin-logstream' +tags: + - monitoring +addedDate: '2025-07-14' diff --git a/microsite/data/plugins/scalr b/microsite/data/plugins/scalr.yaml similarity index 100% rename from microsite/data/plugins/scalr rename to microsite/data/plugins/scalr.yaml diff --git a/microsite/data/plugins/signadot.yaml b/microsite/data/plugins/signadot.yaml new file mode 100644 index 0000000000..9b6b162901 --- /dev/null +++ b/microsite/data/plugins/signadot.yaml @@ -0,0 +1,10 @@ +--- +title: Signadot +author: Signadot +authorUrl: https://www.signadot.com/ +category: Kubernetes +description: View and manage lightweight ephemeral Kubernetes environments (Sandboxes) for testing microservices in pull requests and local development. +documentation: https://github.com/signadot/backstage-plugin +iconUrl: https://avatars.githubusercontent.com/u/57196635 +npmPackageName: '@signadot-lib/backstage-plugin' +addedDate: '2025-07-09' diff --git a/microsite/data/plugins/spacelift-io.yaml b/microsite/data/plugins/spacelift-io.yaml new file mode 100644 index 0000000000..fe9b48139f --- /dev/null +++ b/microsite/data/plugins/spacelift-io.yaml @@ -0,0 +1,10 @@ +--- +title: Spacelift.io +author: Spacelift.io +authorUrl: https://spacelift.io/ +category: Infrastructure +description: The Spacelift plugin allows you to manage your infrastructure directly from Backstage. Visualize your IaC stacks and trigger runs with ease. +documentation: https://docs.spacelift.io/integrations/external-integrations/backstage +iconUrl: https://avatars.githubusercontent.com/u/53318513?s=200&v=4 +npmPackageName: '@spacelift-io/backstage-integration-frontend' +addedDate: '2025-06-10' diff --git a/microsite/data/plugins/stackoverflow-teams.yaml b/microsite/data/plugins/stackoverflow-teams.yaml new file mode 100644 index 0000000000..c2fc1f5480 --- /dev/null +++ b/microsite/data/plugins/stackoverflow-teams.yaml @@ -0,0 +1,10 @@ +--- +title: Stack Overflow for Teams +author: Stack Overflow +authorUrl: https://github.com/StackExchange/ +category: Discovery +description: Provide seamless access to Stack Overflow Teams most relevant data, allowing you to display the top users, top tags, and top questions directly within Backstage. It also allows to securely create SO Teams questions from Backstage. +documentation: https://stackoverflowteams.help/en/articles/9692515-backstage-io-integration +iconUrl: /img/stack-overflow-logo.svg +npmPackageName: 'backstage-plugin-stack-overflow-teams' +addedDate: '2025-06-10' 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..d7538cd12d 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: [ { @@ -482,7 +483,7 @@ const config: Config = { copyright: `

    Made with ❤️ at Spotify

    `, }, algolia: { - apiKey: '1f0ba86672ccfc3576faa94583e5b318', + apiKey: '60d2643a9c6306463f15f8c3556e7f2e', // Owned by @Rugvip indexName: 'crawler_Backstage Docusaurus 2', appId: 'JCMFNHCHI8', searchParameters: {}, diff --git a/microsite/sidebars.ts b/microsite/sidebars.ts index dd52f9a801..eef7ef5ee9 100644 --- a/microsite/sidebars.ts +++ b/microsite/sidebars.ts @@ -228,7 +228,7 @@ export default { 'features/software-templates/migrating-from-v1beta2-to-v1beta3', 'features/software-templates/dry-run-testing', 'features/software-templates/experimental', - 'features/software-templates/template-extensions', + 'features/software-templates/templating-extensions', ], }, { @@ -330,7 +330,7 @@ export default { { type: 'category', label: 'Gitea', - items: ['integrations/gitea/locations'], + items: ['integrations/gitea/locations', 'integrations/gitea/discovery'], }, { type: 'category', @@ -462,6 +462,8 @@ export default { 'backend-system/core-services/token-manager', 'backend-system/core-services/url-reader', 'backend-system/core-services/user-info', + 'backend-system/core-services/actions-registry', + 'backend-system/core-services/actions', ], }, ], 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 0a45464805..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'; @@ -70,53 +72,60 @@ const Community = () => { link: 'https://www.cncf.io/training/certification/cba/', label: 'Learn more', }, + { + title: 'KodeKloud Certified Backstage Associate Course', + content: + 'KodeKloud offers an interactive course with hands-on labs to prepare you for the Certified Backstage Associate exam. Perfect for practical learners who want to gain real-world Backstage skills.', + link: 'https://learn.kodekloud.com/user/courses?search=backstage', + label: 'Learn more', + }, ]; const partners: { name: string; url: string; logo: string }[] = [ { 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 @@ -226,7 +235,13 @@ const Community = () => { {partners.map(({ name, url, logo }, index) => (
    - {name} +
    ))} diff --git a/microsite/src/pages/demos/index.tsx b/microsite/src/pages/demos/index.tsx index 9f4a4c28c3..7b2253b555 100644 --- a/microsite/src/pages/demos/index.tsx +++ b/microsite/src/pages/demos/index.tsx @@ -31,7 +31,7 @@ const Demos = () => {

    Watch the videos below to get an introduction to Backstage and to see how we use different plugins to customize{' '} - + our internal version of Backstage at Spotify

    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/backstage-search-platform.svg b/microsite/static/img/backstage-search-platform.svg index 0c058386ad..9ddc3fbb2e 100644 --- a/microsite/static/img/backstage-search-platform.svg +++ b/microsite/static/img/backstage-search-platform.svg @@ -3,8 +3,8 @@ 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/microsite/yarn.lock b/microsite/yarn.lock index 618b716f4d..dca57839d5 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -2961,90 +2961,90 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-darwin-arm64@npm:1.11.21" +"@swc/core-darwin-arm64@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-darwin-arm64@npm:1.11.24" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-darwin-x64@npm:1.11.21" +"@swc/core-darwin-x64@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-darwin-x64@npm:1.11.24" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.21" +"@swc/core-linux-arm-gnueabihf@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.24" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-linux-arm64-gnu@npm:1.11.21" +"@swc/core-linux-arm64-gnu@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-linux-arm64-gnu@npm:1.11.24" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-linux-arm64-musl@npm:1.11.21" +"@swc/core-linux-arm64-musl@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-linux-arm64-musl@npm:1.11.24" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-linux-x64-gnu@npm:1.11.21" +"@swc/core-linux-x64-gnu@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-linux-x64-gnu@npm:1.11.24" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-linux-x64-musl@npm:1.11.21" +"@swc/core-linux-x64-musl@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-linux-x64-musl@npm:1.11.24" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-win32-arm64-msvc@npm:1.11.21" +"@swc/core-win32-arm64-msvc@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-win32-arm64-msvc@npm:1.11.24" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-win32-ia32-msvc@npm:1.11.21" +"@swc/core-win32-ia32-msvc@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-win32-ia32-msvc@npm:1.11.24" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-win32-x64-msvc@npm:1.11.21" +"@swc/core-win32-x64-msvc@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-win32-x64-msvc@npm:1.11.24" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.3.46": - version: 1.11.21 - resolution: "@swc/core@npm:1.11.21" + version: 1.11.24 + resolution: "@swc/core@npm:1.11.24" dependencies: - "@swc/core-darwin-arm64": "npm:1.11.21" - "@swc/core-darwin-x64": "npm:1.11.21" - "@swc/core-linux-arm-gnueabihf": "npm:1.11.21" - "@swc/core-linux-arm64-gnu": "npm:1.11.21" - "@swc/core-linux-arm64-musl": "npm:1.11.21" - "@swc/core-linux-x64-gnu": "npm:1.11.21" - "@swc/core-linux-x64-musl": "npm:1.11.21" - "@swc/core-win32-arm64-msvc": "npm:1.11.21" - "@swc/core-win32-ia32-msvc": "npm:1.11.21" - "@swc/core-win32-x64-msvc": "npm:1.11.21" + "@swc/core-darwin-arm64": "npm:1.11.24" + "@swc/core-darwin-x64": "npm:1.11.24" + "@swc/core-linux-arm-gnueabihf": "npm:1.11.24" + "@swc/core-linux-arm64-gnu": "npm:1.11.24" + "@swc/core-linux-arm64-musl": "npm:1.11.24" + "@swc/core-linux-x64-gnu": "npm:1.11.24" + "@swc/core-linux-x64-musl": "npm:1.11.24" + "@swc/core-win32-arm64-msvc": "npm:1.11.24" + "@swc/core-win32-ia32-msvc": "npm:1.11.24" + "@swc/core-win32-x64-msvc": "npm:1.11.24" "@swc/counter": "npm:^0.1.3" "@swc/types": "npm:^0.1.21" peerDependencies: @@ -3073,7 +3073,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/98a0f201a0a4aa026b0b07c61c8da49d94d4ac3b059b466416c90b9b2cb4f457d836ae55ccfe90b0a89c7bdde36526ba5b7747c4e616c0d556c733e3728e9dd4 + checksum: 10/0b3e883f8a5652a7ab221a777386ccc8a65fc5b53d533bad15b703b22984eb3b449efd907b1872263f1a9990a9a50612f3c6deb619894a43f03cd974ec9bd1b7 languageName: node linkType: hard @@ -4430,12 +4430,12 @@ __metadata: linkType: hard "brace-expansion@npm:^1.1.7": - version: 1.1.11 - resolution: "brace-expansion@npm:1.1.11" + version: 1.1.12 + resolution: "brace-expansion@npm:1.1.12" dependencies: balanced-match: "npm:^1.0.0" concat-map: "npm:0.0.1" - checksum: 10/faf34a7bb0c3fcf4b59c7808bc5d2a96a40988addf2e7e09dfbb67a2251800e0d14cd2bfc1aa79174f2f5095c54ff27f46fb1289fe2d77dac755b5eb3434cc07 + checksum: 10/12cb6d6310629e3048cadb003e1aca4d8c9bb5c67c3c321bafdd7e7a50155de081f78ea3e0ed92ecc75a9015e784f301efc8132383132f4f7904ad1ac529c562 languageName: node linkType: hard @@ -5353,7 +5353,7 @@ __metadata: languageName: node linkType: hard -"create-hash@npm:^1.1.0, create-hash@npm:^1.1.2, create-hash@npm:^1.2.0": +"create-hash@npm:^1.1.0, create-hash@npm:^1.2.0": version: 1.2.0 resolution: "create-hash@npm:1.2.0" dependencies: @@ -5366,7 +5366,19 @@ __metadata: languageName: node linkType: hard -"create-hmac@npm:^1.1.4, create-hmac@npm:^1.1.7": +"create-hash@npm:~1.1.3": + version: 1.1.3 + resolution: "create-hash@npm:1.1.3" + dependencies: + cipher-base: "npm:^1.0.1" + inherits: "npm:^2.0.1" + ripemd160: "npm:^2.0.0" + sha.js: "npm:^2.4.0" + checksum: 10/b9f675719321dd3a3c3540bb46afcbdaf7182366ce93da9265318290e928be881e5edeff8c48a5ee9263c342e5e3f705fad5eb48f2e2cddc5fed1eb54077e076 + languageName: node + linkType: hard + +"create-hmac@npm:^1.1.7": version: 1.1.7 resolution: "create-hmac@npm:1.1.7" dependencies: @@ -5942,11 +5954,11 @@ __metadata: linkType: hard "docusaurus-pushfeedback@npm:^1.0.0": - version: 1.0.3 - resolution: "docusaurus-pushfeedback@npm:1.0.3" + version: 1.0.5 + resolution: "docusaurus-pushfeedback@npm:1.0.5" peerDependencies: "@docusaurus/core": 3.x - checksum: 10/ee80ae0c1fc079b2c317cab86d83ba50cce18938a6ce0ac647aad25dc68fc0ad659e55c11e93d0e7c23270c503c3cb300111dfbdd04785ef7aaf6fb123b57eae + checksum: 10/5323af7f1c7b4590744ea9099cacb49087d98c48c11e2ed957ed744d6210a3d82fa0cc42ef0f011f25b0d08c2d0040065c8a2b641c7c9160532ceb51ad41475c languageName: node linkType: hard @@ -7288,6 +7300,15 @@ __metadata: languageName: node linkType: hard +"hash-base@npm:^2.0.0": + version: 2.0.2 + resolution: "hash-base@npm:2.0.2" + dependencies: + inherits: "npm:^2.0.1" + checksum: 10/e39f3f2bb91679ed350bd2eb81035acb1e1e6e9bb86d9f1197fcfdc3cf39a2c56bf82a1870f000fae651477883b4c107fd6ac0c640a18ab06298b87c39939396 + languageName: node + linkType: hard + "hash-base@npm:^3.0.0": version: 3.1.0 resolution: "hash-base@npm:3.1.0" @@ -7749,8 +7770,8 @@ __metadata: linkType: hard "http-proxy-middleware@npm:^2.0.3": - version: 2.0.7 - resolution: "http-proxy-middleware@npm:2.0.7" + version: 2.0.9 + resolution: "http-proxy-middleware@npm:2.0.9" dependencies: "@types/http-proxy": "npm:^1.17.8" http-proxy: "npm:^1.18.1" @@ -7762,7 +7783,7 @@ __metadata: peerDependenciesMeta: "@types/express": optional: true - checksum: 10/4a51bf612b752ad945701995c1c029e9501c97e7224c0cf3f8bf6d48d172d6a8f2b57c20fec469534fdcac3aa8a6f332224a33c6b0d7f387aa2cfff9b67216fd + checksum: 10/4ece416a91d52e96f8136c5f4abfbf7ac2f39becbad21fa8b158a12d7e7d8f808287ff1ae342b903fd1f15f2249dee87fabc09e1f0e73106b83331c496d67660 languageName: node linkType: hard @@ -8327,7 +8348,7 @@ __metadata: languageName: node linkType: hard -"is-typed-array@npm:^1.1.3": +"is-typed-array@npm:^1.1.14, is-typed-array@npm:^1.1.3": version: 1.1.15 resolution: "is-typed-array@npm:1.1.15" dependencies: @@ -8366,6 +8387,13 @@ __metadata: languageName: node linkType: hard +"isarray@npm:^2.0.5": + version: 2.0.5 + resolution: "isarray@npm:2.0.5" + checksum: 10/1d8bc7911e13bb9f105b1b3e0b396c787a9e63046af0b8fe0ab1414488ab06b2b099b87a2d8a9e31d21c9a6fad773c7fc8b257c4880f2d957274479d28ca3414 + languageName: node + linkType: hard + "isarray@npm:~1.0.0": version: 1.0.0 resolution: "isarray@npm:1.0.0" @@ -11289,15 +11317,16 @@ __metadata: linkType: hard "pbkdf2@npm:^3.1.2": - version: 3.1.2 - resolution: "pbkdf2@npm:3.1.2" + version: 3.1.3 + resolution: "pbkdf2@npm:3.1.3" dependencies: - create-hash: "npm:^1.1.2" - create-hmac: "npm:^1.1.4" - ripemd160: "npm:^2.0.1" - safe-buffer: "npm:^5.0.1" - sha.js: "npm:^2.4.8" - checksum: 10/40bdf30df1c9bb1ae41ec50c11e480cf0d36484b7c7933bf55e4451d1d0e3f09589df70935c56e7fccc5702779a0d7b842d012be8c08a187b44eb24d55bb9460 + create-hash: "npm:~1.1.3" + create-hmac: "npm:^1.1.7" + ripemd160: "npm:=2.0.1" + safe-buffer: "npm:^5.2.1" + sha.js: "npm:^2.4.11" + to-buffer: "npm:^1.2.0" + checksum: 10/980cf2977aa84ec3166fde195a28464ab494131c0a5778fc8f20b8894410747e502159c19ef2b41842c728bc52ba49ffee6847e3ee61ac0d482689f85d8a1b30 languageName: node linkType: hard @@ -12876,6 +12905,16 @@ __metadata: languageName: node linkType: hard +"ripemd160@npm:=2.0.1": + version: 2.0.1 + resolution: "ripemd160@npm:2.0.1" + dependencies: + hash-base: "npm:^2.0.0" + inherits: "npm:^2.0.1" + checksum: 10/f1a20b72b3ef897a981544c72a1fe15c2bd580f6f40e3062f7839af8e81232f746aa860964686e4b81e90929ad086f14823a9864e4e4bed3367e597fe14a0968 + languageName: node + linkType: hard + "ripemd160@npm:^2.0.0, ripemd160@npm:^2.0.1": version: 2.0.2 resolution: "ripemd160@npm:2.0.2" @@ -13243,7 +13282,7 @@ __metadata: languageName: node linkType: hard -"sha.js@npm:^2.4.0, sha.js@npm:^2.4.8": +"sha.js@npm:^2.4.0, sha.js@npm:^2.4.11, sha.js@npm:^2.4.8": version: 2.4.11 resolution: "sha.js@npm:2.4.11" dependencies: @@ -14041,6 +14080,17 @@ __metadata: languageName: node linkType: hard +"to-buffer@npm:^1.2.0": + version: 1.2.1 + resolution: "to-buffer@npm:1.2.1" + dependencies: + isarray: "npm:^2.0.5" + safe-buffer: "npm:^5.2.1" + typed-array-buffer: "npm:^1.0.3" + checksum: 10/f8d03f070b8567d9c949f1b59c8d47c83ed2e59b50b5449258f931df9a1fcb751aa8bb8756a9345adc529b6b1822521157c48e1a7d01779a47185060d7bf96d4 + languageName: node + linkType: hard + "to-fast-properties@npm:^2.0.0": version: 2.0.0 resolution: "to-fast-properties@npm:2.0.0" @@ -14144,6 +14194,17 @@ __metadata: languageName: node linkType: hard +"typed-array-buffer@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-buffer@npm:1.0.3" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-typed-array: "npm:^1.1.14" + checksum: 10/3fb91f0735fb413b2bbaaca9fabe7b8fc14a3fa5a5a7546bab8a57e755be0e3788d893195ad9c2b842620592de0e68d4c077d4c2c41f04ec25b8b5bb82fa9a80 + languageName: node + linkType: hard + "typedarray-to-buffer@npm:^3.1.5": version: 3.1.5 resolution: "typedarray-to-buffer@npm:3.1.5" diff --git a/mkdocs.yml b/mkdocs.yml index d56ec49b66..eea5fc9c40 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -71,7 +71,7 @@ nav: - Builtin Actions: 'features/software-templates/builtin-actions.md' - Writing Custom Actions: 'features/software-templates/writing-custom-actions.md' - Writing Custom Step Layouts: 'features/software-templates/writing-custom-step-layouts.md' - - Template Extensions: 'features/software-templates/template-extensions.md' + - Templating Extensions: 'features/software-templates/templating-extensions.md' - Migrating from v1beta2 to v1beta3 templates: 'features/software-templates/migrating-from-v1beta2-to-v1beta3.md' - Dry Run Testing: 'features/software-templates/dry-run-testing.md' - Backstage Search: @@ -126,6 +126,9 @@ nav: - GitLab: - Locations: 'integrations/gitlab/locations.md' - Discovery: 'integrations/gitlab/discovery.md' + - Gitea: + - Locations: 'integrations/gitea/locations.md' + - Discovery: 'integrations/gitea/discovery.md' - Google GCS: - Locations: 'integrations/google-cloud-storage/locations.md' - LDAP: diff --git a/package.json b/package.json index b5d88d947b..24c7fa1057 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.39.0-next.2", + "version": "1.42.0-next.3", "backstage": { "cli": { "new": { @@ -23,7 +23,7 @@ ] }, "scripts": { - "build-storybook": "yarn ./storybook run build-storybook", + "build-storybook": "storybook build --output-dir dist-storybook", "build:all": "backstage-cli repo build --all", "build:api-docs": "LANG=en_EN yarn build:api-reports --docs --exclude 'plugins/@(adr|adr-backend|adr-common|airbrake|airbrake-backend|allure|analytics-module-ga|analytics-module-ga4|analytics-module-newrelic-browser|apache-airflow|api-docs|api-docs-module-protoc-gen-doc|apollo-explorer|app-visualizer|azure-devops|azure-devops-backend|azure-devops-common|azure-sites|azure-sites-backend|azure-sites-common|badges|badges-backend|bazaar|bazaar-backend|bitbucket-cloud-common|bitrise|catalog-graph|catalog-graphql|catalog-import|catalog-unprocessed-entities|cicd-statistics|cicd-statistics-module-gitlab|circleci|cloudbuild|code-climate|code-coverage|code-coverage-backend|codescene|config-schema|cost-insights|cost-insights-common|dynatrace|entity-feedback|entity-feedback-backend|entity-feedback-common|entity-validation|example-todo-list|example-todo-list-backend|example-todo-list-common|firehydrant|fossa|gcalendar|gcp-projects|git-release-manager|github-actions|github-deployments|github-issues|github-pull-requests-board|gitops-profiles|gocd|graphiql|graphql-backend|graphql-voyager|ilert|jenkins|jenkins-backend|jenkins-common|kafka|kafka-backend|lighthouse|lighthouse-backend|lighthouse-common|linguist|linguist-backend|linguist-common|microsoft-calendar|newrelic|newrelic-dashboard|nomad|nomad-backend|octopus-deploy|opencost|pagerduty|periskop|periskop-backend|playlist|playlist-backend|playlist-common|proxy-backend|puppetdb|rollbar|rollbar-backend|sentry|shortcuts|splunk-on-call|stack-overflow|stack-overflow-backend|stackstorm|tech-radar|tech-radar-2|todo|todo-backend|xcmetrics)'", "build:api-reports": "yarn build:api-reports:only --tsc", @@ -40,6 +40,7 @@ "postinstall": "husky || true", "lint": "backstage-cli repo lint --since origin/master", "lint:all": "backstage-cli repo lint", + "lint:circular-deps": "madge --circular .", "lint:docs": "node ./scripts/check-docs-quality", "lint:peer-deps": "backstage-repo-tools peer-deps", "lint:type-deps": "backstage-repo-tools type-deps", @@ -54,7 +55,7 @@ "start-backend": "echo \"Use 'yarn start example-backend' instead\"", "start:microsite": "cd microsite/ && yarn start", "start:next": "yarn start example-app-next example-backend", - "storybook": "yarn ./storybook run storybook", + "storybook": "storybook dev -p 6006", "sync-issue-templates": "node ./.github/ISSUE_TEMPLATE/sync.js", "techdocs-cli": "node scripts/techdocs-cli.js", "techdocs-cli:dev": "cross-env TECHDOCS_CLI_DEV_MODE=true node scripts/techdocs-cli.js", @@ -122,11 +123,19 @@ "@backstage/codemods": "workspace:*", "@backstage/create-app": "workspace:*", "@backstage/e2e-test-utils": "workspace:*", + "@backstage/eslint-plugin": "workspace:*", "@backstage/repo-tools": "workspace:*", "@changesets/cli": "^2.14.0", "@octokit/rest": "^19.0.3", "@playwright/test": "^1.32.3", "@spotify/eslint-plugin": "^15.0.0", + "@storybook/addon-essentials": "^8.6.12", + "@storybook/addon-interactions": "^8.6.12", + "@storybook/addon-links": "^8.6.12", + "@storybook/addon-storysource": "^8.6.12", + "@storybook/addon-themes": "^8.6.12", + "@storybook/react": "^8.6.12", + "@storybook/react-vite": "^8.6.12", "@techdocs/cli": "workspace:*", "@types/cacheable-request": "^8.3.6", "@types/memjs": "^1.3.3", @@ -143,18 +152,38 @@ "fs-extra": "^11.2.0", "husky": "^9.0.0", "lint-staged": "^15.0.0", + "madge": "^8.0.0", "minimist": "^1.2.5", "node-gyp": "^10.0.0", "prettier": "^2.2.1", "semver": "^7.5.3", - "shx": "^0.3.2", + "shx": "^0.4.0", "sloc": "^0.3.1", "sort-package-json": "^2.8.0", - "typedoc": "^0.27.6", - "typescript": "~5.6.0" + "storybook": "^8.6.12", + "typedoc": "^0.28.0", + "typescript": "~5.7.0", + "vite": "^7.1.2" }, "packageManager": "yarn@4.8.1", "engines": { "node": "20 || 22" + }, + "madge": { + "fileExtensions": [ + "ts", + "tsx", + "js", + "jsx" + ], + "detectiveOptions": { + "ts": { + "skipTypeImports": true + }, + "tsx": { + "skipTypeImports": true + } + }, + "tsConfig": "./tsconfig.json" } } diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index d130df1b26..131de45450 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,102 @@ # @backstage/app-defaults +## 1.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.8-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-permission-react@0.4.36 + +## 1.6.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/core-app-api@1.18.0 + - @backstage/plugin-permission-react@0.4.36 + +## 1.6.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.1 + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/core-plugin-api@1.10.9-next.0 + +## 1.6.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-permission-react@0.4.36-next.0 + +## 1.6.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-app-api@1.17.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-permission-react@0.4.35 + +## 1.6.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/core-app-api@1.17.1 + - @backstage/theme@0.6.6 + - @backstage/plugin-permission-react@0.4.35 + +## 1.6.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-app-api@1.17.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/theme@0.6.6 + - @backstage/plugin-permission-react@0.4.34 + +## 1.6.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.6 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-permission-react@0.4.34 + +## 1.6.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + ## 1.6.2-next.1 ### Patch Changes diff --git a/packages/app-defaults/catalog-info.yaml b/packages/app-defaults/catalog-info.yaml index e8450be1ec..057505a89c 100644 --- a/packages/app-defaults/catalog-info.yaml +++ b/packages/app-defaults/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index b2cc639068..3d52ba73ea 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/app-defaults", - "version": "1.6.2-next.1", + "version": "1.6.5-next.0", "description": "Provides the default wiring of a Backstage App", "backstage": { "role": "web-library" diff --git a/packages/app-next-example-plugin/CHANGELOG.md b/packages/app-next-example-plugin/CHANGELOG.md index f39080a253..0bced7b28c 100644 --- a/packages/app-next-example-plugin/CHANGELOG.md +++ b/packages/app-next-example-plugin/CHANGELOG.md @@ -1,5 +1,60 @@ # app-next-example-plugin +## 0.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + +## 0.0.25 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.0.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.0 + +## 0.0.24 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/frontend-plugin-api@0.10.3 + +## 0.0.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + +## 0.0.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + +## 0.0.23 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + ## 0.0.23-next.1 ### Patch Changes diff --git a/packages/app-next-example-plugin/catalog-info.yaml b/packages/app-next-example-plugin/catalog-info.yaml index b89b43285a..53869a83a9 100644 --- a/packages/app-next-example-plugin/catalog-info.yaml +++ b/packages/app-next-example-plugin/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-frontend-plugin - owner: maintainers + owner: framework-maintainers diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index e0fd8ffff7..51203881c5 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-next.1", + "version": "0.0.26-next.0", "description": "Backstage internal example plugin", "backstage": { "role": "frontend-plugin", diff --git a/packages/app-next-example-plugin/report.api.md b/packages/app-next-example-plugin/report.api.md index 636d735914..0635a25844 100644 --- a/packages/app-next-example-plugin/report.api.md +++ b/packages/app-next-example-plugin/report.api.md @@ -4,7 +4,7 @@ ```ts import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; @@ -26,9 +26,9 @@ const examplePlugin: FrontendPlugin< path?: string | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { @@ -37,7 +37,8 @@ const examplePlugin: FrontendPlugin< >; inputs: {}; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise; routeRef?: RouteRef; }; diff --git a/packages/app-next-example-plugin/src/plugin.tsx b/packages/app-next-example-plugin/src/plugin.tsx index 7e1e7a1bd6..40000dcd91 100644 --- a/packages/app-next-example-plugin/src/plugin.tsx +++ b/packages/app-next-example-plugin/src/plugin.tsx @@ -21,7 +21,7 @@ import { export const ExamplePage = PageBlueprint.make({ params: { - defaultPath: '/example', + path: '/example', loader: () => import('./Component').then(m => ), }, }); diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index 5cb9e8581e..3322da6a86 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,608 @@ # example-app-next +## 0.0.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-defaults@0.3.0-next.2 + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/frontend-app-api@0.12.0-next.2 + - @backstage/cli@0.34.0-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.2 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-app-visualizer@0.1.22-next.1 + - @backstage/plugin-catalog-import@0.13.4-next.2 + - @backstage/plugin-catalog-graph@0.4.22-next.2 + - @backstage/plugin-notifications@0.5.8-next.2 + - @backstage/plugin-user-settings@0.8.25-next.2 + - @backstage/plugin-search-react@1.9.3-next.1 + - @backstage/plugin-kubernetes@0.12.10-next.2 + - @backstage/plugin-scaffolder@1.34.0-next.2 + - @backstage/plugin-api-docs@0.12.10-next.2 + - @backstage/plugin-techdocs@1.14.0-next.2 + - @backstage/plugin-catalog@1.31.2-next.2 + - @backstage/plugin-search@1.4.29-next.2 + - @backstage/plugin-home@0.8.11-next.2 + - @backstage/plugin-app@0.2.0-next.1 + - @backstage/ui@0.7.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/plugin-signals@0.0.22-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/app-defaults@1.6.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/theme@0.6.8-next.0 + - @backstage/plugin-auth-react@0.1.18-next.0 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-kubernetes-cluster@0.0.28-next.1 + - @backstage/plugin-org@0.6.42-next.2 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-scaffolder-react@1.19.0-next.1 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.27-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + +## 0.0.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.0-next.1 + - @backstage/plugin-scaffolder@1.34.0-next.1 + - @backstage/ui@0.7.0-next.1 + - @backstage/plugin-home@0.8.11-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.1 + - @backstage/frontend-app-api@0.11.5-next.1 + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-import@0.13.4-next.1 + - @backstage/plugin-notifications@0.5.8-next.1 + - @backstage/plugin-kubernetes@0.12.10-next.1 + - @backstage/plugin-api-docs@0.12.10-next.1 + - @backstage/plugin-techdocs@1.14.0-next.1 + - @backstage/plugin-signals@0.0.22-next.1 + - @backstage/plugin-search@1.4.29-next.1 + - @backstage/plugin-app@0.2.0-next.0 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/cli@0.33.2-next.0 + - @backstage/frontend-defaults@0.2.5-next.1 + - @backstage/plugin-catalog-graph@0.4.22-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.28-next.1 + - @backstage/plugin-org@0.6.42-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.27-next.0 + - @backstage/plugin-user-settings@0.8.25-next.1 + - @backstage/plugin-app-visualizer@0.1.22-next.0 + - @backstage/plugin-search-react@1.9.3-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + - @backstage/app-defaults@1.6.5-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-auth-react@0.1.18-next.0 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search-common@1.2.19 + +## 0.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-org@0.6.42-next.0 + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/ui@0.7.0-next.0 + - @backstage/plugin-catalog@1.31.2-next.0 + - @backstage/frontend-defaults@0.2.5-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/plugin-api-docs@0.12.10-next.0 + - @backstage/plugin-catalog-graph@0.4.22-next.0 + - @backstage/plugin-catalog-import@0.13.4-next.0 + - @backstage/plugin-home@0.8.11-next.0 + - @backstage/plugin-kubernetes@0.12.10-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.28-next.0 + - @backstage/plugin-scaffolder@1.33.1-next.0 + - @backstage/plugin-scaffolder-react@1.18.1-next.0 + - @backstage/plugin-search@1.4.29-next.0 + - @backstage/plugin-techdocs@1.13.3-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26 + - @backstage/plugin-user-settings@0.8.25-next.0 + - @backstage/frontend-app-api@0.11.5-next.0 + - @backstage/plugin-app-visualizer@0.1.21 + - @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.0 + - @backstage/plugin-notifications@0.5.8-next.0 + - @backstage/plugin-signals@0.0.22-next.0 + - @backstage/cli@0.33.1 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-app@0.1.11 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-search-react@1.9.2 + +## 0.0.25 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.8.10 + - @backstage/plugin-techdocs@1.13.2 + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/plugin-catalog-graph@0.4.21 + - @backstage/config@1.3.3 + - @backstage/frontend-app-api@0.11.4 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-scaffolder@1.33.0 + - @backstage/ui@0.6.0 + - @backstage/cli@0.33.1 + - @backstage/plugin-scaffolder-react@1.18.0 + - @backstage/plugin-catalog@1.31.1 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-org@0.6.41 + - @backstage/theme@0.6.7 + - @backstage/plugin-catalog-import@0.13.3 + - @backstage/core-app-api@1.18.0 + - @backstage/plugin-user-settings@0.8.24 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-app@0.1.11 + - @backstage/plugin-signals@0.0.21 + - @backstage/app-defaults@1.6.4 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-defaults@0.2.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-api-docs@0.12.9 + - @backstage/plugin-app-visualizer@0.1.21 + - @backstage/plugin-auth-react@0.1.17 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-unprocessed-entities@0.2.19 + - @backstage/plugin-kubernetes@0.12.9 + - @backstage/plugin-kubernetes-cluster@0.0.27 + - @backstage/plugin-notifications@0.5.7 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search@1.4.28 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-search-react@1.9.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26 + - @backstage/plugin-techdocs-react@1.3.1 + +## 0.0.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.8.10-next.2 + - @backstage/canon@0.6.0-next.1 + - @backstage/cli@0.33.1-next.2 + - @backstage/plugin-scaffolder-react@1.18.0-next.2 + - @backstage/theme@0.6.7-next.1 + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/plugin-user-settings@0.8.24-next.2 + - @backstage/plugin-scaffolder@1.33.0-next.2 + - @backstage/plugin-signals@0.0.21-next.2 + - @backstage/app-defaults@1.6.4-next.2 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-app-api@0.11.4-next.2 + - @backstage/plugin-api-docs@0.12.9-next.1 + - @backstage/plugin-catalog@1.31.1-next.2 + - @backstage/plugin-catalog-graph@0.4.21-next.1 + - @backstage/plugin-catalog-import@0.13.3-next.1 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/plugin-notifications@0.5.7-next.1 + - @backstage/plugin-org@0.6.41-next.1 + - @backstage/plugin-search@1.4.28-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/plugin-techdocs@1.13.2-next.1 + +## 0.0.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/frontend-app-api@0.11.4-next.1 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/cli@0.33.1-next.1 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-defaults@0.2.4-next.1 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-catalog-import@0.13.3-next.1 + - @backstage/plugin-home@0.8.10-next.1 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-techdocs@1.13.2-next.1 + - @backstage/plugin-techdocs-react@1.3.1-next.1 + - @backstage/plugin-catalog@1.31.1-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-org@0.6.41-next.1 + - @backstage/plugin-scaffolder@1.32.1-next.1 + - @backstage/plugin-scaffolder-react@1.17.1-next.1 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/plugin-api-docs@0.12.9-next.1 + - @backstage/plugin-catalog-graph@0.4.21-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.19-next.1 + - @backstage/plugin-kubernetes@0.12.9-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.27-next.1 + - @backstage/plugin-user-settings@0.8.24-next.1 + - @backstage/app-defaults@1.6.4-next.1 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/plugin-notifications@0.5.7-next.1 + - @backstage/plugin-search@1.4.28-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/plugin-signals@0.0.21-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + - @backstage/plugin-app@0.1.11-next.1 + - @backstage/plugin-app-visualizer@0.1.21-next.1 + - @backstage/plugin-auth-react@0.1.17-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26-next.1 + +## 0.0.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.2-next.0 + - @backstage/plugin-catalog-graph@0.4.21-next.0 + - @backstage/canon@0.6.0-next.0 + - @backstage/plugin-scaffolder@1.32.1-next.0 + - @backstage/plugin-catalog-import@0.13.2-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/theme@0.6.7-next.0 + - @backstage/cli@0.33.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26-next.0 + - @backstage/plugin-app@0.1.11-next.0 + - @backstage/plugin-catalog@1.31.1-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/app-defaults@1.6.4-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/plugin-home@0.8.10-next.0 + - @backstage/plugin-notifications@0.5.7-next.0 + - @backstage/plugin-scaffolder-react@1.17.1-next.0 + - @backstage/plugin-search-react@1.9.2-next.0 + - @backstage/plugin-signals@0.0.21-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.0 + - @backstage/plugin-user-settings@0.8.24-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-app-api@0.11.4-next.0 + - @backstage/frontend-defaults@0.2.4-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-api-docs@0.12.9-next.0 + - @backstage/plugin-app-visualizer@0.1.21-next.0 + - @backstage/plugin-auth-react@0.1.17-next.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-unprocessed-entities@0.2.19-next.0 + - @backstage/plugin-kubernetes@0.12.9-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.27-next.0 + - @backstage/plugin-org@0.6.41-next.0 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-search@1.4.28-next.0 + - @backstage/plugin-search-common@1.2.18 + +## 0.0.24 + +### Patch Changes + +- Updated dependencies + - @backstage/canon@0.5.0 + - @backstage/core-components@0.17.3 + - @backstage/cli@0.33.0 + - @backstage/plugin-scaffolder-react@1.17.0 + - @backstage/plugin-scaffolder@1.32.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18 + - @backstage/plugin-app-visualizer@0.1.20 + - @backstage/plugin-catalog-import@0.13.1 + - @backstage/plugin-catalog-graph@0.4.20 + - @backstage/plugin-notifications@0.5.6 + - @backstage/plugin-user-settings@0.8.23 + - @backstage/plugin-kubernetes@0.12.8 + - @backstage/plugin-api-docs@0.12.8 + - @backstage/plugin-techdocs@1.13.0 + - @backstage/plugin-catalog@1.31.0 + - @backstage/plugin-signals@0.0.20 + - @backstage/plugin-search@1.4.27 + - @backstage/plugin-home@0.8.9 + - @backstage/plugin-app@0.1.10 + - @backstage/plugin-org@0.6.40 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/frontend-app-api@0.11.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/plugin-techdocs-react@1.3.0 + - @backstage/frontend-defaults@0.2.3 + - @backstage/plugin-search-react@1.9.1 + - @backstage/app-defaults@1.6.3 + - @backstage/integration-react@1.2.8 + - @backstage/plugin-auth-react@0.1.16 + - @backstage/plugin-kubernetes-cluster@0.0.26 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/core-compat-api@0.4.3 + - @backstage/theme@0.6.6 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-search-common@1.2.18 + +## 0.0.24-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.33.0-next.2 + - @backstage/app-defaults@1.6.3-next.0 + - @backstage/canon@0.5.0-next.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.2 + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-app-api@0.11.3-next.1 + - @backstage/frontend-defaults@0.2.3-next.1 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/plugin-api-docs@0.12.8-next.2 + - @backstage/plugin-app@0.1.10-next.1 + - @backstage/plugin-app-visualizer@0.1.20-next.1 + - @backstage/plugin-auth-react@0.1.16-next.0 + - @backstage/plugin-catalog@1.31.0-next.2 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-graph@0.4.20-next.2 + - @backstage/plugin-catalog-import@0.13.1-next.2 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.2 + - @backstage/plugin-home@0.8.9-next.2 + - @backstage/plugin-kubernetes@0.12.8-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.2 + - @backstage/plugin-notifications@0.5.6-next.2 + - @backstage/plugin-org@0.6.40-next.2 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-scaffolder@1.32.0-next.2 + - @backstage/plugin-scaffolder-react@1.16.1-next.2 + - @backstage/plugin-search@1.4.27-next.2 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/plugin-signals@0.0.20-next.1 + - @backstage/plugin-techdocs@1.13.0-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.2 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/plugin-user-settings@0.8.23-next.2 + +## 0.0.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/cli@0.33.0-next.1 + - @backstage/canon@0.5.0-next.2 + - @backstage/plugin-notifications@0.5.6-next.2 + - @backstage/plugin-scaffolder@1.32.0-next.2 + - @backstage/plugin-catalog@1.31.0-next.2 + - @backstage/plugin-techdocs@1.13.0-next.2 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-catalog-import@0.13.1-next.2 + - @backstage/plugin-home@0.8.9-next.2 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/app-defaults@1.6.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-api-docs@0.12.8-next.2 + - @backstage/plugin-app@0.1.10-next.1 + - @backstage/plugin-app-visualizer@0.1.20-next.1 + - @backstage/plugin-auth-react@0.1.16-next.0 + - @backstage/plugin-catalog-graph@0.4.20-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.2 + - @backstage/plugin-kubernetes@0.12.8-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.2 + - @backstage/plugin-org@0.6.40-next.2 + - @backstage/plugin-scaffolder-react@1.16.1-next.2 + - @backstage/plugin-search@1.4.27-next.2 + - @backstage/plugin-signals@0.0.20-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.2 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/plugin-user-settings@0.8.23-next.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.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-app-api@0.11.3-next.1 + - @backstage/frontend-defaults@0.2.3-next.1 + - @backstage/theme@0.6.6 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-search-common@1.2.18 + +## 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 + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/core-app-api@1.17.0 + - @backstage/canon@0.4.0 + - @backstage/cli@0.32.1 + - @backstage/plugin-user-settings@0.8.22 + - @backstage/core-components@0.17.2 + - @backstage/plugin-home@0.8.8 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-scaffolder@1.31.0 + - @backstage/frontend-app-api@0.11.2 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17 + - @backstage/plugin-app-visualizer@0.1.19 + - @backstage/plugin-catalog-import@0.13.0 + - @backstage/plugin-catalog-graph@0.4.19 + - @backstage/plugin-notifications@0.5.5 + - @backstage/plugin-kubernetes@0.12.7 + - @backstage/plugin-api-docs@0.12.7 + - @backstage/plugin-techdocs@1.12.6 + - @backstage/plugin-catalog@1.30.0 + - @backstage/plugin-signals@0.0.19 + - @backstage/plugin-search@1.4.26 + - @backstage/plugin-app@0.1.9 + - @backstage/plugin-org@0.6.39 + - @backstage/plugin-search-react@1.9.0 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/plugin-scaffolder-react@1.16.0 + - @backstage/plugin-kubernetes-cluster@0.0.25 + - @backstage/plugin-techdocs-react@1.2.17 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-defaults@0.2.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24 + - @backstage/app-defaults@1.6.2 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-search-common@1.2.18 + +## 0.0.23-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/canon@0.4.0-next.3 + - @backstage/cli@0.32.1-next.3 + - @backstage/plugin-home@0.8.8-next.3 + - @backstage/plugin-catalog@1.30.0-next.3 + - @backstage/frontend-app-api@0.11.2-next.3 + - @backstage/plugin-search-react@1.9.0-next.2 + - @backstage/plugin-scaffolder@1.31.0-next.3 + - @backstage/plugin-search@1.4.26-next.3 + - @backstage/app-defaults@1.6.2-next.2 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-api-docs@0.12.7-next.3 + - @backstage/plugin-catalog-graph@0.4.19-next.3 + - @backstage/plugin-catalog-import@0.13.0-next.3 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/plugin-notifications@0.5.5-next.3 + - @backstage/plugin-org@0.6.39-next.3 + - @backstage/plugin-scaffolder-react@1.16.0-next.3 + - @backstage/plugin-signals@0.0.19-next.1 + - @backstage/plugin-techdocs@1.12.6-next.3 + - @backstage/plugin-user-settings@0.8.22-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/frontend-defaults@0.2.2-next.3 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-app@0.1.9-next.3 + - @backstage/plugin-app-visualizer@0.1.19-next.1 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.3 + - @backstage/plugin-kubernetes@0.12.7-next.3 + - @backstage/plugin-kubernetes-cluster@0.0.25-next.3 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.3 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + ## 0.0.23-next.2 ### Patch Changes diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml index 9d2bcf198a..f83e4d4ba0 100644 --- a/packages/app-next/app-config.yaml +++ b/packages/app-next/app-config.yaml @@ -1,6 +1,5 @@ app: - experimental: - packages: 'all' # ✨ + packages: 'all' # ✨ routes: bindings: @@ -8,10 +7,31 @@ 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 + - nav-item:search: false + - nav-item:user-settings: false + - nav-item:catalog + - nav-item:api-docs + - nav-item:scaffolder + - nav-item:app-visualizer + # Pages - page:catalog/entity: config: @@ -56,7 +76,10 @@ app: - entity-card:org/group-profile - entity-card:org/members-list - entity-card:org/ownership - - entity-card:org/user-profile + - entity-card:org/user-profile: + config: + maxRelations: 5 + hideIcons: true # - entity-card:azure-devops/readme # Entity page contents diff --git a/packages/app-next/catalog-info.yaml b/packages/app-next/catalog-info.yaml index 85e3f0d70b..14e51ecd04 100644 --- a/packages/app-next/catalog-info.yaml +++ b/packages/app-next/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-frontend - owner: maintainers + owner: framework-maintainers diff --git a/packages/app-next/knip-report.md b/packages/app-next/knip-report.md index c30e426318..de7de83ffc 100644 --- a/packages/app-next/knip-report.md +++ b/packages/app-next/knip-report.md @@ -1,6 +1,6 @@ # Knip report -## Unused dependencies (30) +## Unused dependencies (26) | Name | Location | Severity | | :----------------------------------------------- | :----------- | :------- | @@ -12,19 +12,15 @@ | @backstage/plugin-catalog-common | package.json | error | | @backstage/plugin-techdocs-react | package.json | error | | @backstage/plugin-catalog-graph | package.json | error | -| @backstage/plugin-notifications | package.json | error | | @backstage/plugin-search-common | package.json | error | | @backstage/plugin-search-react | package.json | error | | @backstage/integration-react | package.json | error | | @backstage/plugin-auth-react | package.json | error | | @backstage/plugin-scaffolder | package.json | error | -| @backstage/frontend-app-api | package.json | error | | @backstage/core-plugin-api | package.json | error | | @backstage/plugin-api-docs | package.json | error | | @backstage/plugin-catalog | package.json | error | | @backstage/plugin-signals | package.json | error | -| @backstage/catalog-model | package.json | error | -| @backstage/plugin-search | package.json | error | | @backstage/app-defaults | package.json | error | | @backstage/plugin-app | package.json | error | | @backstage/plugin-org | package.json | error | diff --git a/packages/app-next/package.json b/packages/app-next/package.json index 0f56c433dd..0fee34f1f5 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-next.2", + "version": "0.0.26-next.2", "backstage": { "role": "frontend" }, @@ -35,7 +35,6 @@ }, "dependencies": { "@backstage/app-defaults": "workspace:^", - "@backstage/canon": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/config": "workspace:^", @@ -74,6 +73,7 @@ "@backstage/plugin-techdocs-react": "workspace:^", "@backstage/plugin-user-settings": "workspace:^", "@backstage/theme": "workspace:^", + "@backstage/ui": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index 0ecb46e06a..ac15c895df 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -43,6 +43,8 @@ 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'; +import { appModuleNav } from './modules/appModuleNav'; /* @@ -83,10 +85,10 @@ const convertedTechdocsPlugin = convertLegacyPlugin(techdocsPlugin, { // TODO: We likely also need a way to convert an entire tree similar to collectLegacyRoutes convertLegacyPageExtension(TechDocsIndexPage, { name: 'index', - defaultPath: '/docs', + path: '/docs', }), convertLegacyPageExtension(TechDocsReaderPage, { - defaultPath: '/docs/:namespace/:kind/:name/*', + path: '/docs/:namespace/:kind/:name/*', }), convertLegacyEntityContentExtension(EntityTechdocsContent), ], @@ -129,9 +131,13 @@ const app = createApp({ appVisualizerPlugin, kubernetesPlugin, notFoundErrorPageModule, + appModuleNav, customHomePageModule, ...collectedLegacyPlugins, ], + advanced: { + pluginInfoResolver, + }, /* Handled through config instead */ // bindRoutes({ bind }) { // bind(pagesPlugin.externalRoutes, { pageX: pagesPlugin.routes.pageX }); diff --git a/packages/app-next/src/examples/notFoundErrorPageExtension.tsx b/packages/app-next/src/examples/notFoundErrorPageExtension.tsx index a636b2b220..7f690f90cb 100644 --- a/packages/app-next/src/examples/notFoundErrorPageExtension.tsx +++ b/packages/app-next/src/examples/notFoundErrorPageExtension.tsx @@ -15,14 +15,14 @@ */ import { - createComponentExtension, - coreComponentRefs, + SwappableComponentBlueprint, + NotFoundErrorPage, } from '@backstage/frontend-plugin-api'; import Box from '@material-ui/core/Box'; import Typography from '@material-ui/core/Typography'; import { Button } from '@backstage/core-components'; -export function CustomNotFoundErrorPage() { +function CustomNotFoundErrorPage() { return ( CustomNotFoundErrorPage }, + params: define => + define({ + component: NotFoundErrorPage, + loader: () => CustomNotFoundErrorPage, + }), }); diff --git a/packages/app-next/src/examples/pagesPlugin.tsx b/packages/app-next/src/examples/pagesPlugin.tsx index ddd080c28b..35426b589b 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,10 +39,26 @@ export const pageXRouteRef = createRouteRef(); // path: '/page2', // }); +function PluginInfo() { + const node = useAppNode(); + const [info, setInfo] = useState(undefined); + + useEffect(() => { + node?.spec.plugin?.info().then(setInfo); + }, [node]); + + return ( +
    +

    Plugin Info

    +
    {JSON.stringify(info, null, 2)}
    +
    + ); +} + const IndexPage = PageBlueprint.make({ name: 'index', params: { - defaultPath: '/', + path: '/', routeRef: indexRouteRef, loader: async () => { const Component = () => { @@ -64,6 +83,7 @@ const IndexPage = PageBlueprint.make({
    Settings
    +
    ); }; @@ -75,7 +95,7 @@ const IndexPage = PageBlueprint.make({ const Page1 = PageBlueprint.make({ name: 'page1', params: { - defaultPath: '/page1', + path: '/page1', routeRef: page1RouteRef, loader: async () => { const Component = () => { @@ -111,7 +131,7 @@ const Page1 = PageBlueprint.make({ const ExternalPage = PageBlueprint.make({ name: 'pageX', params: { - defaultPath: '/pageX', + path: '/pageX', routeRef: pageXRouteRef, loader: async () => { const Component = () => { @@ -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/index-public-experimental.tsx b/packages/app-next/src/index-public-experimental.tsx index 24c4a9d66a..ce558ed4f0 100644 --- a/packages/app-next/src/index-public-experimental.tsx +++ b/packages/app-next/src/index-public-experimental.tsx @@ -15,9 +15,13 @@ */ import ReactDOM from 'react-dom/client'; -import { createPublicSignInApp } from '@backstage/frontend-defaults'; -import '@backstage/canon/css/styles.css'; +import { createApp } from '@backstage/frontend-defaults'; +import { appModulePublicSignIn } from '@backstage/plugin-app/alpha'; -const app = createPublicSignInApp(); +import '@backstage/ui/css/styles.css'; + +const app = createApp({ + features: [appModulePublicSignIn], +}); ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot()); diff --git a/packages/app-next/src/index.tsx b/packages/app-next/src/index.tsx index 82b9595c84..fd86261385 100644 --- a/packages/app-next/src/index.tsx +++ b/packages/app-next/src/index.tsx @@ -17,6 +17,6 @@ import '@backstage/cli/asset-types'; import ReactDOM from 'react-dom/client'; import app from './App'; -import '@backstage/canon/css/styles.css'; +import '@backstage/ui/css/styles.css'; ReactDOM.createRoot(document.getElementById('root')!).render(app); diff --git a/packages/app-next/src/modules/appModuleNav.tsx b/packages/app-next/src/modules/appModuleNav.tsx new file mode 100644 index 0000000000..aa7d92bd1b --- /dev/null +++ b/packages/app-next/src/modules/appModuleNav.tsx @@ -0,0 +1,142 @@ +/* + * 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 { compatWrapper } from '@backstage/core-compat-api'; +import { + Link, + Sidebar, + sidebarConfig, + SidebarDivider, + SidebarGroup, + SidebarItem, + SidebarScrollWrapper, + SidebarSpace, + useSidebarOpenState, +} from '@backstage/core-components'; +import SearchIcon from '@material-ui/icons/Search'; +import MenuIcon from '@material-ui/icons/Menu'; +import BuildIcon from '@material-ui/icons/Build'; +import { + createFrontendModule, + NavContentBlueprint, +} from '@backstage/frontend-plugin-api'; +import { SidebarSearchModal } from '@backstage/plugin-search'; +import { NotificationsSidebarItem } from '@backstage/plugin-notifications'; +import { + Settings, + UserSettingsSignInAvatar, +} from '@backstage/plugin-user-settings'; +import { makeStyles } from '@material-ui/core/styles'; + +const useSidebarLogoStyles = makeStyles({ + root: { + width: sidebarConfig.drawerWidthClosed, + height: 3 * sidebarConfig.logoHeight, + display: 'flex', + flexFlow: 'row nowrap', + alignItems: 'center', + marginBottom: -14, + }, + link: { + width: sidebarConfig.drawerWidthClosed, + marginLeft: 24, + }, +}); + +const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); + const { isOpen } = useSidebarOpenState(); + + return ( +
    + + {isOpen ? ( + + + + ) : ( + + + + )} + +
    + ); +}; + +export const appModuleNav = createFrontendModule({ + pluginId: 'app', + extensions: [ + NavContentBlueprint.make({ + params: { + component: ({ items }) => { + return compatWrapper( + + + } to="/search"> + + + + }> + + {items.map((item, index) => ( + + ))} + + + + + + } + to="/settings" + > + + + + + , + ); + }, + }, + }), + ], +}); 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 ff42b42cc8..61a287153b 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,555 @@ # example-app +## 0.2.112-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.12.0-next.2 + - @backstage/cli@0.34.0-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.2 + - @backstage/plugin-catalog-import@0.13.4-next.2 + - @backstage/plugin-catalog-graph@0.4.22-next.2 + - @backstage/plugin-notifications@0.5.8-next.2 + - @backstage/plugin-user-settings@0.8.25-next.2 + - @backstage/plugin-search-react@1.9.3-next.1 + - @backstage/plugin-kubernetes@0.12.10-next.2 + - @backstage/plugin-scaffolder@1.34.0-next.2 + - @backstage/plugin-api-docs@0.12.10-next.2 + - @backstage/plugin-devtools@0.1.30-next.2 + - @backstage/plugin-techdocs@1.14.0-next.2 + - @backstage/plugin-catalog@1.31.2-next.2 + - @backstage/plugin-search@1.4.29-next.2 + - @backstage/plugin-home@0.8.11-next.2 + - @backstage/ui@0.7.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/plugin-signals@0.0.22-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/app-defaults@1.6.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/theme@0.6.8-next.0 + - @backstage/plugin-auth-react@0.1.18-next.0 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-kubernetes-cluster@0.0.28-next.1 + - @backstage/plugin-org@0.6.42-next.2 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-scaffolder-react@1.19.0-next.1 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.27-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + +## 0.2.112-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.0-next.1 + - @backstage/plugin-scaffolder@1.34.0-next.1 + - @backstage/ui@0.7.0-next.1 + - @backstage/plugin-home@0.8.11-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.1 + - @backstage/frontend-app-api@0.11.5-next.1 + - @backstage/plugin-catalog-import@0.13.4-next.1 + - @backstage/plugin-notifications@0.5.8-next.1 + - @backstage/plugin-kubernetes@0.12.10-next.1 + - @backstage/plugin-api-docs@0.12.10-next.1 + - @backstage/plugin-devtools@0.1.30-next.1 + - @backstage/plugin-techdocs@1.14.0-next.1 + - @backstage/plugin-signals@0.0.22-next.1 + - @backstage/plugin-search@1.4.29-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/theme@0.6.8-next.0 + - @backstage/cli@0.33.2-next.0 + - @backstage/plugin-catalog-graph@0.4.22-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.28-next.1 + - @backstage/plugin-org@0.6.42-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.27-next.0 + - @backstage/plugin-user-settings@0.8.25-next.1 + - @backstage/plugin-search-react@1.9.3-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + - @backstage/app-defaults@1.6.5-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-auth-react@0.1.18-next.0 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search-common@1.2.19 + +## 0.2.112-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-org@0.6.42-next.0 + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/ui@0.7.0-next.0 + - @backstage/plugin-catalog@1.31.2-next.0 + - @backstage/plugin-api-docs@0.12.10-next.0 + - @backstage/plugin-catalog-graph@0.4.22-next.0 + - @backstage/plugin-catalog-import@0.13.4-next.0 + - @backstage/plugin-home@0.8.11-next.0 + - @backstage/plugin-kubernetes@0.12.10-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.28-next.0 + - @backstage/plugin-scaffolder@1.33.1-next.0 + - @backstage/plugin-scaffolder-react@1.18.1-next.0 + - @backstage/plugin-search@1.4.29-next.0 + - @backstage/plugin-techdocs@1.13.3-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26 + - @backstage/plugin-user-settings@0.8.25-next.0 + - @backstage/frontend-app-api@0.11.5-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.20-next.0 + - @backstage/plugin-devtools@0.1.30-next.0 + - @backstage/plugin-notifications@0.5.8-next.0 + - @backstage/plugin-signals@0.0.22-next.0 + - @backstage/cli@0.33.1 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-search-react@1.9.2 + +## 0.2.111 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.8.10 + - @backstage/plugin-techdocs@1.13.2 + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/plugin-catalog-graph@0.4.21 + - @backstage/config@1.3.3 + - @backstage/frontend-app-api@0.11.4 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-scaffolder@1.33.0 + - @backstage/ui@0.6.0 + - @backstage/cli@0.33.1 + - @backstage/plugin-scaffolder-react@1.18.0 + - @backstage/plugin-catalog@1.31.1 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-org@0.6.41 + - @backstage/theme@0.6.7 + - @backstage/plugin-catalog-import@0.13.3 + - @backstage/core-app-api@1.18.0 + - @backstage/plugin-user-settings@0.8.24 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-signals@0.0.21 + - @backstage/app-defaults@1.6.4 + - @backstage/plugin-api-docs@0.12.9 + - @backstage/plugin-auth-react@0.1.17 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-unprocessed-entities@0.2.19 + - @backstage/plugin-devtools@0.1.29 + - @backstage/plugin-kubernetes@0.12.9 + - @backstage/plugin-kubernetes-cluster@0.0.27 + - @backstage/plugin-notifications@0.5.7 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search@1.4.28 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-search-react@1.9.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26 + - @backstage/plugin-techdocs-react@1.3.1 + +## 0.2.111-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.8.10-next.2 + - @backstage/canon@0.6.0-next.1 + - @backstage/cli@0.33.1-next.2 + - @backstage/plugin-scaffolder-react@1.18.0-next.2 + - @backstage/theme@0.6.7-next.1 + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/plugin-user-settings@0.8.24-next.2 + - @backstage/plugin-scaffolder@1.33.0-next.2 + - @backstage/plugin-signals@0.0.21-next.2 + - @backstage/app-defaults@1.6.4-next.2 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-app-api@0.11.4-next.2 + - @backstage/plugin-api-docs@0.12.9-next.1 + - @backstage/plugin-catalog@1.31.1-next.2 + - @backstage/plugin-catalog-graph@0.4.21-next.1 + - @backstage/plugin-catalog-import@0.13.3-next.1 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/plugin-notifications@0.5.7-next.1 + - @backstage/plugin-org@0.6.41-next.1 + - @backstage/plugin-search@1.4.28-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/plugin-techdocs@1.13.2-next.1 + +## 0.2.111-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/frontend-app-api@0.11.4-next.1 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/cli@0.33.1-next.1 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-catalog-import@0.13.3-next.1 + - @backstage/plugin-home@0.8.10-next.1 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-techdocs@1.13.2-next.1 + - @backstage/plugin-techdocs-react@1.3.1-next.1 + - @backstage/plugin-catalog@1.31.1-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-org@0.6.41-next.1 + - @backstage/plugin-scaffolder@1.32.1-next.1 + - @backstage/plugin-scaffolder-react@1.17.1-next.1 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/plugin-api-docs@0.12.9-next.1 + - @backstage/plugin-catalog-graph@0.4.21-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.19-next.1 + - @backstage/plugin-kubernetes@0.12.9-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.27-next.1 + - @backstage/plugin-user-settings@0.8.24-next.1 + - @backstage/app-defaults@1.6.4-next.1 + - @backstage/plugin-notifications@0.5.7-next.1 + - @backstage/plugin-search@1.4.28-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/plugin-signals@0.0.21-next.1 + - @backstage/plugin-auth-react@0.1.17-next.1 + - @backstage/plugin-devtools@0.1.29-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26-next.1 + +## 0.2.111-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.2-next.0 + - @backstage/plugin-catalog-graph@0.4.21-next.0 + - @backstage/canon@0.6.0-next.0 + - @backstage/plugin-scaffolder@1.32.1-next.0 + - @backstage/plugin-catalog-import@0.13.2-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/theme@0.6.7-next.0 + - @backstage/cli@0.33.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.26-next.0 + - @backstage/plugin-catalog@1.31.1-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/app-defaults@1.6.4-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/plugin-home@0.8.10-next.0 + - @backstage/plugin-notifications@0.5.7-next.0 + - @backstage/plugin-scaffolder-react@1.17.1-next.0 + - @backstage/plugin-search-react@1.9.2-next.0 + - @backstage/plugin-signals@0.0.21-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.0 + - @backstage/plugin-user-settings@0.8.24-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-app-api@0.11.4-next.0 + - @backstage/plugin-api-docs@0.12.9-next.0 + - @backstage/plugin-auth-react@0.1.17-next.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-unprocessed-entities@0.2.19-next.0 + - @backstage/plugin-devtools@0.1.29-next.0 + - @backstage/plugin-kubernetes@0.12.9-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.27-next.0 + - @backstage/plugin-org@0.6.41-next.0 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-search@1.4.28-next.0 + - @backstage/plugin-search-common@1.2.18 + +## 0.2.110 + +### Patch Changes + +- Updated dependencies + - @backstage/canon@0.5.0 + - @backstage/core-components@0.17.3 + - @backstage/cli@0.33.0 + - @backstage/plugin-scaffolder-react@1.17.0 + - @backstage/plugin-scaffolder@1.32.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18 + - @backstage/plugin-catalog-import@0.13.1 + - @backstage/plugin-catalog-graph@0.4.20 + - @backstage/plugin-notifications@0.5.6 + - @backstage/plugin-user-settings@0.8.23 + - @backstage/plugin-kubernetes@0.12.8 + - @backstage/plugin-api-docs@0.12.8 + - @backstage/plugin-devtools@0.1.28 + - @backstage/plugin-techdocs@1.13.0 + - @backstage/plugin-catalog@1.31.0 + - @backstage/plugin-signals@0.0.20 + - @backstage/plugin-search@1.4.27 + - @backstage/plugin-home@0.8.9 + - @backstage/plugin-org@0.6.40 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-app-api@0.11.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/plugin-techdocs-react@1.3.0 + - @backstage/plugin-search-react@1.9.1 + - @backstage/app-defaults@1.6.3 + - @backstage/integration-react@1.2.8 + - @backstage/plugin-auth-react@0.1.16 + - @backstage/plugin-kubernetes-cluster@0.0.26 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/theme@0.6.6 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-search-common@1.2.18 + +## 0.2.110-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.33.0-next.2 + - @backstage/app-defaults@1.6.3-next.0 + - @backstage/canon@0.5.0-next.2 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-app-api@0.11.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/plugin-api-docs@0.12.8-next.2 + - @backstage/plugin-auth-react@0.1.16-next.0 + - @backstage/plugin-catalog@1.31.0-next.2 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-graph@0.4.20-next.2 + - @backstage/plugin-catalog-import@0.13.1-next.2 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.2 + - @backstage/plugin-devtools@0.1.28-next.2 + - @backstage/plugin-home@0.8.9-next.2 + - @backstage/plugin-kubernetes@0.12.8-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.2 + - @backstage/plugin-notifications@0.5.6-next.2 + - @backstage/plugin-org@0.6.40-next.2 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-scaffolder@1.32.0-next.2 + - @backstage/plugin-scaffolder-react@1.16.1-next.2 + - @backstage/plugin-search@1.4.27-next.2 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/plugin-signals@0.0.20-next.1 + - @backstage/plugin-techdocs@1.13.0-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.2 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/plugin-user-settings@0.8.23-next.2 + +## 0.2.110-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/cli@0.33.0-next.1 + - @backstage/canon@0.5.0-next.2 + - @backstage/plugin-notifications@0.5.6-next.2 + - @backstage/plugin-scaffolder@1.32.0-next.2 + - @backstage/plugin-catalog@1.31.0-next.2 + - @backstage/plugin-techdocs@1.13.0-next.2 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-catalog-import@0.13.1-next.2 + - @backstage/plugin-home@0.8.9-next.2 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/app-defaults@1.6.3-next.0 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-api-docs@0.12.8-next.2 + - @backstage/plugin-auth-react@0.1.16-next.0 + - @backstage/plugin-catalog-graph@0.4.20-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.2 + - @backstage/plugin-devtools@0.1.28-next.2 + - @backstage/plugin-kubernetes@0.12.8-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.2 + - @backstage/plugin-org@0.6.40-next.2 + - @backstage/plugin-scaffolder-react@1.16.1-next.2 + - @backstage/plugin-search@1.4.27-next.2 + - @backstage/plugin-signals@0.0.20-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.2 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/plugin-user-settings@0.8.23-next.2 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-app-api@0.11.3-next.1 + - @backstage/theme@0.6.6 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-search-common@1.2.18 + +## 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 + +- Updated dependencies + - @backstage/theme@0.6.6 + - @backstage/core-app-api@1.17.0 + - @backstage/canon@0.4.0 + - @backstage/cli@0.32.1 + - @backstage/plugin-user-settings@0.8.22 + - @backstage/core-components@0.17.2 + - @backstage/plugin-home@0.8.8 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-scaffolder@1.31.0 + - @backstage/frontend-app-api@0.11.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17 + - @backstage/plugin-catalog-import@0.13.0 + - @backstage/plugin-catalog-graph@0.4.19 + - @backstage/plugin-notifications@0.5.5 + - @backstage/plugin-kubernetes@0.12.7 + - @backstage/plugin-api-docs@0.12.7 + - @backstage/plugin-devtools@0.1.27 + - @backstage/plugin-techdocs@1.12.6 + - @backstage/plugin-catalog@1.30.0 + - @backstage/plugin-signals@0.0.19 + - @backstage/plugin-search@1.4.26 + - @backstage/plugin-org@0.6.39 + - @backstage/plugin-search-react@1.9.0 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/plugin-scaffolder-react@1.16.0 + - @backstage/plugin-kubernetes-cluster@0.0.25 + - @backstage/plugin-techdocs-react@1.2.17 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24 + - @backstage/app-defaults@1.6.2 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-search-common@1.2.18 + +## 0.2.109-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/canon@0.4.0-next.3 + - @backstage/cli@0.32.1-next.3 + - @backstage/plugin-home@0.8.8-next.3 + - @backstage/plugin-catalog@1.30.0-next.3 + - @backstage/frontend-app-api@0.11.2-next.3 + - @backstage/plugin-search-react@1.9.0-next.2 + - @backstage/plugin-scaffolder@1.31.0-next.3 + - @backstage/plugin-search@1.4.26-next.3 + - @backstage/app-defaults@1.6.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-api-docs@0.12.7-next.3 + - @backstage/plugin-catalog-graph@0.4.19-next.3 + - @backstage/plugin-catalog-import@0.13.0-next.3 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/plugin-notifications@0.5.5-next.3 + - @backstage/plugin-org@0.6.39-next.3 + - @backstage/plugin-scaffolder-react@1.16.0-next.3 + - @backstage/plugin-signals@0.0.19-next.1 + - @backstage/plugin-techdocs@1.12.6-next.3 + - @backstage/plugin-user-settings@0.8.22-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.3 + - @backstage/plugin-devtools@0.1.27-next.3 + - @backstage/plugin-kubernetes@0.12.7-next.3 + - @backstage/plugin-kubernetes-cluster@0.0.25-next.3 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.3 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + ## 0.2.109-next.2 ### Patch Changes diff --git a/packages/app/catalog-info.yaml b/packages/app/catalog-info.yaml index e0808307cc..5880de9597 100644 --- a/packages/app/catalog-info.yaml +++ b/packages/app/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-frontend - owner: maintainers + owner: framework-maintainers diff --git a/packages/app/e2e-tests/HomePage.test.ts b/packages/app/e2e-tests/HomePage.test.ts index 0b1459935f..f495fb08d9 100644 --- a/packages/app/e2e-tests/HomePage.test.ts +++ b/packages/app/e2e-tests/HomePage.test.ts @@ -35,7 +35,7 @@ test('Should not throw `ResizeObserver loop completed with undelivered notificat ).not.toBeVisible(); }); -test('Should resize widgets vertically and horizontally', async ({ page }) => { +test('Should render some home page widgets', async ({ page }) => { await page.goto('/'); const enterButton = page.getByRole('button', { name: 'Enter' }); @@ -43,32 +43,6 @@ test('Should resize widgets vertically and horizontally', async ({ page }) => { await enterButton.click(); await page.goto('/home'); - await expect(page.getByText('Backstage Example App')).toBeVisible(); - - // Start editing mode - await page.getByRole('button', { name: /Edit/ }).click(); - await expect(page.getByRole('button', { name: /Save/ })).toBeVisible(); - - // Resize the last installed widget - const widgetElement = await page.locator('.react-grid-item:nth-child(3)'); - const defaultWidgetBox = { x: 1, y: 1, width: 1, height: 1 }; - const widgetBoxBefore = - (await widgetElement.boundingBox()) ?? defaultWidgetBox; - const widgetResizeHandle = await widgetElement.locator( - '.react-resizable-handle', - ); - await widgetResizeHandle.hover(); - await page.mouse.down(); - await page.mouse.move(widgetBoxBefore.width / 2, widgetBoxBefore.height / 2); - await page.mouse.up(); - const widgetBoxAfter = - (await widgetElement.boundingBox()) ?? defaultWidgetBox; - - // Ensure that both height and width was reduced - expect(widgetBoxAfter.width).toBeLessThan(widgetBoxBefore.width); - expect(widgetBoxAfter.height).toBeLessThan(widgetBoxBefore.height); - - // Exit editing mode - await page.getByRole('button', { name: 'Save' }).click(); - await expect(page.getByRole('button', { name: /Edit/ })).toBeVisible(); + await expect(page.getByText('Top Visited')).toBeVisible(); + await expect(page.getByText('Recently Visited')).toBeVisible(); }); diff --git a/packages/app/package.json b/packages/app/package.json index b988bf460d..6ef4a0c14a 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.109-next.2", + "version": "0.2.112-next.2", "backstage": { "role": "frontend" }, @@ -36,7 +36,6 @@ }, "dependencies": { "@backstage/app-defaults": "workspace:^", - "@backstage/canon": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/config": "workspace:^", @@ -71,6 +70,7 @@ "@backstage/plugin-techdocs-react": "workspace:^", "@backstage/plugin-user-settings": "workspace:^", "@backstage/theme": "workspace:^", + "@backstage/ui": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", @@ -94,7 +94,7 @@ "@types/react": "*", "@types/react-dom": "*", "@types/zen-observable": "^0.8.0", - "axios": "^1.7.7", + "axios": "^1.11.0", "cross-env": "^7.0.0", "msw": "^1.0.0" }, diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 3927992fd4..5ac9052d2a 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -27,7 +27,7 @@ import { RELATION_PROVIDES_API, } from '@backstage/catalog-model'; import { createApp } from '@backstage/app-defaults'; -import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; +import { AppRouter, FeatureFlagged, FlatRoutes } from '@backstage/core-app-api'; import { AlertDisplay, OAuthRequestDialog, @@ -66,7 +66,6 @@ import AlarmIcon from '@material-ui/icons/Alarm'; import { Navigate, Route } from 'react-router-dom'; import { apis } from './apis'; import { entityPage } from './components/catalog/EntityPage'; -import { homePage } from './components/home/HomePage'; import { Root } from './components/Root'; import { DelayingComponentFieldExtension } from './components/scaffolder/customScaffolderExtensions'; import { defaultPreviewTemplate } from './components/scaffolder/defaultPreviewTemplate'; @@ -84,6 +83,8 @@ import { NotificationsPage, UserNotificationSettingsCard, } from '@backstage/plugin-notifications'; +import { CustomizableHomePage } from './components/home/CustomizableHomePage'; +import { HomePage } from './components/home/HomePage'; const app = createApp({ apis, @@ -115,10 +116,20 @@ const app = createApp({ const routes = ( } /> + {/* TODO(rubenl): Move this to / once its more mature and components exist */} - }> - {homePage} - + + + }> + + + + + }> + + + + } diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index e39122337f..8a05f100bc 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -24,11 +24,15 @@ import { configApiRef, createApiFactory, discoveryApiRef, + fetchApiRef, + identityApiRef, } 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 +46,24 @@ export const apis: AnyApiFactory[] = [ factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), }), + createApiFactory({ + api: scaffolderApiRef, + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, + identityApi: identityApiRef, + }, + factory: ({ discoveryApi, fetchApi, scmIntegrationsApi, identityApi }) => + new ScaffolderClient({ + useLongPollingLogs: true, + discoveryApi, + fetchApi, + scmIntegrationsApi, + identityApi, + }), + }), + 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/app/src/components/home/CustomizableHomePage.tsx b/packages/app/src/components/home/CustomizableHomePage.tsx new file mode 100644 index 0000000000..b592490f2e --- /dev/null +++ b/packages/app/src/components/home/CustomizableHomePage.tsx @@ -0,0 +1,96 @@ +/* + * 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 { Page, Content } from '@backstage/core-components'; +import { + HomePageCompanyLogo, + TemplateBackstageLogo, + HomePageStarredEntities, + HomePageToolkit, + CustomHomepageGrid, + HomePageRandomJoke, + HomePageTopVisited, + HomePageRecentlyVisited, +} from '@backstage/plugin-home'; +import { HomePageSearchBar } from '@backstage/plugin-search'; +import Grid from '@material-ui/core/Grid'; + +import { tools, useLogoStyles } from './shared'; +import { WelcomeTitle } from '@backstage/plugin-home'; + +const defaultConfig = [ + { + component: 'HomePageSearchBar', + x: 0, + y: 0, + width: 24, + height: 2, + }, + { + component: 'HomePageRecentlyVisited', + x: 0, + y: 1, + width: 5, + height: 4, + }, + { + component: 'HomePageTopVisited', + x: 5, + y: 1, + width: 5, + height: 4, + }, + { + component: 'HomePageStarredEntities', + x: 0, + y: 2, + width: 6, + height: 4, + }, + { + component: 'HomePageToolkit', + x: 6, + y: 6, + width: 4, + height: 4, + }, +]; + +export const CustomizableHomePage = () => { + const { svg, path, container } = useLogoStyles(); + + return ( + + + + } + /> + + + + + + + + + + + + + + ); +}; diff --git a/packages/app/src/components/home/HomePage.tsx b/packages/app/src/components/home/HomePage.tsx index f0f49ab34e..ca555280dd 100644 --- a/packages/app/src/components/home/HomePage.tsx +++ b/packages/app/src/components/home/HomePage.tsx @@ -14,21 +14,37 @@ * limitations under the License. */ +import { Page, Content, Header } from '@backstage/core-components'; import { - ClockConfig, - CustomHomepageGrid, - HeaderWorldClock, HomePageCompanyLogo, - HomePageRandomJoke, + TemplateBackstageLogo, HomePageStarredEntities, HomePageToolkit, HomePageTopVisited, HomePageRecentlyVisited, WelcomeTitle, + HeaderWorldClock, + ClockConfig, } from '@backstage/plugin-home'; -import { Content, Header, Page } from '@backstage/core-components'; import { HomePageSearchBar } from '@backstage/plugin-search'; -import HomeIcon from '@material-ui/icons/Home'; +import { SearchContextProvider } from '@backstage/plugin-search-react'; +import Grid from '@material-ui/core/Grid'; +import { makeStyles } from '@material-ui/core/styles'; + +import { tools, useLogoStyles } from './shared'; + +const useStyles = makeStyles(theme => ({ + searchBarInput: { + maxWidth: '60vw', + margin: 'auto', + backgroundColor: theme.palette.background.paper, + borderRadius: '50px', + boxShadow: theme.shadows[1], + }, + searchBarOutline: { + borderStyle: 'none', + }, +})); const clockConfigs: ClockConfig[] = [ { @@ -55,60 +71,55 @@ const timeFormat: Intl.DateTimeFormatOptions = { hour12: false, }; -const defaultConfig = [ - { - component: 'CompanyLogo', - x: 0, - y: 0, - width: 12, - height: 1, - movable: false, - resizable: false, - deletable: false, - }, - { - component: 'WelcomeTitle', - x: 0, - y: 1, - width: 12, - height: 1, - }, - { - component: 'HomePageSearchBar', - x: 0, - y: 2, - width: 12, - height: 2, - }, -]; +export const HomePage = () => { + const classes = useStyles(); + const { svg, path, container } = useLogoStyles(); -export const homePage = ( - -
    } pageTitleOverride="Home"> - -
    - - - - - - - - , - }, - ]} - /> - - - - -
    -); + return ( + + +
    } pageTitleOverride="Home"> + +
    + + + } + /> + + + + + + + + + + + + + + + + + + + + + +
    +
    + ); +}; diff --git a/packages/app/src/components/home/shared.tsx b/packages/app/src/components/home/shared.tsx new file mode 100644 index 0000000000..c8b9764c83 --- /dev/null +++ b/packages/app/src/components/home/shared.tsx @@ -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 { TemplateBackstageLogoIcon } from '@backstage/plugin-home'; +import { makeStyles } from '@material-ui/core/styles'; + +export const useLogoStyles = makeStyles(theme => ({ + container: { + margin: theme.spacing(5, 0), + }, + svg: { + width: 'auto', + height: 100, + }, + path: { + fill: '#7df3e1', + }, +})); + +export const tools = [ + { + url: 'https://backstage.io/docs', + label: 'Docs', + icon: , + }, + { + url: 'https://github.com/backstage/backstage', + label: 'GitHub', + icon: , + }, + { + url: 'https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md', + label: 'Contributing', + icon: , + }, + { + url: 'https://backstage.io/plugins', + label: 'Plugins Directory', + icon: , + }, + { + url: 'https://github.com/backstage/backstage/issues/new/choose', + label: 'Submit New Issue', + icon: , + }, +]; diff --git a/packages/app/src/index-public-experimental.tsx b/packages/app/src/index-public-experimental.tsx index fb79eb921a..972c1fd55c 100644 --- a/packages/app/src/index-public-experimental.tsx +++ b/packages/app/src/index-public-experimental.tsx @@ -30,7 +30,7 @@ import { discoveryApiRef, } from '@backstage/core-plugin-api'; import { AuthProxyDiscoveryApi } from '../src/AuthProxyDiscoveryApi'; -import '@backstage/canon/css/styles.css'; +import '@backstage/ui/css/styles.css'; const app = createApp({ apis: [ diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index cb6eeb3185..05dcc024bf 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -17,6 +17,6 @@ import '@backstage/cli/asset-types'; import ReactDOM from 'react-dom/client'; import App from './App'; -import '@backstage/canon/css/styles.css'; +import '@backstage/ui/css/styles.css'; ReactDOM.createRoot(document.getElementById('root')!).render(); diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 2d49092405..8ab9c90852 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,117 @@ # @backstage/backend-app-api +## 1.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## 1.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + +## 1.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + +## 1.2.4 + +### Patch Changes + +- bb9a501: Fixed a bug where occasionally the initialization order of multiple modules consuming a single extension point could happen in the wrong order. +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 1.2.4-next.2 + +### Patch Changes + +- bb9a501: Fixed a bug where occasionally the initialization order of multiple modules consuming a single extension point could happen in the wrong order. +- 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.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 + +- 729a7d6: Added a configuration to permit backend plugin module failures on startup: + + ```yaml + backend: + ... + startup: + plugins: + plugin-x: + modules: + module-y: + onPluginModuleBootFailure: continue + ``` + + This configuration permits `plugin-x` with `module-y` to fail on startup. Omitting the + `onPluginModuleBootFailure` configuration matches the previous behavior, wherein any + individual plugin module failure is forwarded to the plugin and aborts backend startup. + + The default can also be changed, so that continuing on failure is the default + unless otherwise specified: + + ```yaml + backend: + startup: + default: + onPluginModuleBootFailure: continue + plugins: + catalog: + modules: + github: + onPluginModuleBootFailure: abort + ``` + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 1.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + ## 1.2.3-next.1 ### Patch Changes diff --git a/packages/backend-app-api/catalog-info.yaml b/packages/backend-app-api/catalog-info.yaml index 051e7ab5c2..c181c0720a 100644 --- a/packages/backend-app-api/catalog-info.yaml +++ b/packages/backend-app-api/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 01812f903c..0caa2c5ca0 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-next.1", + "version": "1.2.6-next.0", "description": "Core API used by Backstage backend apps", "backstage": { "role": "node-library" diff --git a/packages/backend-app-api/src/lib/DependencyGraph.test.ts b/packages/backend-app-api/src/lib/DependencyGraph.test.ts index 394359e486..8aafcb21aa 100644 --- a/packages/backend-app-api/src/lib/DependencyGraph.test.ts +++ b/packages/backend-app-api/src/lib/DependencyGraph.test.ts @@ -514,5 +514,21 @@ describe('DependencyGraph', () => { }).parallelTopologicalTraversal(async id => id), ).rejects.toThrow('Circular dependency detected'); }); + + it('awaits all producers', async () => { + await expect( + DependencyGraph.fromMap({ + 1: { provides: ['a'] }, + 2: { provides: ['a'] }, + 3: { consumes: ['a'] }, + }).parallelTopologicalTraversal(async id => { + // Delaying 2 should not make 3 run, wait for 1 too first + if (id === '2') { + await new Promise(resolve => setTimeout(resolve, 100)); + } + return id; + }), + ).resolves.toEqual(['1', '2', '3']); + }); }); }); diff --git a/packages/backend-app-api/src/lib/DependencyGraph.ts b/packages/backend-app-api/src/lib/DependencyGraph.ts index 86fad3fbf2..8b3de48ff2 100644 --- a/packages/backend-app-api/src/lib/DependencyGraph.ts +++ b/packages/backend-app-api/src/lib/DependencyGraph.ts @@ -188,12 +188,25 @@ export class DependencyGraph { fn: (value: T) => Promise, ): Promise { const allProvided = this.#allProvided; - const producedSoFar = new Set(); const waiting = new Set(this.#nodes.values()); const visited = new Set>(); const results = new Array(); let inFlight = 0; // Keep track of how many callbacks are in flight, so that we know if we got stuck + // This keeps track of a counter of how many providers there are still left + // to be visited for each dependency. This needs to be a counter instead of + // a flag for the special case where there are several providers of a given + // value, even though there may be only one consumer of it. + const producedRemaining = new Map(); + for (const node of this.#nodes) { + for (const provided of node.provides) { + producedRemaining.set( + provided, + (producedRemaining.get(provided) ?? 0) + 1, + ); + } + } + // Find all nodes that have no dependencies that have not already been produced by visited nodes async function processMoreNodes() { if (waiting.size === 0) { @@ -203,7 +216,10 @@ export class DependencyGraph { for (const node of waiting) { let ready = true; for (const consumed of node.consumes) { - if (allProvided.has(consumed) && !producedSoFar.has(consumed)) { + if ( + allProvided.has(consumed) && + producedRemaining.get(consumed) !== 0 + ) { ready = false; continue; } @@ -234,7 +250,17 @@ export class DependencyGraph { const result = await fn(node.value); results.push(result); - node.provides.forEach(produced => producedSoFar.add(produced)); + node.provides.forEach(produced => { + const remaining = producedRemaining.get(produced); + if (!remaining) { + // This should be impossible, if the code that generates the map is correct + throw new Error( + `Internal error: Node provided superfluous dependency '${produced}'`, + ); + } + producedRemaining.set(produced, remaining - 1); + }); + inFlight -= 1; await processMoreNodes(); } diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts index 3b0596cd13..9e8a6579fd 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts @@ -1134,4 +1134,68 @@ describe('BackendInitializer', () => { backend.add(instanceMetadataPlugin); await backend.start(); }); + + it('should properly wait for all modules that consume an extension point to really finish, before starting the module that provides that extension point', async () => { + expect.assertions(3); + const backend = new BackendInitializer(baseFactories); + const ext = createExtensionPoint<{ hello: (message: string) => void }>({ + id: 'a', + }); + const plugin = createBackendPlugin({ + pluginId: 'test', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }); + const producerModule = createBackendModule({ + pluginId: 'test', + moduleId: 'producer', + register(reg) { + const hello = jest.fn(); + reg.registerExtensionPoint(ext, { hello }); + reg.registerInit({ + deps: {}, + async init() { + // we must not have been initialized before both of the consuming modules have been initialized + expect(hello).toHaveBeenCalledTimes(2); + expect(hello).toHaveBeenNthCalledWith(1, 'fast'); + expect(hello).toHaveBeenNthCalledWith(2, 'slow'); + }, + }); + }, + }); + const fastConsumerModule = createBackendModule({ + pluginId: 'test', + moduleId: 'fast-consumer', + register(reg) { + reg.registerInit({ + deps: { x: ext }, + async init({ x }) { + x.hello('fast'); + }, + }); + }, + }); + const slowConsumerModule = createBackendModule({ + pluginId: 'test', + moduleId: 'slow-consumer', + register(reg) { + reg.registerInit({ + deps: { x: ext }, + async init({ x }) { + await new Promise(resolve => setTimeout(resolve, 100)); + x.hello('slow'); + }, + }); + }, + }); + await backend.add(plugin); + await backend.add(producerModule); + await backend.add(fastConsumerModule); + await backend.add(slowConsumerModule); + await backend.start(); + }); }); diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index b66cdb5ac7..603e787d3e 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,244 @@ # @backstage/backend-defaults +## 0.11.2-next.0 + +### Patch Changes + +- caee2eb: Fixed WinstonLogger throwing when redactions were null or undefined +- 3a7dad9: Updated `better-sqlite3` to v12 +- Updated dependencies + - @backstage/backend-app-api@1.2.6-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.2 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/types@1.2.1 + +## 0.11.1 + +### Patch Changes + +- ead925a: Add a standard `toString` on credentials objects +- e0189b8: UrlReader: Fix handling of access tokens for GitLab readURL requests +- d1e4a6d: Fixed bug where the GitLab user token and GitLab integration token were being merged together +- Updated dependencies + - @backstage/config-loader@1.10.2 + - @backstage/config@1.3.3 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/integration@1.17.1 + - @backstage/backend-app-api@1.2.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-events-node@0.4.13 + +## 0.11.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2-next.0 + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/integration-aws-node@0.1.17-next.0 + - @backstage/backend-app-api@1.2.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.11.1-next.0 + +### Patch Changes + +- ead925a: Add a standard `toString` on credentials objects +- e0189b8: UrlReader: Fix handling of access tokens for GitLab readURL requests +- d1e4a6d: Fixed bug where the GitLab user token and GitLab integration token were being merged together +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-app-api@1.2.4 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + +## 0.11.0 + +### Minor Changes + +- 3ccb7fc: Enhanced error handling in the auditor service factory to pass errors as objects. Aligned WinstonRootAuditorService with the default service factory's error handling. + +### Patch Changes + +- 1220cf8: Added new rate limit middleware to allow rate limiting requests to the backend + + If you are using the `configure` callback of the root HTTP router service and do NOT call `applyDefaults()` inside it, please see [the relevant changes](https://github.com/backstage/backstage/pull/28708/files#diff-86ad1b6a694dd250823aee39d410428dd837c9d9a04ca8c33bd1081fbe3f22af) that were made, to see if you want to apply them as well to your custom configuration. + Rate limiting can be turned on by adding the following configuration to `app-config.yaml`: + + ```yaml + backend: + rateLimit: + window: 6s + incomingRequestLimit: 100 + ``` + + Plugin specific rate limiting can be configured by adding the following configuration to `app-config.yaml`: + + ```yaml + backend: + rateLimit: + global: false # This will disable the global rate limiting + plugin: + catalog: + window: 6s + incomingRequestLimit: 100 + ``` + +- c999c25: Added some default implementations for the experimental `ActionsService` and `ActionsRegistryService` under `/alpha` that allow registration of actions for a particular plugin. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-app-api@1.2.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/backend-dev-utils@0.1.5 + - @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 + - @backstage/plugin-permission-node@0.10.1 + +## 0.11.0-next.2 + +### Minor Changes + +- 3ccb7fc: Enhanced error handling in the auditor service factory to pass errors as objects. Aligned WinstonRootAuditorService with the default service factory's error handling. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@1.2.4-next.2 + - @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-auth-node@0.6.4-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## 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 + +- d385854: **BREAKING**: The `DefaultSchedulerService` constructor options now requires `RootLifecycleService`, `HttpRouterService`, and `PluginMetadataService` fields. + + The scheduler will register a REST API for listing and triggering tasks. Please see [the scheduler documentation](https://backstage.io/docs/backend-system/core-services/scheduler) for more details about this API. + +### Patch Changes + +- 1e06afd: `GithubUrlReader`'s search detects glob-patterns supported by `minimatch`, instead of just detecting + `*` and `?` characters. + + For example, this allows to search for patterns like `{C,c}atalog-info.yaml`. + +- acea1d4: update documentation +- 72d019d: Removed various typos +- c6bc67d: Added Valkey support alongside Redis in backend-defaults cache clients, using the new Keyv Valkey package. Also extended backend-test-utils to support Valkey in tests. +- 36f77e9: Bug fix: Pass user provided token through to gitlab url resolvers +- 0e7a640: The `GithubUrlReader` will now use the token from `options` when fetching repo details +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-app-api@1.2.3 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/config-loader@1.10.1 + - @backstage/integration-aws-node@0.1.16 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## 0.10.0-next.3 + +### Patch Changes + +- 1e06afd: `GithubUrlReader`'s search detects glob-patterns supported by `minimatch`, instead of just detecting + `*` and `?` characters. + + For example, this allows to search for patterns like `{C,c}atalog-info.yaml`. + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-app-api@1.2.3-next.2 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + - @backstage/plugin-permission-node@0.10.0-next.2 + ## 0.10.0-next.2 ### Patch Changes diff --git a/packages/backend-defaults/catalog-info.yaml b/packages/backend-defaults/catalog-info.yaml index d3d00cc3d2..77971441c0 100644 --- a/packages/backend-defaults/catalog-info.yaml +++ b/packages/backend-defaults/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts index 14732bbccd..167a8935f9 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. */ @@ -780,6 +790,92 @@ export interface Config { headers?: { [name: string]: string }; }; + /** + * Rate limiting options. Defining this as `true` will enable rate limiting with default values. + */ + rateLimit?: + | true + | { + store?: + | { + type: 'redis'; + connection: string; + } + | { + type: 'memory'; + }; + /** + * Enable/disable global rate limiting. If this is disabled, plugin specific rate limiting must be + * used. + */ + global?: boolean; + /** + * Time frame in milliseconds or as human duration for which requests are checked/remembered. + * Defaults to one minute. + */ + window?: string | HumanDuration; + /** + * The maximum number of connections to allow during the `window` before rate limiting the client. + * Defaults to 5. + */ + incomingRequestLimit?: number; + /** + * Whether to pass requests in case of store failure. + * Defaults to false. + */ + passOnStoreError?: boolean; + /** + * List of allowed IP addresses that are not rate limited. + * Defaults to [127.0.0.1, 0:0:0:0:0:0:0:1, ::1]. + */ + ipAllowList?: string[]; + /** + * Skip rate limiting for requests that have been successful. + * Defaults to false. + */ + skipSuccessfulRequests?: boolean; + /** + * Skip rate limiting for requests that have failed. + * Defaults to false. + */ + skipFailedRequests?: boolean; + /** Plugin specific rate limiting configuration */ + plugin?: { + [pluginId: string]: { + /** + * Time frame in milliseconds or as human duration for which requests are checked/remembered. + * Defaults to one minute. + */ + window?: string | HumanDuration; + /** + * The maximum number of connections to allow during the `window` before rate limiting the client. + * Defaults to 5. + */ + incomingRequestLimit?: number; + /** + * Whether to pass requests in case of store failure. + * Defaults to false. + */ + passOnStoreError?: boolean; + /** + * List of allowed IP addresses that are not rate limited. + * Defaults to [127.0.0.1, 0:0:0:0:0:0:0:1, ::1]. + */ + ipAllowList?: string[]; + /** + * Skip rate limiting for requests that have been successful. + * Defaults to false. + */ + skipSuccessfulRequests?: boolean; + /** + * Skip rate limiting for requests that have failed. + * Defaults to false. + */ + skipFailedRequests?: boolean; + }; + }; + }; + /** * Configuration related to URL reading, used for example for reading catalog info * files, scaffolder templates, and techdocs content. diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 093ebdc422..95ea490bb2 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-next.2", + "version": "0.11.2-next.0", "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" @@ -39,6 +39,7 @@ "./scheduler": "./src/entrypoints/scheduler/index.ts", "./urlReader": "./src/entrypoints/urlReader/index.ts", "./userInfo": "./src/entrypoints/userInfo/index.ts", + "./alpha": "./src/alpha/index.ts", "./package.json": "./package.json" }, "main": "src/index.ts", @@ -102,6 +103,9 @@ "userInfo": [ "src/entrypoints/userInfo/index.ts" ], + "alpha": [ + "src/alpha/index.ts" + ], "package.json": [ "package.json" ] @@ -152,7 +156,7 @@ "@types/express": "^4.17.6", "archiver": "^7.0.0", "base64-stream": "^1.0.0", - "better-sqlite3": "^11.0.0", + "better-sqlite3": "^12.0.0", "compression": "^1.7.4", "concat-stream": "^2.0.0", "cookie": "^0.7.0", @@ -160,9 +164,11 @@ "cron": "^3.0.0", "express": "^4.17.1", "express-promise-router": "^4.1.0", + "express-rate-limit": "^7.5.0", "fs-extra": "^11.2.0", "git-url-parse": "^15.0.0", "helmet": "^6.0.0", + "is-glob": "^4.0.3", "jose": "^5.0.0", "keyv": "^5.2.1", "knex": "^3.0.0", @@ -178,6 +184,7 @@ "pg": "^8.11.3", "pg-connection-string": "^2.3.0", "pg-format": "^1.0.4", + "rate-limit-redis": "^4.2.0", "raw-body": "^2.4.1", "selfsigned": "^2.0.0", "tar": "^6.1.12", @@ -187,7 +194,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", @@ -200,6 +208,7 @@ "@types/compression": "^1.7.5", "@types/concat-stream": "^2.0.0", "@types/http-errors": "^2.0.0", + "@types/is-glob": "^4.0.2", "@types/node-forge": "^1.3.0", "@types/pg-format": "^1.0.5", "@types/yauzl": "^2.10.0", diff --git a/packages/backend-defaults/report-alpha.api.md b/packages/backend-defaults/report-alpha.api.md new file mode 100644 index 0000000000..58277b5e1e --- /dev/null +++ b/packages/backend-defaults/report-alpha.api.md @@ -0,0 +1,25 @@ +## 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/alpha'; +import { ActionsService } from '@backstage/backend-plugin-api/alpha'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; + +// @public (undocumented) +export const actionsRegistryServiceFactory: ServiceFactory< + ActionsRegistryService, + 'plugin', + 'singleton' +>; + +// @public (undocumented) +export const actionsServiceFactory: ServiceFactory< + ActionsService, + 'plugin', + 'singleton' +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-defaults/report-auditor.api.md b/packages/backend-defaults/report-auditor.api.md index 353b6f241f..cc00b5c349 100644 --- a/packages/backend-defaults/report-auditor.api.md +++ b/packages/backend-defaults/report-auditor.api.md @@ -8,6 +8,7 @@ import type { AuditorServiceCreateEventOptions } from '@backstage/backend-plugin import type { AuditorServiceEvent } from '@backstage/backend-plugin-api'; import type { AuditorServiceEventSeverityLevel } from '@backstage/backend-plugin-api'; import type { AuthService } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; import type { Format } from 'logform'; import type { HttpAuthService } from '@backstage/backend-plugin-api'; import type { JsonObject } from '@backstage/types'; @@ -58,7 +59,7 @@ export type AuditorEventStatus = } | { status: 'failed'; - error: string; + error: Error; }; // @public @@ -95,6 +96,7 @@ export class WinstonRootAuditorService { // (undocumented) forPlugin(deps: { auth: AuthService; + config: Config; httpAuth: HttpAuthService; plugin: PluginMetadataService; }): AuditorService; diff --git a/packages/backend-defaults/report-rootHttpRouter.api.md b/packages/backend-defaults/report-rootHttpRouter.api.md index 826d592e8b..84424be39d 100644 --- a/packages/backend-defaults/report-rootHttpRouter.api.md +++ b/packages/backend-defaults/report-rootHttpRouter.api.md @@ -93,6 +93,7 @@ export class MiddlewareFactory { helmet(): RequestHandler; logging(): RequestHandler; notFound(): RequestHandler; + rateLimit(): RequestHandler; } // @public diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index d618c94138..4b201fded3 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -35,6 +35,10 @@ 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, + actionsServiceFactory, +} from '@backstage/backend-defaults/alpha'; export const defaultServiceFactories = [ auditorServiceFactory, @@ -57,6 +61,10 @@ export const defaultServiceFactories = [ userInfoServiceFactory, urlReaderServiceFactory, eventsServiceFactory, + + // alpha services + actionsRegistryServiceFactory, + actionsServiceFactory, ]; /** diff --git a/packages/backend-defaults/src/alpha/entrypoints/actions/DefaultActionsService.ts b/packages/backend-defaults/src/alpha/entrypoints/actions/DefaultActionsService.ts new file mode 100644 index 0000000000..d8847c2187 --- /dev/null +++ b/packages/backend-defaults/src/alpha/entrypoints/actions/DefaultActionsService.ts @@ -0,0 +1,141 @@ +/* + * 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 { + AuthService, + BackstageCredentials, + DiscoveryService, + LoggerService, + RootConfigService, +} from '@backstage/backend-plugin-api'; +import { ResponseError } from '@backstage/errors'; +import { JsonObject } from '@backstage/types'; +import { + ActionsService, + ActionsServiceAction, +} from '@backstage/backend-plugin-api/alpha'; + +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/alpha/entrypoints/actions/actionsServiceFactory.test.ts b/packages/backend-defaults/src/alpha/entrypoints/actions/actionsServiceFactory.test.ts new file mode 100644 index 0000000000..af2fcb0c12 --- /dev/null +++ b/packages/backend-defaults/src/alpha/entrypoints/actions/actionsServiceFactory.test.ts @@ -0,0 +1,346 @@ +/* + * 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 '../../../entrypoints/httpRouter'; +import { actionsServiceFactory } from './actionsServiceFactory'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { json } from 'express'; +import Router from 'express-promise-router'; +import request from 'supertest'; +import { ActionsServiceAction } from '@backstage/backend-plugin-api/alpha'; +import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha'; +import { actionsServiceRef } from '@backstage/backend-plugin-api/alpha'; + +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: {}, + }, + attributes: { + destructive: false, + idempotent: false, + readOnly: false, + }, + }; + + 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: actionsRegistryServiceRef }, + 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: actionsServiceRef, + 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', + }, + }, + attributes: { + destructive: true, + idempotent: false, + readOnly: false, + }, + 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/alpha/entrypoints/actions/actionsServiceFactory.ts b/packages/backend-defaults/src/alpha/entrypoints/actions/actionsServiceFactory.ts new file mode 100644 index 0000000000..4325da7d13 --- /dev/null +++ b/packages/backend-defaults/src/alpha/entrypoints/actions/actionsServiceFactory.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createServiceFactory } from '@backstage/backend-plugin-api'; +import { coreServices } from '@backstage/backend-plugin-api'; +import { DefaultActionsService } from './DefaultActionsService'; +import { actionsServiceRef } from '@backstage/backend-plugin-api/alpha'; + +/** + * @public + */ +export const actionsServiceFactory = createServiceFactory({ + service: actionsServiceRef, + 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/packages/canon/src/components/DataTable/Table/types.ts b/packages/backend-defaults/src/alpha/entrypoints/actions/index.ts similarity index 85% rename from packages/canon/src/components/DataTable/Table/types.ts rename to packages/backend-defaults/src/alpha/entrypoints/actions/index.ts index a4ef96c732..bc34150a5f 100644 --- a/packages/canon/src/components/DataTable/Table/types.ts +++ b/packages/backend-defaults/src/alpha/entrypoints/actions/index.ts @@ -13,7 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/** @public */ -export interface DataTableTableProps - extends React.HTMLAttributes {} +export { actionsServiceFactory } from './actionsServiceFactory'; diff --git a/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts new file mode 100644 index 0000000000..2284935787 --- /dev/null +++ b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts @@ -0,0 +1,166 @@ +/* + * 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 { + AuthService, + HttpAuthService, + LoggerService, + PluginMetadataService, +} from '@backstage/backend-plugin-api'; +import PromiseRouter from 'express-promise-router'; +import { Router, json } from 'express'; +import { z, AnyZodObject } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; +import { + ActionsRegistryActionOptions, + ActionsRegistryService, +} from '@backstage/backend-plugin-api/alpha'; +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, + attributes: { + // Inspired by the @modelcontextprotocol/sdk defaults for the hints. + // https://github.com/modelcontextprotocol/typescript-sdk/blob/dd69efa1de8646bb6b195ff8d5f52e13739f4550/src/types.ts#L777-L812 + destructive: action.attributes?.destructive ?? true, + idempotent: action.attributes?.idempotent ?? false, + readOnly: action.attributes?.readOnly ?? false, + }, + schema: { + input: action.schema?.input + ? zodToJsonSchema(action.schema.input(z)) + : zodToJsonSchema(z.object({})), + output: action.schema?.output + ? zodToJsonSchema(action.schema.output(z)) + : zodToJsonSchema(z.object({})), + }, + })), + }); + }); + + 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< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, + >(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/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts new file mode 100644 index 0000000000..79c103c284 --- /dev/null +++ b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts @@ -0,0 +1,538 @@ +/* + * 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 { createBackendPlugin } from '@backstage/backend-plugin-api'; +import { + mockCredentials, + mockServices, + startTestBackend, +} from '@backstage/backend-test-utils'; +import { httpRouterServiceFactory } from '../../../entrypoints/httpRouter'; +import request from 'supertest'; +import { actionsRegistryServiceFactory } from './actionsRegistryServiceFactory'; +import { InputError } from '@backstage/errors'; +import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha'; + +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: actionsRegistryServiceRef, + }, + 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: actionsRegistryServiceRef, + }, + 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: actionsRegistryServiceRef, + }, + 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 set default attributes', async () => { + const pluginSubject = createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { + actionsRegistry: actionsRegistryServiceRef, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({}), + output: z => z.object({}), + }, + 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', + attributes: { + destructive: true, + idempotent: false, + readOnly: false, + }, + }, + ], + }); + }); + + it('should allow setting attributes', async () => { + const pluginSubject = createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { + actionsRegistry: actionsRegistryServiceRef, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + attributes: { + destructive: false, + idempotent: true, + readOnly: true, + }, + schema: { + input: z => z.object({}), + output: z => z.object({}), + }, + 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', + attributes: { + destructive: false, + idempotent: true, + readOnly: true, + }, + }, + ], + }); + }); + + it('should forces registration of input and output schema as objects', async () => { + const pluginSubject = createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { + actionsRegistry: actionsRegistryServiceRef, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + // @ts-expect-error - z.undefined is not a valid schema + input: z => z.undefined(), + // @ts-expect-error - z.string is not a valid schema + output: z => z.string(), + }, + // @ts-expect-error - output is not a valid, needs to be an object + 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: actionsRegistryServiceRef, + }, + 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/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts new file mode 100644 index 0000000000..8c19b8148c --- /dev/null +++ b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts @@ -0,0 +1,48 @@ +/* + * 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'; +import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha'; + +/** + * @public + */ +export const actionsRegistryServiceFactory = createServiceFactory({ + service: actionsRegistryServiceRef, + 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/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/index.ts b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/index.ts new file mode 100644 index 0000000000..ac65dd7a90 --- /dev/null +++ b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { actionsRegistryServiceFactory } from './actionsRegistryServiceFactory'; diff --git a/packages/backend-defaults/src/alpha/index.ts b/packages/backend-defaults/src/alpha/index.ts new file mode 100644 index 0000000000..13bb439acd --- /dev/null +++ b/packages/backend-defaults/src/alpha/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { actionsRegistryServiceFactory } from './entrypoints/actionsRegistry'; +export { actionsServiceFactory } from './entrypoints/actions'; diff --git a/packages/backend-defaults/src/entrypoints/auditor/DefaultAuditorService.test.ts b/packages/backend-defaults/src/entrypoints/auditor/DefaultAuditorService.test.ts index c960a1daa2..ac32c9e911 100644 --- a/packages/backend-defaults/src/entrypoints/auditor/DefaultAuditorService.test.ts +++ b/packages/backend-defaults/src/entrypoints/auditor/DefaultAuditorService.test.ts @@ -83,7 +83,7 @@ describe('DefaultAuditorService', () => { expect(logFn).toHaveBeenLastCalledWith({ eventId: 'test-event', status: 'failed', - error: error.toString(), + error, plugin: 'test', severityLevel: 'low', actor: {}, @@ -135,7 +135,7 @@ describe('DefaultAuditorService', () => { initiated: 'test', failed: 'test', }, - error: error.toString(), + error, plugin: 'test', severityLevel: 'low', actor: {}, diff --git a/packages/backend-defaults/src/entrypoints/auditor/DefaultAuditorService.ts b/packages/backend-defaults/src/entrypoints/auditor/DefaultAuditorService.ts index 79e206e305..b13565d841 100644 --- a/packages/backend-defaults/src/entrypoints/auditor/DefaultAuditorService.ts +++ b/packages/backend-defaults/src/entrypoints/auditor/DefaultAuditorService.ts @@ -48,7 +48,7 @@ export type AuditorEventStatus = | { status: 'succeeded' } | { status: 'failed'; - error: string; + error: Error; }; /** @@ -195,7 +195,7 @@ export class DefaultAuditorService implements AuditorService { await this.log({ ...options, ...params, - error: String(params.error), + error: params.error, meta: { ...options.meta, ...params?.meta }, status: 'failed', }); diff --git a/packages/backend-defaults/src/entrypoints/auditor/WinstonRootAuditorService.test.ts b/packages/backend-defaults/src/entrypoints/auditor/WinstonRootAuditorService.test.ts index 2d9a959c64..d8ffda2473 100644 --- a/packages/backend-defaults/src/entrypoints/auditor/WinstonRootAuditorService.test.ts +++ b/packages/backend-defaults/src/entrypoints/auditor/WinstonRootAuditorService.test.ts @@ -32,6 +32,7 @@ describe('WinstonRootAuditorService', () => { plugin: { getId: () => 'test-plugin', }, + config: mockServices.rootConfig.mock(), }); expect(childLogger).toBeInstanceOf(DefaultAuditorService); }); @@ -45,6 +46,7 @@ describe('WinstonRootAuditorService', () => { plugin: { getId: () => pluginId, }, + config: mockServices.rootConfig.mock(), }); // workaround to spy on private method const auditorSpy = jest.spyOn(auditor as any, 'log'); @@ -68,6 +70,7 @@ describe('WinstonRootAuditorService', () => { plugin: { getId: () => pluginId, }, + config: mockServices.rootConfig.mock(), }); // workaround to spy on private method const auditorSpy = jest.spyOn(auditor as any, 'log'); @@ -95,6 +98,7 @@ describe('WinstonRootAuditorService', () => { plugin: { getId: () => pluginId, }, + config: mockServices.rootConfig.mock(), }); // workaround to spy on private method const auditorSpy = jest.spyOn(auditor as any, 'log'); @@ -111,7 +115,7 @@ describe('WinstonRootAuditorService', () => { eventId: 'test-event', meta: {}, status: 'failed', - error: error.toString(), + error, }); }); @@ -124,6 +128,7 @@ describe('WinstonRootAuditorService', () => { plugin: { getId: () => pluginId, }, + config: mockServices.rootConfig.mock(), }); // workaround to spy on private method const auditorSpy = jest.spyOn(auditor as any, 'log'); @@ -163,7 +168,7 @@ describe('WinstonRootAuditorService', () => { initiated: 'test', failed: 'test', }, - error: error.toString(), + error, }); }); }); diff --git a/packages/backend-defaults/src/entrypoints/auditor/WinstonRootAuditorService.ts b/packages/backend-defaults/src/entrypoints/auditor/WinstonRootAuditorService.ts index 6ea5d02606..67a28cf74b 100644 --- a/packages/backend-defaults/src/entrypoints/auditor/WinstonRootAuditorService.ts +++ b/packages/backend-defaults/src/entrypoints/auditor/WinstonRootAuditorService.ts @@ -20,11 +20,13 @@ import type { HttpAuthService, PluginMetadataService, } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; import type { JsonObject } from '@backstage/types'; import type { Format } from 'logform'; import * as winston from 'winston'; import { WinstonLogger } from '../rootLogger'; import { DefaultAuditorService } from './DefaultAuditorService'; +import { getSeverityLogLevelMappings } from './utils'; /** @public */ export const defaultFormatter = winston.format.combine( @@ -108,12 +110,28 @@ export class WinstonRootAuditorService { forPlugin(deps: { auth: AuthService; + config: Config; httpAuth: HttpAuthService; plugin: PluginMetadataService; }): AuditorService { - return DefaultAuditorService.create( - e => this.winstonLogger.info(`${e.plugin}.${e.eventId}`, e), - deps, - ); + const severityLogLevelMappings = getSeverityLogLevelMappings(deps.config); + + return DefaultAuditorService.create(event => { + if ('error' in event) { + const { error, ...rest } = event; + const childAuditLogger = this.winstonLogger.child(rest); + + childAuditLogger[severityLogLevelMappings[event.severityLevel]]( + `${event.plugin}.${event.eventId}`, + error, + ); + } else { + // the else statement is required for typechecking + this.winstonLogger[severityLogLevelMappings[event.severityLevel]]( + `${event.plugin}.${event.eventId}`, + event, + ); + } + }, deps); } } diff --git a/packages/backend-defaults/src/entrypoints/auditor/auditorServiceFactory.ts b/packages/backend-defaults/src/entrypoints/auditor/auditorServiceFactory.ts index 34ea627762..3c82f95ce6 100644 --- a/packages/backend-defaults/src/entrypoints/auditor/auditorServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/auditor/auditorServiceFactory.ts @@ -19,15 +19,7 @@ import { createServiceFactory, } from '@backstage/backend-plugin-api'; import { DefaultAuditorService } from './DefaultAuditorService'; -import { z } from 'zod'; -import { InputError } from '@backstage/errors'; - -const CONFIG_ROOT_KEY = 'backend.auditor'; - -const severityLogLevelMappingsSchema = z.record( - z.enum(['low', 'medium', 'high', 'critical']), - z.enum(['debug', 'info', 'warn', 'error']), -); +import { getSeverityLogLevelMappings } from './utils'; /** * Plugin-level auditing. @@ -49,47 +41,26 @@ export const auditorServiceFactory = createServiceFactory({ }, factory({ config, logger, plugin, auth, httpAuth }) { const auditLogger = logger.child({ isAuditEvent: true }); - const auditorConfig = config.getOptionalConfig(CONFIG_ROOT_KEY); - const severityLogLevelMappings = { - low: - auditorConfig?.getOptionalString('severityLogLevelMappings.low') ?? - 'debug', - medium: - auditorConfig?.getOptionalString('severityLogLevelMappings.medium') ?? - 'info', - high: - auditorConfig?.getOptionalString('severityLogLevelMappings.high') ?? - 'info', - critical: - auditorConfig?.getOptionalString('severityLogLevelMappings.critical') ?? - 'info', - } as Required>; - - const res = severityLogLevelMappingsSchema.safeParse( - severityLogLevelMappings, - ); - if (!res.success) { - const key = res.error.issues.at(0)?.path.at(0) as string; - const value = ( - res.error.issues.at(0) as unknown as Record - ).received as string; - const validKeys = ( - res.error.issues.at(0) as unknown as Record - ).options as string[]; - throw new InputError( - `The configuration value for 'backend.auditor.severityLogLevelMappings.${key}' was given an invalid value: '${value}'. Expected one of the following valid values: '${validKeys.join( - ', ', - )}'.`, - ); - } + const severityLogLevelMappings = getSeverityLogLevelMappings(config); return DefaultAuditorService.create( event => { - auditLogger[severityLogLevelMappings[event.severityLevel]]( - `${event.plugin}.${event.eventId}`, - event, - ); + if ('error' in event) { + const { error, ...rest } = event; + const childAuditLogger = auditLogger.child(rest); + + childAuditLogger[severityLogLevelMappings[event.severityLevel]]( + `${event.plugin}.${event.eventId}`, + error, + ); + } else { + // the else statement is required for typechecking + auditLogger[severityLogLevelMappings[event.severityLevel]]( + `${event.plugin}.${event.eventId}`, + event, + ); + } }, { plugin, auth, httpAuth }, ); diff --git a/canon-docs/src/components/Columns/Columns.tsx b/packages/backend-defaults/src/entrypoints/auditor/types.ts similarity index 65% rename from canon-docs/src/components/Columns/Columns.tsx rename to packages/backend-defaults/src/entrypoints/auditor/types.ts index ec7f83ea75..7827a31d1f 100644 --- a/canon-docs/src/components/Columns/Columns.tsx +++ b/packages/backend-defaults/src/entrypoints/auditor/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,18 +14,13 @@ * limitations under the License. */ -import { ReactNode, CSSProperties } from 'react'; +import { z } from 'zod'; -export const Columns = ({ - children, - style, -}: { - children: ReactNode; - style?: CSSProperties; -}) => { - return ( -
    - {children} -
    - ); -}; +/** @internal */ +export const severityLogLevelMappingsSchema = z.record( + z.enum(['low', 'medium', 'high', 'critical']), + z.enum(['debug', 'info', 'warn', 'error']), +); + +/** @internal */ +export const CONFIG_ROOT_KEY = 'backend.auditor'; diff --git a/packages/backend-defaults/src/entrypoints/auditor/utils.ts b/packages/backend-defaults/src/entrypoints/auditor/utils.ts new file mode 100644 index 0000000000..719a227bf9 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/auditor/utils.ts @@ -0,0 +1,66 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Config } from '@backstage/config'; +import { InputError } from '@backstage/errors'; +import { z } from 'zod'; +import { CONFIG_ROOT_KEY, severityLogLevelMappingsSchema } from './types'; + +/** + * Gets the `backend.auditor.severityLogLevelMappings` configuration. + * + * @param config - The root Backstage {@link @backstage/config#Config} object. + * @returns The validated severity-to-log-level mappings. + * @throws error - {@link @backstage/errors#InputError} if the mapping configuration is invalid. + */ +export function getSeverityLogLevelMappings(config: Config) { + const auditorConfig = config.getOptionalConfig(CONFIG_ROOT_KEY); + + const severityLogLevelMappings = { + low: + auditorConfig?.getOptionalString('severityLogLevelMappings.low') ?? + 'debug', + medium: + auditorConfig?.getOptionalString('severityLogLevelMappings.medium') ?? + 'info', + high: + auditorConfig?.getOptionalString('severityLogLevelMappings.high') ?? + 'info', + critical: + auditorConfig?.getOptionalString('severityLogLevelMappings.critical') ?? + 'info', + } as Required>; + + const res = severityLogLevelMappingsSchema.safeParse( + severityLogLevelMappings, + ); + if (!res.success) { + const key = res.error.issues.at(0)?.path.at(0) as string; + const value = ( + res.error.issues.at(0) as unknown as Record + ).received as string; + const validKeys = ( + res.error.issues.at(0) as unknown as Record + ).options as string[]; + throw new InputError( + `The configuration value for 'backend.auditor.severityLogLevelMappings.${key}' was given an invalid value: '${value}'. Expected one of the following valid values: '${validKeys.join( + ', ', + )}'.`, + ); + } + + return severityLogLevelMappings; +} diff --git a/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts b/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts index 8454aa3ecc..fdc6e26c3a 100644 --- a/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts +++ b/packages/backend-defaults/src/entrypoints/auth/helpers.test.ts @@ -42,6 +42,23 @@ describe('credentials', () => { }, }); + expect( + createCredentialsWithUserPrincipal( + 'user:default/mock', + 'my-token', + undefined, + 'my-actor', + ), + ).toEqual({ + $$type: '@backstage/BackstageCredentials', + version: 'v1', + principal: { + type: 'user', + userEntityRef: 'user:default/mock', + actor: { type: 'service', subject: 'my-actor' }, + }, + }); + expect(createCredentialsWithNonePrincipal()).toEqual({ $$type: '@backstage/BackstageCredentials', version: 'v1', @@ -64,4 +81,63 @@ describe('credentials', () => { ), ).not.toMatch(/my-token/); }); + + it('should have a serializable form both as strings and as JSON', () => { + const simpleService = createCredentialsWithServicePrincipal('my-service'); + expect(String(simpleService)).toMatchInlineSnapshot( + `"backstageCredentials{servicePrincipal{my-service}}"`, + ); + expect(JSON.stringify(simpleService)).toMatchInlineSnapshot( + `"{"$$type":"@backstage/BackstageCredentials","version":"v1","principal":{"type":"service","subject":"my-service"}}"`, + ); + + const serviceWithAccessRestrictions = createCredentialsWithServicePrincipal( + 'my-service', + undefined, + { + permissionNames: ['perm'], + permissionAttributes: { + action: ['read'], + }, + }, + ); + expect(String(serviceWithAccessRestrictions)).toMatchInlineSnapshot( + `"backstageCredentials{servicePrincipal{my-service,accessRestrictions=cXWOJgUirHkHNZIowUi/YO5nwEwhTicC38iXi2XTYCk}}"`, + ); + expect(JSON.stringify(serviceWithAccessRestrictions)).toMatchInlineSnapshot( + `"{"$$type":"@backstage/BackstageCredentials","version":"v1","principal":{"type":"service","subject":"my-service","accessRestrictions":{"permissionNames":["perm"],"permissionAttributes":{"action":["read"]}}}}"`, + ); + + const simpleUser = createCredentialsWithUserPrincipal( + 'user:default/mock', + 'my-token', + ); + expect(String(simpleUser)).toMatchInlineSnapshot( + `"backstageCredentials{userPrincipal{user:default/mock}}"`, + ); + expect(JSON.stringify(simpleUser)).toMatchInlineSnapshot( + `"{"$$type":"@backstage/BackstageCredentials","version":"v1","principal":{"type":"user","userEntityRef":"user:default/mock"}}"`, + ); + + const userWithActor = createCredentialsWithUserPrincipal( + 'user:default/mock', + 'my-token', + undefined, + 'my-actor', + ); + expect(String(userWithActor)).toMatchInlineSnapshot( + `"backstageCredentials{userPrincipal{user:default/mock,actor={servicePrincipal{my-actor}}}}"`, + ); + expect(JSON.stringify(userWithActor)).toMatchInlineSnapshot( + `"{"$$type":"@backstage/BackstageCredentials","version":"v1","principal":{"type":"user","userEntityRef":"user:default/mock","actor":{"type":"service","subject":"my-actor"}}}"`, + ); + + const none = createCredentialsWithNonePrincipal(); + expect(String(none)).toMatchInlineSnapshot( + `"backstageCredentials{nonePrincipal}"`, + ); + expect(JSON.stringify(none)).toMatchInlineSnapshot( + `"{"$$type":"@backstage/BackstageCredentials","version":"v1","principal":{"type":"none"}}"`, + ); + }); }); diff --git a/packages/backend-defaults/src/entrypoints/auth/helpers.ts b/packages/backend-defaults/src/entrypoints/auth/helpers.ts index 58e2bcff74..02840112a9 100644 --- a/packages/backend-defaults/src/entrypoints/auth/helpers.ts +++ b/packages/backend-defaults/src/entrypoints/auth/helpers.ts @@ -22,29 +22,34 @@ import { BackstageUserPrincipal, } from '@backstage/backend-plugin-api'; import { InternalBackstageCredentials } from './types'; +import { createHash } from 'crypto'; export function createCredentialsWithServicePrincipal( sub: string, token?: string, accessRestrictions?: BackstagePrincipalAccessRestrictions, ): InternalBackstageCredentials { - return Object.defineProperty( - { - $$type: '@backstage/BackstageCredentials', - version: 'v1', - principal: { - type: 'service', - subject: sub, - accessRestrictions, - }, - }, - 'token', - { + const principal = createServicePrincipal(sub, accessRestrictions); + const result = { + $$type: '@backstage/BackstageCredentials', + version: 'v1', + principal, + } as const; + Object.defineProperties(result, { + token: { enumerable: false, configurable: true, + writable: true, value: token, }, - ); + toString: { + enumerable: false, + configurable: true, + writable: true, + value: () => `backstageCredentials{${principal}}`, + }, + }); + return result; } export function createCredentialsWithUserPrincipal( @@ -53,36 +58,49 @@ export function createCredentialsWithUserPrincipal( expiresAt?: Date, actor?: string, ): InternalBackstageCredentials { - return Object.defineProperty( - { - $$type: '@backstage/BackstageCredentials', - version: 'v1', - expiresAt, - principal: { - type: 'user', - userEntityRef: sub, - ...(actor && { - actor: { type: 'service', subject: actor }, - }), - }, - }, - 'token', - { + const principal = createUserPrincipal( + sub, + actor ? createServicePrincipal(actor) : undefined, + ); + const result = { + $$type: '@backstage/BackstageCredentials', + version: 'v1', + expiresAt, + principal, + } as const; + Object.defineProperties(result, { + token: { enumerable: false, configurable: true, + writable: true, value: token, }, - ); + toString: { + enumerable: false, + configurable: true, + writable: true, + value: () => `backstageCredentials{${principal}}`, + }, + }); + return result; } export function createCredentialsWithNonePrincipal(): InternalBackstageCredentials { - return { + const principal = createNonePrincipal(); + const result = { $$type: '@backstage/BackstageCredentials', version: 'v1', - principal: { - type: 'none', + principal, + } as const; + Object.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + writable: true, + value: () => `backstageCredentials{${principal}}`, }, - }; + }); + return result; } export function toInternalBackstageCredentials( @@ -106,3 +124,74 @@ export function toInternalBackstageCredentials( return internalCredentials; } + +function createServicePrincipal( + sub: string, + accessRestrictions?: BackstagePrincipalAccessRestrictions, +): BackstageServicePrincipal { + const result = { + type: 'service', + subject: sub, + accessRestrictions, + } as const; + Object.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + writable: true, + value: () => { + let parts = sub; + if (accessRestrictions) { + const hash = createHash('sha256') + .update(JSON.stringify(accessRestrictions)) + .digest('base64') + .replace(/=+$/, ''); + parts += `,accessRestrictions=${hash}`; + } + return `servicePrincipal{${parts}}`; + }, + }, + }); + return result; +} + +function createUserPrincipal( + userEntityRef: string, + actor?: BackstageServicePrincipal, +): BackstageUserPrincipal { + const result = { + type: 'user', + userEntityRef, + actor, + } as const; + Object.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + writable: true, + value: () => { + let parts = userEntityRef; + if (actor) { + parts += `,actor={${actor}}`; + } + return `userPrincipal{${parts}}`; + }, + }, + }); + return result; +} + +function createNonePrincipal(): BackstageNonePrincipal { + const result = { + type: 'none', + } as const; + Object.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + writable: true, + value: () => 'nonePrincipal', + }, + }); + return result; +} diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/createLifecycleMiddleware.test.ts b/packages/backend-defaults/src/entrypoints/httpRouter/createLifecycleMiddleware.test.ts deleted file mode 100644 index 31eecf14bb..0000000000 --- a/packages/backend-defaults/src/entrypoints/httpRouter/createLifecycleMiddleware.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createLifecycleMiddleware } from './createLifecycleMiddleware'; -import { BackendLifecycleImpl } from '../rootLifecycle/rootLifecycleServiceFactory'; -import { mockServices } from '@backstage/backend-test-utils'; -import { ServiceUnavailableError } from '@backstage/errors'; - -describe('createLifecycleMiddleware', () => { - it('should pause requests when plugin is not ready', async () => { - const lifecycle = new BackendLifecycleImpl(mockServices.rootLogger()); - - const middleware = createLifecycleMiddleware({ lifecycle }); - - const next = jest.fn(); - middleware({} as any, {} as any, next); - expect(next).not.toHaveBeenCalled(); - await lifecycle.startup(); - - // pending call - expect(next).toHaveBeenCalledWith(); - - // new call - const next2 = jest.fn(); - middleware({} as any, {} as any, next2); - expect(next2).toHaveBeenCalledWith(); - }); - - it('should throw ServiceUnavailableError after shutdown', async () => { - const lifecycle = new BackendLifecycleImpl(mockServices.rootLogger()); - const middleware = createLifecycleMiddleware({ lifecycle }); - - const next = jest.fn(); - middleware({} as any, {} as any, next); - expect(next).not.toHaveBeenCalled(); - await lifecycle.shutdown(); - - // pending call - expect(next).toHaveBeenCalledWith( - new ServiceUnavailableError('Service is shutting down'), - ); - - // new call - const next2 = jest.fn(); - middleware({} as any, {} as any, next2); - expect(next2).toHaveBeenCalledWith( - new ServiceUnavailableError('Service is shutting down'), - ); - }); - - it('should throw ServiceUnavailableError after timeout', async () => { - const lifecycle = new BackendLifecycleImpl(mockServices.rootLogger()); - const middleware = createLifecycleMiddleware({ - lifecycle, - startupRequestPauseTimeout: { milliseconds: 1 }, - }); - - const next = jest.fn(); - middleware({} as any, {} as any, next); - expect(next).not.toHaveBeenCalled(); - - await new Promise(r => setTimeout(r, 2)); - - expect(next).toHaveBeenCalledWith( - new ServiceUnavailableError('Service has not started up yet'), - ); - }); -}); diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/createLifecycleMiddleware.ts b/packages/backend-defaults/src/entrypoints/httpRouter/createLifecycleMiddleware.ts deleted file mode 100644 index 99fb82a41e..0000000000 --- a/packages/backend-defaults/src/entrypoints/httpRouter/createLifecycleMiddleware.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { RootLifecycleService } from '@backstage/backend-plugin-api'; -import { ServiceUnavailableError } from '@backstage/errors'; -import { HumanDuration, durationToMilliseconds } from '@backstage/types'; -import { RequestHandler } from 'express'; - -export const DEFAULT_STARTUP_REQUEST_PAUSE_TIMEOUT = { seconds: 5 }; -export const DEFAULT_SERVER_SHUTDOWN_TIMEOUT = { seconds: 0 }; - -/** - * Options for {@link createLifecycleMiddleware}. - * @public - */ -export interface LifecycleMiddlewareOptions { - lifecycle: RootLifecycleService; - /** - * The maximum time that paused requests will wait for the service to start, before returning an error. - * - * Defaults to 5 seconds. - */ - startupRequestPauseTimeout?: HumanDuration; -} - -/** - * Creates a middleware that pauses requests until the service has started. - * - * @remarks - * - * Requests that arrive before the service has started will be paused until startup is complete. - * If the service does not start within the provided timeout, the request will be rejected with a - * {@link @backstage/errors#ServiceUnavailableError}. - * - * If the service is shutting down, all requests will be rejected with a - * {@link @backstage/errors#ServiceUnavailableError}. - * - * @public - */ -export function createLifecycleMiddleware( - options: LifecycleMiddlewareOptions, -): RequestHandler { - const { lifecycle, startupRequestPauseTimeout } = options; - - let state: 'init' | 'up' | 'down' = 'init'; - const waiting = new Set<{ - next: (err?: Error) => void; - timeout: NodeJS.Timeout; - }>(); - - lifecycle.addStartupHook(async () => { - if (state === 'init') { - state = 'up'; - for (const item of waiting) { - clearTimeout(item.timeout); - item.next(); - } - waiting.clear(); - } - }); - - lifecycle.addShutdownHook(async () => { - state = 'down'; - for (const item of waiting) { - clearTimeout(item.timeout); - item.next(new ServiceUnavailableError('Service is shutting down')); - } - waiting.clear(); - }); - - return (_req, _res, next) => { - if (state === 'up') { - next(); - return; - } else if (state === 'down') { - next(new ServiceUnavailableError('Service is shutting down')); - return; - } - - const timeoutMs = durationToMilliseconds( - startupRequestPauseTimeout ?? DEFAULT_STARTUP_REQUEST_PAUSE_TIMEOUT, - ); - - const item = { - next, - timeout: setTimeout(() => { - if (waiting.delete(item)) { - next(new ServiceUnavailableError('Service has not started up yet')); - } - }, timeoutMs), - }; - - waiting.add(item); - }; -} diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/http/createRateLimitMiddleware.ts b/packages/backend-defaults/src/entrypoints/httpRouter/http/createRateLimitMiddleware.ts new file mode 100644 index 0000000000..b139b212d2 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/httpRouter/http/createRateLimitMiddleware.ts @@ -0,0 +1,40 @@ +/* + * 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 { NextFunction, Request, Response } from 'express'; +import { RateLimitStoreFactory } from '../../../lib/RateLimitStoreFactory.ts'; +import { Config } from '@backstage/config'; +import { rateLimitMiddleware } from '../../../lib/rateLimitMiddleware.ts'; + +export const createRateLimitMiddleware = (options: { + pluginId: string; + config: Config; +}) => { + const { pluginId, config } = options; + const configKey = `backend.rateLimit.plugin.${pluginId}`; + const enabled = config.has(configKey); + if (!enabled) { + return (_req: Request, _res: Response, next: NextFunction) => { + next(); + }; + } + + const rateLimitOptions = config.getConfig(configKey); + + return rateLimitMiddleware({ + store: RateLimitStoreFactory.create({ config, prefix: pluginId }), + config: rateLimitOptions, + }); +}; diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.ts b/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.ts index 1f2b989018..0fc70a07f0 100644 --- a/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.ts @@ -22,12 +22,13 @@ import { HttpRouterServiceAuthPolicy, } from '@backstage/backend-plugin-api'; import { - createLifecycleMiddleware, + createAuthIntegrationRouter, createCookieAuthRefreshMiddleware, createCredentialsBarrier, - createAuthIntegrationRouter, + createLifecycleMiddleware, } from './http'; import { MiddlewareFactory } from '../rootHttpRouter'; +import { createRateLimitMiddleware } from './http/createRateLimitMiddleware.ts'; /** * HTTP route registration for plugins. @@ -61,6 +62,8 @@ export const httpRouterServiceFactory = createServiceFactory({ }) { const router = PromiseRouter(); + router.use(createRateLimitMiddleware({ pluginId: plugin.getId(), config })); + rootHttpRouter.use(`/api/${plugin.getId()}`, router); const credentialsBarrier = createCredentialsBarrier({ diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts index 1ff9bd15d1..80a10fe0eb 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts @@ -15,15 +15,15 @@ */ import { - RootConfigService, LoggerService, + RootConfigService, } from '@backstage/backend-plugin-api'; import { - Request, - Response, ErrorRequestHandler, NextFunction, + Request, RequestHandler, + Response, } from 'express'; import cors from 'cors'; import helmet from 'helmet'; @@ -37,12 +37,14 @@ import { InputError, NotAllowedError, NotFoundError, + NotImplementedError, NotModifiedError, - ServiceUnavailableError, serializeError, + ServiceUnavailableError, } from '@backstage/errors'; -import { NotImplementedError } from '@backstage/errors'; import { applyInternalErrorFilter } from './applyInternalErrorFilter'; +import { RateLimitStoreFactory } from '../../../lib/RateLimitStoreFactory.ts'; +import { rateLimitMiddleware } from '../../../lib/rateLimitMiddleware.ts'; type LogMeta = { date: string; @@ -227,6 +229,47 @@ export class MiddlewareFactory { return cors(readCorsOptions(this.#config.getOptionalConfig('backend'))); } + /** + * Returns a middleware that implements rate limiting. + * + * @remarks + * + * Rate limiting is a common technique to prevent abuse of APIs. This middleware is + * configured using the config key `backend.rateLimit`. + * + * @returns An Express request handler + */ + rateLimit(): RequestHandler { + const enabled = this.#config.has('backend.rateLimit'); + if (!enabled) { + return (_req: Request, _res: Response, next: NextFunction) => { + next(); + }; + } + + const useDefaults = this.#config.getOptional('backend.rateLimit') === true; + const rateLimitOptions = useDefaults + ? undefined + : this.#config.getOptionalConfig('backend.rateLimit'); + + // Global rate limiting disabled + if ( + rateLimitOptions && + rateLimitOptions.getOptionalBoolean('global') === false + ) { + return (_req: Request, _res: Response, next: NextFunction) => { + next(); + }; + } + + return rateLimitMiddleware({ + store: useDefaults + ? undefined + : RateLimitStoreFactory.create({ config: this.#config }), + config: rateLimitOptions, + }); + } + /** * Express middleware to handle errors during request processing. * diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts index da7fcfe3f9..3b50515742 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts @@ -15,13 +15,13 @@ */ import { - RootConfigService, coreServices, createServiceFactory, LifecycleService, LoggerService, + RootConfigService, } from '@backstage/backend-plugin-api'; -import express, { RequestHandler, Express } from 'express'; +import express, { Express, RequestHandler } from 'express'; import type { Server } from 'node:http'; import { createHttpServer, @@ -121,6 +121,7 @@ const rootHttpRouterServiceFactoryWithOptions = ( app.use(middleware.cors()); app.use(middleware.compression()); app.use(middleware.logging()); + app.use(middleware.rateLimit()); app.use(healthRouter); app.use(routes); app.use(middleware.notFound()); diff --git a/packages/backend-defaults/src/entrypoints/rootLogger/WinstonLogger.test.ts b/packages/backend-defaults/src/entrypoints/rootLogger/WinstonLogger.test.ts index 39293dcc55..7208c22d39 100644 --- a/packages/backend-defaults/src/entrypoints/rootLogger/WinstonLogger.test.ts +++ b/packages/backend-defaults/src/entrypoints/rootLogger/WinstonLogger.test.ts @@ -110,4 +110,12 @@ describe('WinstonLogger', () => { `={"foo":{"bar":{"baz":"qux"}}}`, ); }); + + it('should handle null and undefined values in redactions without crashing', () => { + const { add } = WinstonLogger.redacter(); + + expect(() => { + add([null as any, undefined as any, 'valid-secret']); + }).not.toThrow(); + }); }); diff --git a/packages/backend-defaults/src/entrypoints/rootLogger/WinstonLogger.ts b/packages/backend-defaults/src/entrypoints/rootLogger/WinstonLogger.ts index 545c9377b2..1b6b9db900 100644 --- a/packages/backend-defaults/src/entrypoints/rootLogger/WinstonLogger.ts +++ b/packages/backend-defaults/src/entrypoints/rootLogger/WinstonLogger.ts @@ -99,6 +99,10 @@ export class WinstonLogger implements RootLoggerService { add(newRedactions) { let added = 0; for (const redactionToTrim of newRedactions) { + // Skip null or undefined values + if (redactionToTrim === null || redactionToTrim === undefined) { + continue; + } // Trimming the string ensures that we don't accdentally get extra // newlines or other whitespace interfering with the redaction; this // can happen for example when using string literals in yaml diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.test.ts index 4e1c5a2d08..861b3ad7cb 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.test.ts @@ -923,6 +923,17 @@ describe('GithubUrlReader', () => { await expect(r5.files[0].content()).resolves.toEqual( Buffer.from('# Test\n'), ); + + const r6 = await reader.search( + `${baseUrl}/backstage/mock/tree/main/{M,m}kdocs.yml`, + ); + expect(r6.files.length).toBe(1); + expect(r6.files[0].url).toBe( + `${baseUrl}/backstage/mock/tree/main/mkdocs.yml`, + ); + await expect(r6.files[0].content()).resolves.toEqual( + Buffer.from('site_name: Test\n'), + ); } // eslint-disable-next-line jest/expect-expect diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.ts index 1dbade8477..fe0bd08fec 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.ts @@ -45,6 +45,7 @@ import { import { ReadTreeResponseFactory, ReaderFactory } from './types'; import { ReadUrlResponseFactory } from './ReadUrlResponseFactory'; import { parseLastModified } from './util'; +import isGlob from 'is-glob'; export type GhRepoResponse = RestEndpointMethodTypes['repos']['get']['response']['data']; @@ -186,7 +187,7 @@ export class GithubUrlReader implements UrlReaderService { const { filepath } = parseGitUrl(url); // If it's a direct URL we use readUrl instead - if (!filepath?.match(/[*?]/)) { + if (!isGlob(filepath)) { try { const data = await this.readUrl(url, options); diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts index b7ccf165ad..337325f6f8 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts @@ -108,9 +108,6 @@ describe('GitlabUrlReader', () => { config: createConfig(), response: expect.objectContaining({ url: 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch', - headers: expect.objectContaining({ - 'private-token': '', - }), }), }, { @@ -119,7 +116,7 @@ describe('GitlabUrlReader', () => { response: expect.objectContaining({ url: 'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch', headers: expect.objectContaining({ - 'private-token': '0123456789', + authorization: 'Bearer 0123456789', }), }), }, @@ -252,10 +249,10 @@ describe('GitlabUrlReader', () => { it('should return the file when using a user token', async () => { worker.use( rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => { - if (req.headers.get('private-token') !== 'gl-user-token') { + if (req.headers.get('authorization') !== 'Bearer gl-user-token') { return res( - ctx.status(403), - ctx.json({ message: 'Not Authorized' }), + ctx.status(401), + ctx.json({ message: '401 Unauthorized' }), ); } return res(ctx.status(200), ctx.json({ id: 12345 })); @@ -571,10 +568,10 @@ describe('GitlabUrlReader', () => { it('should return the file when using a user token', async () => { worker.use( rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => { - if (req.headers.get('private-token') !== 'gl-user-token') { + if (req.headers.get('authorization') !== 'Bearer gl-user-token') { return res( - ctx.status(403), - ctx.json({ message: 'Not Authorized' }), + ctx.status(401), + ctx.json({ message: '401 Unauthorized' }), ); } return res(ctx.status(200), ctx.json({ id: 12345 })); @@ -737,10 +734,10 @@ describe('GitlabUrlReader', () => { (_, res, ctx) => res(ctx.status(200), ctx.json({ id: 12345 })), ), rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => { - if (req.headers.get('private-token') !== 'gl-user-token') { + if (req.headers.get('authorization') !== 'Bearer gl-user-token') { return res( - ctx.status(403), - ctx.json({ message: 'Not Authorized' }), + ctx.status(401), + ctx.json({ message: '401 Unauthorized' }), ); } return res(ctx.status(200), ctx.json({ id: 12345 })); @@ -798,10 +795,10 @@ describe('GitlabUrlReader', () => { (_, res, ctx) => res(ctx.status(404)), ), rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => { - if (req.headers.get('private-token') !== 'gl-user-token') { + if (req.headers.get('authorization') !== 'Bearer gl-user-token') { return res( - ctx.status(403), - ctx.json({ message: 'Not Authorized' }), + ctx.status(401), + ctx.json({ message: '401 Unauthorized' }), ); } return res(ctx.status(200), ctx.json({ id: 12345 })); @@ -857,21 +854,19 @@ describe('GitlabUrlReader', () => { beforeEach(() => { worker.use( rest.get('*/api/v4/projects/group%2Fproject', (req, res, ctx) => { - // the private-token header must be included on API calls - if (req.headers.get('private-token') !== 'gl-dummy-token') { + if (req.headers.get('authorization') !== 'Bearer gl-dummy-token') { return res( - ctx.status(403), - ctx.json({ message: 'Not Authorized' }), + ctx.status(401), + ctx.json({ message: '401 Unauthorized' }), ); } return res(ctx.status(200), ctx.json({ id: 12345 })); }), rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => { - // the private-token header must be included on API calls - if (req.headers.get('private-token') !== 'gl-user-token') { + if (req.headers.get('authorization') !== 'Bearer gl-user-token') { return res( - ctx.status(403), - ctx.json({ message: 'Not Authorized' }), + ctx.status(401), + ctx.json({ message: '401 Unauthorized' }), ); } return res(ctx.status(200), ctx.json({ id: 12345 })); diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts index 3e5497a307..11746fef0d 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts @@ -340,10 +340,7 @@ export class GitlabUrlReader implements UrlReaderService { ); } // Default to the old behavior of assuming the url is for a file - return getGitLabFileFetchUrl(target, { - ...this.integration.config, - ...(token && { token }), - }); + return getGitLabFileFetchUrl(target, this.integration.config, token); } // convert urls of the form: @@ -398,6 +395,12 @@ export class GitlabUrlReader implements UrlReaderService { ); const data = await result.json(); if (!result.ok) { + if (result.status === 401) { + throw new Error( + 'GitLab Error: 401 - Unauthorized. The access token used is either expired, or does not have permission to read the project', + ); + } + throw new Error(`Gitlab error: ${data.error}, ${data.error_description}`); } return Number(data.id); diff --git a/packages/backend-defaults/src/lib/RateLimitStoreFactory.test.ts b/packages/backend-defaults/src/lib/RateLimitStoreFactory.test.ts new file mode 100644 index 0000000000..684c204cb8 --- /dev/null +++ b/packages/backend-defaults/src/lib/RateLimitStoreFactory.test.ts @@ -0,0 +1,68 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { mockServices } from '@backstage/backend-test-utils'; +import { RateLimitStoreFactory } from './RateLimitStoreFactory.ts'; +import { RedisStore } from 'rate-limit-redis'; + +jest.mock('@keyv/redis', () => { + const Actual = jest.requireActual('@keyv/redis'); + return { + ...Actual, + __esModule: true, + default: jest.fn(() => { + return { + getClient: jest.fn(() => ({ + sendCommand: jest.fn().mockReturnValue('mock'), + })), + }; + }), + }; +}); + +describe('CacheRateLimitStoreFactory', () => { + afterEach(jest.clearAllMocks); + + it('should return undefined store with auto configuration if redis is not available', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + rateLimit: { + store: undefined, + }, + }, + }, + }); + const store = RateLimitStoreFactory.create({ config }); + expect(store).toBeUndefined(); + }); + + it('should return redis store if configured explicitly', async () => { + const config = mockServices.rootConfig({ + data: { + backend: { + rateLimit: { + store: { + type: 'redis', + connection: 'redis://localhost:6379', + }, + }, + }, + }, + }); + const store = RateLimitStoreFactory.create({ config }); + expect(store).toBeInstanceOf(RedisStore); + }); +}); diff --git a/packages/backend-defaults/src/lib/RateLimitStoreFactory.ts b/packages/backend-defaults/src/lib/RateLimitStoreFactory.ts new file mode 100644 index 0000000000..9df6f03225 --- /dev/null +++ b/packages/backend-defaults/src/lib/RateLimitStoreFactory.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Config } from '@backstage/config'; +import type { Store } from 'express-rate-limit'; +import { RedisStore } from 'rate-limit-redis'; + +/** + * Creates a store for `express-rate-limit` based on the configuration. + * + * @internal + */ +export class RateLimitStoreFactory { + static create(options: { + config: Config; + prefix?: string; + }): Store | undefined { + const { config, prefix } = options; + const store = config.getOptionalConfig('backend.rateLimit.store'); + if (!store) { + return undefined; + } + const type = store.getString('type'); + switch (type) { + case 'redis': + return this.redis({ store, prefix }); + case 'memory': + default: + return undefined; + } + } + + private static redis(options: { store: Config; prefix?: string }): Store { + const { store, prefix } = options; + const connectionString = store.getString('connection'); + const KeyvRedis = require('@keyv/redis').default; + const keyv = new KeyvRedis(connectionString); + return new RedisStore({ + prefix, + sendCommand: async (...args: string[]) => { + const client = await keyv.getClient(); + return client.sendCommand(args); + }, + }); + } +} diff --git a/packages/backend-defaults/src/lib/rateLimitMiddleware.ts b/packages/backend-defaults/src/lib/rateLimitMiddleware.ts new file mode 100644 index 0000000000..57163b5908 --- /dev/null +++ b/packages/backend-defaults/src/lib/rateLimitMiddleware.ts @@ -0,0 +1,81 @@ +/* + * 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 { RequestHandler } from 'express'; +import { rateLimit, Store } from 'express-rate-limit'; +import { Config, readDurationFromConfig } from '@backstage/config'; +import { durationToMilliseconds } from '@backstage/types'; + +export const rateLimitMiddleware = (options: { + store?: Store; + config?: Config; +}): RequestHandler => { + const { store, config } = options; + let windowMs: number = 60000; + if (config && config.has('window')) { + const windowDuration = readDurationFromConfig(config, { + key: 'window', + }); + windowMs = durationToMilliseconds(windowDuration); + } + const limit = config?.getOptionalNumber('incomingRequestLimit'); + const ipAllowList = config?.getOptionalStringArray('ipAllowList') ?? [ + '127.0.0.1', + '0:0:0:0:0:0:0:1', + '::1', + ]; + const skipSuccessfulRequests = config?.getOptionalBoolean( + 'skipSuccessfulRequests', + ); + const skipFailedRequests = config?.getOptionalBoolean('skipFailedRequests'); + const passOnStoreError = config?.getOptionalBoolean('passOnStoreError'); + + return rateLimit({ + windowMs, + limit, + skipSuccessfulRequests, + message: { + error: { + name: 'Error', + message: `Too many requests, please try again later`, + }, + response: { + statusCode: 429, + }, + }, + statusCode: 429, + skipFailedRequests, + passOnStoreError: passOnStoreError, + keyGenerator(req, _res): string { + if (!req.ip) { + return req.socket.remoteAddress!; + } + return req.ip; + }, + skip: (req, _res) => { + return ( + Boolean(req.ip && ipAllowList.includes(req.ip)) || + Boolean( + req.socket.remoteAddress && + ipAllowList.includes(req.socket.remoteAddress), + ) + ); + }, + validate: { + trustProxy: false, + }, + store, + }); +}; diff --git a/packages/backend-dev-utils/catalog-info.yaml b/packages/backend-dev-utils/catalog-info.yaml index ecbcb4e0bb..c53d388a51 100644 --- a/packages/backend-dev-utils/catalog-info.yaml +++ b/packages/backend-dev-utils/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index 8ad2f1e0cf..07ef41bed8 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,259 @@ # @backstage/backend-dynamic-feature-service +## 0.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/backend-openapi-utils@0.6.0-next.0 + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/plugin-catalog-backend@3.0.1-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-events-backend@0.5.5-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.2 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.36-next.0 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-common@1.2.19 + +## 0.7.2 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- 3d61c36: Fix wrong imports which lead to module initialization failures when enabling dynamic plugins. +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0 + - @backstage/config-loader@1.10.2 + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/backend-defaults@0.11.1 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/backend-openapi-utils@0.5.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-app-node@0.1.35 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-events-backend@0.5.4 + - @backstage/plugin-events-node@0.4.13 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## 0.7.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## 0.7.2-next.1 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- 3d61c36: Fix wrong imports which lead to module initialization failures when enabling dynamic plugins. +- Updated dependencies + - @backstage/config-loader@1.10.2-next.0 + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/plugin-app-node@0.1.35-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-catalog-backend@3.0.0-next.1 + - @backstage/plugin-events-backend@0.5.4-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/backend-openapi-utils@0.5.5-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.7.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0-next.0 + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/plugin-events-backend@0.5.3 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/backend-openapi-utils@0.5.4 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.34 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-search-common@1.2.18 + +## 0.7.1 + +### Patch Changes + +- c83cd8b: Fixed some circular or otherwise unclear imports +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/plugin-catalog-backend@2.1.0 + - @backstage/plugin-events-backend@0.5.3 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/backend-openapi-utils@0.5.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/types@1.2.1 + - @backstage/plugin-app-node@0.1.34 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-search-common@1.2.18 + +## 0.7.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/plugin-catalog-backend@2.0.1-next.2 + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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-auth-node@0.6.4-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.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 + +- 10f693c: **BREAKING** Removed support for the legacy backend, please migrate to the new backend system + +### Patch Changes + +- 72d019d: Fixed various typos. + `FrontendRemoteResolver`'s misspelled `getAdditionaRemoteInfo` has been deprecated. Use the correct spelling `getAdditionalRemoteInfo` instead. +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.0 + - @backstage/backend-defaults@0.10.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config-loader@1.10.1 + - @backstage/backend-openapi-utils@0.5.3 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.33 + - @backstage/plugin-events-backend@0.5.2 + - @backstage/plugin-events-node@0.4.11 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## 0.7.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/plugin-catalog-backend@2.0.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-openapi-utils@0.5.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.33-next.2 + - @backstage/plugin-events-backend@0.5.2-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 0.7.0-next.2 ### Patch Changes diff --git a/packages/backend-dynamic-feature-service/catalog-info.yaml b/packages/backend-dynamic-feature-service/catalog-info.yaml index 1269a5c406..a5ed85a20a 100644 --- a/packages/backend-dynamic-feature-service/catalog-info.yaml +++ b/packages/backend-dynamic-feature-service/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-dynamic-feature-service/knip-report.md b/packages/backend-dynamic-feature-service/knip-report.md index 79f2435433..55e3fd302b 100644 --- a/packages/backend-dynamic-feature-service/knip-report.md +++ b/packages/backend-dynamic-feature-service/knip-report.md @@ -1,8 +1,17 @@ # Knip report -## Unused dependencies (1) +## Unused dependencies (10) -| Name | Location | Severity | -| :------------------------------ | :----------- | :------- | -| @backstage/plugin-search-common | package.json | error | +| Name | Location | Severity | +| :------------------------------------ | :----------- | :------- | +| @backstage/plugin-search-backend-node | package.json | error | +| @backstage/plugin-permission-common | package.json | error | +| @backstage/plugin-catalog-backend | package.json | error | +| @backstage/plugin-permission-node | package.json | error | +| @backstage/plugin-scaffolder-node | package.json | error | +| @backstage/plugin-events-backend | package.json | error | +| @backstage/plugin-search-common | package.json | error | +| @backstage/plugin-events-node | package.json | error | +| @backstage/plugin-auth-node | package.json | error | +| express-promise-router | package.json | error | diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 54ea6c8986..a31284613c 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-next.2", + "version": "0.7.3-next.0", "description": "Backstage dynamic feature service", "backstage": { "role": "node-library" diff --git a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/router.ts b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/router.ts index 9f1ef68a77..cf48bcfc95 100644 --- a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/router.ts +++ b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/router.ts @@ -18,7 +18,7 @@ // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** import { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage/backend-openapi-utils'; -import { EndpointMap } from './'; +import { EndpointMap } from './apis'; export const spec = { openapi: '3.0.3', diff --git a/packages/backend-dynamic-feature-service/src/server/frontendRemotesServer.ts b/packages/backend-dynamic-feature-service/src/server/frontendRemotesServer.ts index f1a245b1ac..a12804ba48 100644 --- a/packages/backend-dynamic-feature-service/src/server/frontendRemotesServer.ts +++ b/packages/backend-dynamic-feature-service/src/server/frontendRemotesServer.ts @@ -13,14 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { coreServices, createServiceFactory, createServiceRef, } from '@backstage/backend-plugin-api'; import { createRouter } from './router'; -import { dynamicPluginsServiceRef } from '@backstage/backend-dynamic-feature-service'; -import { spec } from '../schema/openapi'; +import { dynamicPluginsServiceRef } from '../manager/plugin-manager'; +import { spec } from '../schema/openapi/generated'; import { ManifestFileName } from '@module-federation/sdk'; import { RemoteInfo } from '../schema/openapi/generated/models'; import { JsonObject } from '@backstage/types'; diff --git a/packages/backend-dynamic-feature-service/src/server/router.ts b/packages/backend-dynamic-feature-service/src/server/router.ts index 61c97797e7..9e2fb59c59 100644 --- a/packages/backend-dynamic-feature-service/src/server/router.ts +++ b/packages/backend-dynamic-feature-service/src/server/router.ts @@ -19,8 +19,8 @@ import { RootConfigService, } from '@backstage/backend-plugin-api'; import express from 'express'; -import { createOpenApiRouter, spec } from '../schema/openapi'; -import { DynamicPluginProvider } from '@backstage/backend-dynamic-feature-service'; +import { createOpenApiRouter, spec } from '../schema/openapi/generated'; +import { DynamicPluginProvider } from '../manager/types'; import * as fs from 'fs'; import * as path from 'path'; import * as url from 'url'; diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md index f24aa49ae8..c849283be2 100644 --- a/packages/backend-openapi-utils/CHANGELOG.md +++ b/packages/backend-openapi-utils/CHANGELOG.md @@ -1,5 +1,81 @@ # @backstage/backend-openapi-utils +## 0.6.0-next.1 + +### Patch Changes + +- 3760352: Update `express-openapi-validator` to 5.5.8 to fix security vulnerability in transitive dependency `multer` + +## 0.6.0-next.0 + +### Minor Changes + +- c08cbc4: Move Scaffolder API to OpenAPI + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + +## 0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + +## 0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.5.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.5.3-next.1 ### Patch Changes diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index f9ea9294bb..fbb38f27e5 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-next.1", + "version": "0.6.0-next.1", "description": "OpenAPI typescript support.", "backstage": { "role": "node-library" @@ -53,7 +53,7 @@ "@types/express-serve-static-core": "^4.17.5", "ajv": "^8.16.0", "express": "^4.17.1", - "express-openapi-validator": "^5.0.4", + "express-openapi-validator": "^5.5.8", "express-promise-router": "^4.1.0", "get-port": "^5.1.1", "json-schema-to-ts": "^3.0.0", diff --git a/packages/backend-openapi-utils/report.api.md b/packages/backend-openapi-utils/report.api.md index 6606f88b4a..75bf53cdf1 100644 --- a/packages/backend-openapi-utils/report.api.md +++ b/packages/backend-openapi-utils/report.api.md @@ -274,7 +274,7 @@ type EndpointMap = Record< { query?: object; body?: object; - response?: object | void; + response?: object | string | void; path?: object; } >; diff --git a/packages/backend-openapi-utils/src/types/generated.ts b/packages/backend-openapi-utils/src/types/generated.ts index e4d5dada72..ffbabb6f92 100644 --- a/packages/backend-openapi-utils/src/types/generated.ts +++ b/packages/backend-openapi-utils/src/types/generated.ts @@ -21,7 +21,12 @@ import { PathTemplate, ValueOf } from './common'; */ export type EndpointMap = Record< string, - { query?: object; body?: object; response?: object | void; path?: object } + { + query?: object; + body?: object; + response?: object | string | void; + path?: object; + } >; // OpenAPI generator doesn't emit regular lowercase 'delete'. diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index 8314ec21b3..953d4c50c4 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,135 @@ # @backstage/backend-plugin-api +## 1.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.1 + +## 1.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/plugin-auth-node@0.6.5 + +## 1.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 1.4.0 + +### Minor Changes + +- 664c07a: Added `actionsRegistry` and `actions` experimental services to `/alpha` to allow registration of distributed actions from plugins, and the ability to invoke these actions. You can use these services by including them like the following: + + ```ts + import { + actionsRegistryServiceRef, + actionsServiceRef, + } from '@backstage/backend-plugin-api/alpha'; + + createBackendPlugin({ + pluginId: 'test-plugin', + register({ registerInit }) { + registerInit({ + deps: { + actions: actionsServiceRef, + actionsRegistry: actionsRegistryServiceRef, + }, + async init({ actions, actionsRegistry }) { + actionsRegistry.register({ + ..., + }); + + await actions.invoke(...); + }, + }); + }, + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @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 + +## 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 + +- acea1d4: update documentation +- 72d019d: Removed various typos +- d385854: Minor doc comment update +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/config@1.3.2 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 1.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @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-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + ## 1.3.1-next.1 ### Patch Changes diff --git a/packages/backend-plugin-api/catalog-info.yaml b/packages/backend-plugin-api/catalog-info.yaml index 35ca9850fa..28c7ea07b0 100644 --- a/packages/backend-plugin-api/catalog-info.yaml +++ b/packages/backend-plugin-api/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 906b6ebeb4..a67525cd62 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-next.1", + "version": "1.4.2-next.0", "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" @@ -20,7 +20,7 @@ "license": "Apache-2.0", "exports": { ".": "./src/index.ts", - "./alpha": "./src/alpha.ts", + "./alpha": "./src/alpha/index.ts", "./testUtils": "./src/testUtils.ts", "./package.json": "./package.json" }, @@ -29,7 +29,7 @@ "typesVersions": { "*": { "alpha": [ - "src/alpha.ts" + "src/alpha/index.ts" ], "testUtils": [ "src/testUtils.ts" @@ -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-alpha.api.md b/packages/backend-plugin-api/report-alpha.api.md index d485e2132e..9be7a29ed5 100644 --- a/packages/backend-plugin-api/report-alpha.api.md +++ b/packages/backend-plugin-api/report-alpha.api.md @@ -3,7 +3,105 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyZodObject } from 'zod'; +import { BackstageCredentials } from '@backstage/backend-plugin-api'; +import { JsonObject } from '@backstage/types'; +import { JSONSchema7 } from 'json-schema'; +import { JsonValue } from '@backstage/types'; +import { LoggerService } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; +import { z } from 'zod'; + +// @alpha (undocumented) +export type ActionsRegistryActionContext = { + input: z.infer; + logger: LoggerService; + credentials: BackstageCredentials; +}; + +// @alpha (undocumented) +export type ActionsRegistryActionOptions< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, +> = { + name: string; + title: string; + description: string; + schema: { + input: (zod: typeof z) => TInputSchema; + output: (zod: typeof z) => TOutputSchema; + }; + attributes?: { + destructive?: boolean; + idempotent?: boolean; + readOnly?: boolean; + }; + action: (context: ActionsRegistryActionContext) => Promise< + z.infer extends void + ? void + : { + output: z.infer; + } + >; +}; + +// @alpha (undocumented) +export interface ActionsRegistryService { + // (undocumented) + register< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, + >( + options: ActionsRegistryActionOptions, + ): void; +} + +// @alpha +export const actionsRegistryServiceRef: ServiceRef< + ActionsRegistryService, + 'plugin', + 'singleton' +>; + +// @alpha (undocumented) +export interface ActionsService { + // (undocumented) + invoke(opts: { + id: string; + input?: JsonObject; + credentials: BackstageCredentials; + }): Promise<{ + output: JsonValue; + }>; + // (undocumented) + list: (opts: { credentials: BackstageCredentials }) => Promise<{ + actions: ActionsServiceAction[]; + }>; +} + +// @alpha (undocumented) +export type ActionsServiceAction = { + id: string; + name: string; + title: string; + description: string; + schema: { + input: JSONSchema7; + output: JSONSchema7; + }; + attributes: { + readOnly: boolean; + destructive: boolean; + idempotent: boolean; + }; +}; + +// @alpha +export const actionsServiceRef: ServiceRef< + ActionsService, + 'plugin', + 'singleton' +>; // @alpha (undocumented) export type BackendFeatureMeta = @@ -23,7 +121,7 @@ export interface InstanceMetadataService { getInstalledFeatures: () => BackendFeatureMeta[]; } -// @alpha +// @alpha (undocumented) export const instanceMetadataServiceRef: ServiceRef< InstanceMetadataService, 'plugin', diff --git a/packages/backend-plugin-api/src/alpha/ActionsRegistryService.ts b/packages/backend-plugin-api/src/alpha/ActionsRegistryService.ts new file mode 100644 index 0000000000..9a7820e354 --- /dev/null +++ b/packages/backend-plugin-api/src/alpha/ActionsRegistryService.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 { z, AnyZodObject } from 'zod'; +import { + LoggerService, + BackstageCredentials, +} from '@backstage/backend-plugin-api'; + +/** + * @alpha + */ +export type ActionsRegistryActionContext = { + input: z.infer; + logger: LoggerService; + credentials: BackstageCredentials; +}; + +/** + * @alpha + */ +export type ActionsRegistryActionOptions< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, +> = { + name: string; + title: string; + description: string; + schema: { + input: (zod: typeof z) => TInputSchema; + output: (zod: typeof z) => TOutputSchema; + }; + attributes?: { + destructive?: boolean; + idempotent?: boolean; + readOnly?: boolean; + }; + action: ( + context: ActionsRegistryActionContext, + ) => Promise< + z.infer extends void + ? void + : { output: z.infer } + >; +}; + +/** + * @alpha + */ +export interface ActionsRegistryService { + register< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, + >( + options: ActionsRegistryActionOptions, + ): void; +} diff --git a/packages/backend-plugin-api/src/alpha/ActionsService.ts b/packages/backend-plugin-api/src/alpha/ActionsService.ts new file mode 100644 index 0000000000..528b6cea67 --- /dev/null +++ b/packages/backend-plugin-api/src/alpha/ActionsService.ts @@ -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 { JsonObject, JsonValue } from '@backstage/types'; +import { JSONSchema7 } from 'json-schema'; +import { BackstageCredentials } from '@backstage/backend-plugin-api'; + +/** + * @alpha + */ +export type ActionsServiceAction = { + id: string; + name: string; + title: string; + description: string; + schema: { + input: JSONSchema7; + output: JSONSchema7; + }; + attributes: { + readOnly: boolean; + destructive: boolean; + idempotent: boolean; + }; +}; + +/** + * @alpha + */ +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/InstanceMetadataService.ts b/packages/backend-plugin-api/src/alpha/InstanceMetadataService.ts similarity index 100% rename from packages/backend-plugin-api/src/services/definitions/InstanceMetadataService.ts rename to packages/backend-plugin-api/src/alpha/InstanceMetadataService.ts diff --git a/packages/backend-plugin-api/src/alpha.ts b/packages/backend-plugin-api/src/alpha/index.ts similarity index 60% rename from packages/backend-plugin-api/src/alpha.ts rename to packages/backend-plugin-api/src/alpha/index.ts index 6e91e10671..5bb69eb4e2 100644 --- a/packages/backend-plugin-api/src/alpha.ts +++ b/packages/backend-plugin-api/src/alpha/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. @@ -14,20 +14,21 @@ * limitations under the License. */ -import { createServiceRef } from '@backstage/backend-plugin-api'; - -/** - * EXPERIMENTAL: Instance metadata service. - * - * @alpha - */ -export const instanceMetadataServiceRef = createServiceRef< - import('./services/definitions/InstanceMetadataService').InstanceMetadataService ->({ - id: 'core.instanceMetadata', -}); - export type { BackendFeatureMeta, InstanceMetadataService, -} from './services/definitions/InstanceMetadataService'; +} from './InstanceMetadataService'; + +export type { + ActionsRegistryService, + ActionsRegistryActionOptions, + ActionsRegistryActionContext, +} from './ActionsRegistryService'; + +export type { ActionsService, ActionsServiceAction } from './ActionsService'; + +export { + actionsRegistryServiceRef, + actionsServiceRef, + instanceMetadataServiceRef, +} from './refs'; diff --git a/packages/backend-plugin-api/src/alpha/refs.ts b/packages/backend-plugin-api/src/alpha/refs.ts new file mode 100644 index 0000000000..81996679f7 --- /dev/null +++ b/packages/backend-plugin-api/src/alpha/refs.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createServiceRef } from '@backstage/backend-plugin-api'; + +/** + * @alpha + */ +export const instanceMetadataServiceRef = createServiceRef< + import('./InstanceMetadataService').InstanceMetadataService +>({ + id: 'core.instanceMetadata', +}); + +/** + * 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. + * + * @alpha + */ +export const actionsServiceRef = createServiceRef< + import('./ActionsService').ActionsService +>({ + id: 'alpha.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. + * + * @alpha + */ +export const actionsRegistryServiceRef = createServiceRef< + import('./ActionsRegistryService').ActionsRegistryService +>({ + id: 'alpha.core.actionsRegistry', +}); 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..6c0936f85c --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts @@ -0,0 +1,67 @@ +/* + * 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, AnyZodObject } 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 AnyZodObject, + TOutputSchema extends AnyZodObject, +> = { + name: string; + title: string; + description: string; + schema: { + input: (zod: typeof z) => TInputSchema; + output: (zod: typeof z) => TOutputSchema; + }; + attributes?: { + destructive?: boolean; + idempotent?: boolean; + readOnly?: boolean; + }; + action: ( + context: ActionsRegistryActionContext, + ) => Promise< + z.infer extends void + ? void + : { output: z.infer } + >; +}; + +/** + * @public + */ +export interface ActionsRegistryService { + register< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, + >( + 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..d595008a42 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/ActionsService.ts @@ -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 { 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; + }; + attributes: { + readOnly: boolean; + destructive: boolean; + idempotent: boolean; + }; +}; + +/** + * @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/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index d6346cbe4e..c811a513a8 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - export type { AuditorService, AuditorServiceCreateEventOptions, diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 5cf584fc9f..98360edf90 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,193 @@ # @backstage/backend-test-utils +## 1.7.1-next.0 + +### Patch Changes + +- 3a7dad9: Updated `better-sqlite3` to v12 +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/backend-app-api@1.2.6-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.1 + +## 1.7.0 + +### Minor Changes + +- ead925a: Add a standard `toString` on credentials objects + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/backend-defaults@0.11.1 + - @backstage/backend-app-api@1.2.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-events-node@0.4.13 + +## 1.7.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/backend-app-api@1.2.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 1.7.0-next.0 + +### Minor Changes + +- ead925a: Add a standard `toString` on credentials objects + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/backend-app-api@1.2.4 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + +## 1.6.0 + +### Minor Changes + +- 6dfb7be: Added `mockServices.permissions()` that can return actual results. +- c999c25: Added an `actionsRegistryServiceMock` and `actionsServiceMock` to `/alpha` export for the experimental services. + + This allows you to write tests for your actions by doing something similar to the following: + + ```ts + import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; + + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock({ + entities: [ + ... + ], + }); + + createGetCatalogEntityAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:get-catalog-entity', + input: { name: 'test' }, + }), + ).resolves.toEqual(...) + ``` + +### Patch Changes + +- 12c1fd4: Make the `user` credentials mock behave more like production +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-app-api@1.2.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-permission-common@0.9.0 + +## 1.6.0-next.2 + +### Patch Changes + +- 12c1fd4: Make the `user` credentials mock behave more like production +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/backend-app-api@1.2.4-next.2 + - @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-auth-node@0.6.4-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 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 + +- b3832d1: Add a functional `mockServices.events()` +- c6bc67d: Added Valkey support alongside Redis in backend-defaults cache clients, using the new Keyv Valkey package. Also extended backend-test-utils to support Valkey in tests. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0 + - @backstage/backend-app-api@1.2.3 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## 1.5.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-app-api@1.2.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 1.5.0-next.2 ### Patch Changes diff --git a/packages/backend-test-utils/catalog-info.yaml b/packages/backend-test-utils/catalog-info.yaml index 396f190ef4..94c889b089 100644 --- a/packages/backend-test-utils/catalog-info.yaml +++ b/packages/backend-test-utils/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index bcd3ef014c..b7f14a3de5 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-next.2", + "version": "1.7.1-next.0", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" @@ -21,7 +21,8 @@ "license": "Apache-2.0", "exports": { ".": "./src/index.ts", - "./package.json": "./package.json" + "./package.json": "./package.json", + "./alpha": "./src/alpha/index.ts" }, "main": "src/index.ts", "types": "src/index.ts", @@ -29,6 +30,9 @@ "*": { "package.json": [ "package.json" + ], + "alpha": [ + "src/alpha/index.ts" ] } }, @@ -52,6 +56,7 @@ "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-events-node": "workspace:^", + "@backstage/plugin-permission-common": "workspace:^", "@backstage/types": "workspace:^", "@keyv/memcache": "^2.0.1", "@keyv/redis": "^4.0.1", @@ -60,7 +65,7 @@ "@types/express-serve-static-core": "^4.17.5", "@types/keyv": "^4.2.0", "@types/qs": "^6.9.6", - "better-sqlite3": "^11.0.0", + "better-sqlite3": "^12.0.0", "cookie": "^0.7.0", "express": "^4.17.1", "fs-extra": "^11.0.0", @@ -72,7 +77,9 @@ "testcontainers": "^10.0.0", "textextensions": "^5.16.0", "uuid": "^11.0.0", - "yn": "^4.0.0" + "yn": "^4.0.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.20.4" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/packages/backend-test-utils/report-alpha.api.md b/packages/backend-test-utils/report-alpha.api.md new file mode 100644 index 0000000000..ccbf96c0c7 --- /dev/null +++ b/packages/backend-test-utils/report-alpha.api.md @@ -0,0 +1,85 @@ +## API Report File for "@backstage/backend-test-utils" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { ActionsRegistryActionOptions } from '@backstage/backend-plugin-api/alpha'; +import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; +import { ActionsService } from '@backstage/backend-plugin-api/alpha'; +import { ActionsServiceAction } from '@backstage/backend-plugin-api/alpha'; +import { AnyZodObject } from 'zod'; +import { BackstageCredentials } from '@backstage/backend-plugin-api'; +import { JsonObject } from '@backstage/types'; +import { JsonValue } from '@backstage/types'; +import { LoggerService } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; + +// @alpha (undocumented) +export function actionsRegistryServiceMock(options?: { + logger: LoggerService; +}): MockActionsRegistry; + +// @alpha (undocumented) +export namespace actionsRegistryServiceMock { + const // (undocumented) + factory: () => ServiceFactory< + ActionsRegistryService, + 'plugin', + 'singleton' + >; + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ServiceMock; +} + +// @alpha (undocumented) +export namespace actionsServiceMock { + const // (undocumented) + factory: () => ServiceFactory; + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ServiceMock; +} + +// @alpha +export class MockActionsRegistry + implements ActionsRegistryService, ActionsService +{ + // (undocumented) + readonly actions: Map>; + // (undocumented) + static create(opts: { logger: LoggerService }): MockActionsRegistry; + // (undocumented) + invoke(opts: { + id: string; + input?: JsonObject; + credentials?: BackstageCredentials; + }): Promise<{ + output: JsonValue; + }>; + // (undocumented) + list(): Promise<{ + actions: ActionsServiceAction[]; + }>; + // (undocumented) + register< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, + >(options: ActionsRegistryActionOptions): void; +} + +// @alpha (undocumented) +export type ServiceMock = { + factory: ServiceFactory; +} & { + [Key in keyof TService]: TService[Key] extends ( + ...args: infer Args + ) => infer Return + ? TService[Key] & jest.MockInstance + : TService[Key]; +}; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-test-utils/report.api.md b/packages/backend-test-utils/report.api.md index 7edcf6e1b7..4853ca57c5 100644 --- a/packages/backend-test-utils/report.api.md +++ b/packages/backend-test-utils/report.api.md @@ -4,6 +4,7 @@ ```ts import { AuditorService } from '@backstage/backend-plugin-api'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { AuthService } from '@backstage/backend-plugin-api'; import { Backend } from '@backstage/backend-app-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; @@ -275,14 +276,17 @@ export namespace mockServices { partialImpl?: Partial | undefined, ) => ServiceMock; } + export function permissions(options?: { + result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; + }): PermissionsService; // (undocumented) export namespace permissions { - const // (undocumented) - factory: () => ServiceFactory; - const // (undocumented) - mock: ( - partialImpl?: Partial | undefined, - ) => ServiceMock; + const factory: (options?: { + result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; + }) => ServiceFactory; + const mock: ( + partialImpl?: Partial | undefined, + ) => ServiceMock; } // (undocumented) export namespace permissionsRegistry { diff --git a/packages/backend-test-utils/src/next/index.ts b/packages/backend-test-utils/src/alpha/index.ts similarity index 96% rename from packages/backend-test-utils/src/next/index.ts rename to packages/backend-test-utils/src/alpha/index.ts index 572fb3a9e7..d891744260 100644 --- a/packages/backend-test-utils/src/next/index.ts +++ b/packages/backend-test-utils/src/alpha/index.ts @@ -14,5 +14,4 @@ * limitations under the License. */ -export * from './wiring'; export * from './services'; diff --git a/packages/backend-test-utils/src/alpha/services/ActionsRegistryServiceMock.ts b/packages/backend-test-utils/src/alpha/services/ActionsRegistryServiceMock.ts new file mode 100644 index 0000000000..faf89b393a --- /dev/null +++ b/packages/backend-test-utils/src/alpha/services/ActionsRegistryServiceMock.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { mockServices } from '../../services'; +import { LoggerService } from '@backstage/backend-plugin-api'; +import { MockActionsRegistry } from './MockActionsRegistry'; +import { simpleMock } from './simpleMock'; +import { + ActionsRegistryService, + actionsRegistryServiceRef, +} from '@backstage/backend-plugin-api/alpha'; +import { actionsRegistryServiceFactory } from '@backstage/backend-defaults/alpha'; + +/** + * @alpha + */ +export function actionsRegistryServiceMock(options?: { + logger: LoggerService; +}): MockActionsRegistry { + return MockActionsRegistry.create({ + logger: options?.logger ?? mockServices.logger.mock(), + }); +} + +/** + * @alpha + */ +export namespace actionsRegistryServiceMock { + export const factory = () => actionsRegistryServiceFactory; + + export const mock = simpleMock( + actionsRegistryServiceRef, + () => ({ + register: jest.fn(), + }), + ); +} diff --git a/packages/backend-test-utils/src/alpha/services/ActionsServiceMock.ts b/packages/backend-test-utils/src/alpha/services/ActionsServiceMock.ts new file mode 100644 index 0000000000..63abff4c37 --- /dev/null +++ b/packages/backend-test-utils/src/alpha/services/ActionsServiceMock.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { simpleMock } from './simpleMock'; +import { + ActionsService, + actionsServiceRef, +} from '@backstage/backend-plugin-api/alpha'; +import { actionsServiceFactory } from '@backstage/backend-defaults/alpha'; + +/** + * @alpha + */ +export namespace actionsServiceMock { + export const factory = () => actionsServiceFactory; + + export const mock = simpleMock(actionsServiceRef, () => ({ + invoke: jest.fn(), + list: jest.fn(), + })); +} diff --git a/packages/backend-test-utils/src/alpha/services/MockActionsRegistry.test.ts b/packages/backend-test-utils/src/alpha/services/MockActionsRegistry.test.ts new file mode 100644 index 0000000000..f604689c63 --- /dev/null +++ b/packages/backend-test-utils/src/alpha/services/MockActionsRegistry.test.ts @@ -0,0 +1,236 @@ +/* + * 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 { startTestBackend } from '../../wiring'; +import { actionsRegistryServiceMock } from './ActionsRegistryServiceMock'; +import { mockCredentials } from '../../services'; +import { Router } from 'express'; +import supertest from 'supertest'; +import { + actionsRegistryServiceRef, + actionsServiceRef, +} from '@backstage/backend-plugin-api/alpha'; + +describe('MockActionsRegistry', () => { + it('should be able to register and invoke actions', async () => { + const registry = actionsRegistryServiceMock(); + + registry.register({ + name: 'my-demo-action', + title: 'Test', + description: 'Test', + schema: { + input: z => + z.object({ + name: z.string(), + }), + output: z => + z.object({ + name: z.string(), + }), + }, + action: async ({ input }) => ({ output: { name: input.name } }), + }); + + const result = await registry.invoke({ + id: 'test:my-demo-action', + input: { name: 'test' }, + }); + + expect(result).toEqual({ output: { name: 'test' } }); + }); + + it('should throw an error when the input is invalid to the action', async () => { + const registry = actionsRegistryServiceMock(); + + registry.register({ + name: 'my-demo-action', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({ name: z.string() }), + output: z => z.object({ name: z.string() }), + }, + action: async ({ input }) => ({ output: { name: input.name } }), + }); + + await expect( + registry.invoke({ id: 'test:my-demo-action', input: { name: 1 } }), + ).rejects.toThrow('Invalid input to action "test:my-demo-action"'); + }); + + it('should throw an error when the action is not found', async () => { + const registry = actionsRegistryServiceMock(); + + await expect(registry.invoke({ id: 'test' })).rejects.toThrow( + 'Action "test" not found, available actions: none', + ); + }); + + it('should throw an error when the action is not found with recommended actions', async () => { + const registry = actionsRegistryServiceMock(); + + registry.register({ + name: 'my-demo-action', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({ name: z.string() }), + output: z => z.object({ name: z.string() }), + }, + action: async ({ input }) => ({ output: { name: input.name } }), + }); + + await expect(registry.invoke({ id: 'test' })).rejects.toThrow( + 'Action "test" not found, available actions: "test:my-demo-action"', + ); + }); + + it('should throw an error when the output is invalid', async () => { + const registry = actionsRegistryServiceMock(); + + registry.register({ + name: 'my-demo-action', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({ name: z.number() }), + output: z => z.object({ name: z.string() }), + }, + // @ts-expect-error - we want to test the error case + action: async ({ input }) => ({ output: { name: input.name } }), + }); + + await expect( + registry.invoke({ id: 'test:my-demo-action', input: { name: 1 } }), + ).rejects.toThrow('Invalid output from action "test:my-demo-action"'); + }); + + it('should list the actions correctly', async () => { + const registry = actionsRegistryServiceMock(); + + registry.register({ + name: 'my-demo-action', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({ name: z.string() }), + output: z => z.object({ name: z.string() }), + }, + action: async ({ input }) => ({ output: { name: input.name } }), + }); + + const result = await registry.list(); + + expect(result).toMatchObject({ + actions: [ + { + id: 'test:my-demo-action', + name: 'my-demo-action', + title: 'Test', + description: 'Test', + attributes: { + destructive: true, + idempotent: false, + readOnly: false, + }, + schema: { + input: { + type: 'object', + properties: { + name: { type: 'string' }, + }, + }, + output: { + type: 'object', + properties: { + name: { type: 'string' }, + }, + }, + }, + }, + ], + }); + }); + + describe('actionsRegistryServiceMock + mockService.actionsRegistry', () => { + it('should be able to register and invoke actions', async () => { + const pluginWithAction = createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { actionsRegistry: actionsRegistryServiceRef }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({ name: z.string() }), + output: z => z.object({ name: z.string() }), + }, + action: async ({ input }) => { + expect(input).toEqual({ name: 'test' }); + return { output: { name: input.name } }; + }, + }); + }, + }); + }, + }); + + const pluginToCallAction = createBackendPlugin({ + pluginId: 'my-plugin-to-call-action', + register(reg) { + reg.registerInit({ + deps: { + actions: actionsServiceRef, + router: coreServices.httpRouter, + }, + async init({ actions, router }) { + const testRouter = Router(); + router.use(testRouter); + + testRouter.post('/test', async (_, res) => { + const { output } = await actions.invoke({ + id: 'my-plugin:test', + input: { name: 'test' }, + credentials: mockCredentials.service(), + }); + + res.json(output); + }); + }, + }); + }, + }); + + const { server } = await startTestBackend({ + features: [pluginWithAction, pluginToCallAction], + }); + + const { body, status } = await supertest(server).post( + '/api/my-plugin-to-call-action/test', + ); + + expect(status).toBe(200); + expect(body).toEqual({ name: 'test' }); + }); + }); +}); diff --git a/packages/backend-test-utils/src/alpha/services/MockActionsRegistry.ts b/packages/backend-test-utils/src/alpha/services/MockActionsRegistry.ts new file mode 100644 index 0000000000..49c80b7a3d --- /dev/null +++ b/packages/backend-test-utils/src/alpha/services/MockActionsRegistry.ts @@ -0,0 +1,168 @@ +/* + * 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 { + BackstageCredentials, + LoggerService, +} from '@backstage/backend-plugin-api'; +import { ForwardedError, InputError, NotFoundError } from '@backstage/errors'; +import { JsonObject, JsonValue } from '@backstage/types'; +import { z, AnyZodObject } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; +import { mockCredentials } from '../../services'; +import { + ActionsRegistryActionOptions, + ActionsRegistryService, + ActionsService, + ActionsServiceAction, +} from '@backstage/backend-plugin-api/alpha'; + +/** + * A mock implementation of the ActionsRegistryService and ActionsService that can be used in tests. + * + * This is useful for testing actions that are registered with the ActionsRegistryService and ActionsService. + * + * The plugin ID is hardcoded to `testing` in the mock implementation. + * + * @example + * ```ts + * const actionsRegistry = mockServices.actionsRegistry(); + * + * actionsRegistry.register({ + * name: 'test', + * title: 'Test', + * description: 'Test', + * schema: { + * input: z.object({ name: z.string() }), + * output: z.object({ name: z.string() }), + * }, + * action: async ({ input }) => ({ output: { name: input.name } }), + * }); + * + * + * const result = await actionsRegistry.invoke({ + * id: 'testing:test', + * input: { name: 'test' }, + * }); + * + * expect(result).toEqual({ output: { name: 'test' } }); + * ``` + * + * @alpha + */ +export class MockActionsRegistry + implements ActionsRegistryService, ActionsService +{ + private constructor(private readonly logger: LoggerService) {} + + static create(opts: { logger: LoggerService }) { + return new MockActionsRegistry(opts.logger); + } + + readonly actions: Map> = + new Map(); + + async list(): Promise<{ actions: ActionsServiceAction[] }> { + return { + actions: Array.from(this.actions.entries()).map(([id, action]) => ({ + id, + name: action.name, + title: action.title, + description: action.description, + attributes: { + destructive: action.attributes?.destructive ?? true, + idempotent: action.attributes?.idempotent ?? false, + readOnly: action.attributes?.readOnly ?? false, + }, + schema: { + input: action.schema?.input + ? zodToJsonSchema(action.schema.input(z)) + : zodToJsonSchema(z.object({})), + output: action.schema?.output + ? zodToJsonSchema(action.schema.output(z)) + : zodToJsonSchema(z.object({})), + } as ActionsServiceAction['schema'], + })), + }; + } + + async invoke(opts: { + id: string; + input?: JsonObject; + credentials?: BackstageCredentials; + }): Promise<{ output: JsonValue }> { + const action = this.actions.get(opts.id); + + if (!action) { + const availableActionIds = Array.from(this.actions.keys()).join(', '); + throw new NotFoundError( + `Action "${opts.id}" not found, available actions: ${ + availableActionIds ? `"${availableActionIds}"` : 'none' + }`, + ); + } + + const input = action.schema?.input + ? action.schema.input(z).safeParse(opts.input) + : ({ success: true, data: undefined } as const); + + if (!input.success) { + throw new InputError(`Invalid input to action "${opts.id}"`, input.error); + } + + try { + const result = await action.action({ + input: input.data, + credentials: opts.credentials ?? mockCredentials.none(), + 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 "${opts.id}"`, + output.error, + ); + } + + return { output: output.data }; + } catch (error) { + throw new ForwardedError( + `Failed execution of action "${opts.id}"`, + error, + ); + } + } + + register< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, + >(options: ActionsRegistryActionOptions): void { + // hardcode test: prefix similar to how the default actions registry does it + // and other places around the testing ecosystem: + // https://github.com/backstage/backstage/blob/a9219496d5c073aaa0b8caf32ece10455cf65e61/packages/backend-test-utils/src/next/services/mockServices.ts#L321 + // https://github.com/backstage/backstage/blob/861f162b4a39117b824669d67a951ed1db142e3d/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts#L99 + const id = `test:${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-test-utils/src/alpha/services/index.ts b/packages/backend-test-utils/src/alpha/services/index.ts new file mode 100644 index 0000000000..ca9b3f23a0 --- /dev/null +++ b/packages/backend-test-utils/src/alpha/services/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { actionsRegistryServiceMock } from './ActionsRegistryServiceMock'; +export { MockActionsRegistry } from './MockActionsRegistry'; +export { actionsServiceMock } from './ActionsServiceMock'; +export { type ServiceMock } from './simpleMock'; diff --git a/packages/backend-test-utils/src/alpha/services/simpleMock.ts b/packages/backend-test-utils/src/alpha/services/simpleMock.ts new file mode 100644 index 0000000000..d07b143f7a --- /dev/null +++ b/packages/backend-test-utils/src/alpha/services/simpleMock.ts @@ -0,0 +1,57 @@ +/* + * 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, + ServiceFactory, + ServiceRef, +} from '@backstage/backend-plugin-api'; + +/** @alpha */ +export type ServiceMock = { + factory: ServiceFactory; +} & { + [Key in keyof TService]: TService[Key] extends ( + ...args: infer Args + ) => infer Return + ? TService[Key] & jest.MockInstance + : TService[Key]; +}; + +/** @internal */ +export function simpleMock( + ref: ServiceRef, + mockFactory: () => jest.Mocked, +): (partialImpl?: Partial) => ServiceMock { + return partialImpl => { + const mock = mockFactory(); + if (partialImpl) { + for (const [key, impl] of Object.entries(partialImpl)) { + if (typeof impl === 'function') { + (mock as any)[key].mockImplementation(impl); + } else { + (mock as any)[key] = impl; + } + } + } + return Object.assign(mock, { + factory: createServiceFactory({ + service: ref, + deps: {}, + factory: () => mock, + }), + }) as ServiceMock; + }; +} diff --git a/packages/backend-test-utils/src/index.ts b/packages/backend-test-utils/src/index.ts index d8e0e2c4a6..7cc4546415 100644 --- a/packages/backend-test-utils/src/index.ts +++ b/packages/backend-test-utils/src/index.ts @@ -24,5 +24,6 @@ export * from './cache'; export * from './database'; export * from './msw'; export * from './filesystem'; -export * from './next'; +export * from './services'; +export * from './wiring'; export { mockErrorHandler } from './util'; diff --git a/packages/backend-test-utils/src/next/services/MockAuthService.test.ts b/packages/backend-test-utils/src/services/MockAuthService.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockAuthService.test.ts rename to packages/backend-test-utils/src/services/MockAuthService.test.ts diff --git a/packages/backend-test-utils/src/next/services/MockAuthService.ts b/packages/backend-test-utils/src/services/MockAuthService.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockAuthService.ts rename to packages/backend-test-utils/src/services/MockAuthService.ts diff --git a/packages/backend-test-utils/src/next/services/MockEventsService.test.ts b/packages/backend-test-utils/src/services/MockEventsService.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockEventsService.test.ts rename to packages/backend-test-utils/src/services/MockEventsService.test.ts diff --git a/packages/backend-test-utils/src/next/services/MockEventsService.ts b/packages/backend-test-utils/src/services/MockEventsService.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockEventsService.ts rename to packages/backend-test-utils/src/services/MockEventsService.ts diff --git a/packages/backend-test-utils/src/next/services/MockHttpAuthService.test.ts b/packages/backend-test-utils/src/services/MockHttpAuthService.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockHttpAuthService.test.ts rename to packages/backend-test-utils/src/services/MockHttpAuthService.test.ts diff --git a/packages/backend-test-utils/src/next/services/MockHttpAuthService.ts b/packages/backend-test-utils/src/services/MockHttpAuthService.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockHttpAuthService.ts rename to packages/backend-test-utils/src/services/MockHttpAuthService.ts diff --git a/packages/backend-test-utils/src/services/MockPermissionsService.test.ts b/packages/backend-test-utils/src/services/MockPermissionsService.test.ts new file mode 100644 index 0000000000..eb98fe9457 --- /dev/null +++ b/packages/backend-test-utils/src/services/MockPermissionsService.test.ts @@ -0,0 +1,57 @@ +/* + * 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 { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { MockPermissionsService } from './MockPermissionsService'; + +describe('MockPermissionsService', () => { + it('should return the correct result', async () => { + const permission = { + permission: { + name: 'test', + type: 'basic', + attributes: {}, + }, + } as const; + + const defaultService = new MockPermissionsService(); + const allowService = new MockPermissionsService({ + result: AuthorizeResult.ALLOW, + }); + const denyService = new MockPermissionsService({ + result: AuthorizeResult.DENY, + }); + + await expect(defaultService.authorize([permission])).resolves.toEqual([ + { + ...permission, + result: AuthorizeResult.ALLOW, + }, + ]); + await expect(allowService.authorize([permission])).resolves.toEqual([ + { + ...permission, + result: AuthorizeResult.ALLOW, + }, + ]); + await expect(denyService.authorize([permission])).resolves.toEqual([ + { + ...permission, + result: AuthorizeResult.DENY, + }, + ]); + }); +}); diff --git a/packages/backend-test-utils/src/services/MockPermissionsService.ts b/packages/backend-test-utils/src/services/MockPermissionsService.ts new file mode 100644 index 0000000000..3637c34554 --- /dev/null +++ b/packages/backend-test-utils/src/services/MockPermissionsService.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 { PermissionsService } from '@backstage/backend-plugin-api'; +import { + AuthorizePermissionRequest, + AuthorizePermissionResponse, + AuthorizeResult, + QueryPermissionRequest, + QueryPermissionResponse, +} from '@backstage/plugin-permission-common'; + +export class MockPermissionsService implements PermissionsService { + readonly #result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; + + constructor(options?: { + result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; + }) { + this.#result = options?.result ?? AuthorizeResult.ALLOW; + } + + async authorize( + requests: AuthorizePermissionRequest[], + ): Promise { + return requests.map(request => ({ + ...request, + result: this.#result, + })); + } + + async authorizeConditional( + requests: QueryPermissionRequest[], + ): Promise { + return requests.map(request => ({ + ...request, + result: this.#result, + })); + } +} diff --git a/packages/backend-test-utils/src/next/services/MockRootLoggerService.test.ts b/packages/backend-test-utils/src/services/MockRootLoggerService.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockRootLoggerService.test.ts rename to packages/backend-test-utils/src/services/MockRootLoggerService.test.ts diff --git a/packages/backend-test-utils/src/next/services/MockRootLoggerService.ts b/packages/backend-test-utils/src/services/MockRootLoggerService.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockRootLoggerService.ts rename to packages/backend-test-utils/src/services/MockRootLoggerService.ts diff --git a/packages/backend-test-utils/src/next/services/MockUserInfoService.test.ts b/packages/backend-test-utils/src/services/MockUserInfoService.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockUserInfoService.test.ts rename to packages/backend-test-utils/src/services/MockUserInfoService.test.ts diff --git a/packages/backend-test-utils/src/next/services/MockUserInfoService.ts b/packages/backend-test-utils/src/services/MockUserInfoService.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockUserInfoService.ts rename to packages/backend-test-utils/src/services/MockUserInfoService.ts diff --git a/packages/backend-test-utils/src/next/services/index.ts b/packages/backend-test-utils/src/services/index.ts similarity index 87% rename from packages/backend-test-utils/src/next/services/index.ts rename to packages/backend-test-utils/src/services/index.ts index 7c3949e207..73f179ca41 100644 --- a/packages/backend-test-utils/src/next/services/index.ts +++ b/packages/backend-test-utils/src/services/index.ts @@ -13,5 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { mockServices, type ServiceMock } from './mockServices'; +export { mockServices } from './mockServices'; export { mockCredentials } from './mockCredentials'; +export { type ServiceMock } from './simpleMock'; diff --git a/packages/backend-test-utils/src/next/services/mockCredentials.test.ts b/packages/backend-test-utils/src/services/mockCredentials.test.ts similarity index 89% rename from packages/backend-test-utils/src/next/services/mockCredentials.test.ts rename to packages/backend-test-utils/src/services/mockCredentials.test.ts index 343d6d6d20..9248e3bde0 100644 --- a/packages/backend-test-utils/src/next/services/mockCredentials.test.ts +++ b/packages/backend-test-utils/src/services/mockCredentials.test.ts @@ -170,4 +170,27 @@ describe('mockCredentials', () => { "Invalid user entity reference 'wrong', expected :/", ); }); + + it('should have a serializable form', () => { + expect(String(mockCredentials.service('my-service'))).toMatchInlineSnapshot( + `"mockCredentials{servicePrincipal{my-service}}"`, + ); + expect( + String(mockCredentials.user('user:default/mock')), + ).toMatchInlineSnapshot( + `"mockCredentials{userPrincipal{user:default/mock}}"`, + ); + expect( + String( + mockCredentials.user('user:default/mock', { + actor: { subject: 'my-actor' }, + }), + ), + ).toMatchInlineSnapshot( + `"mockCredentials{userPrincipal{user:default/mock,actor={my-actor}}}"`, + ); + expect(String(mockCredentials.none())).toMatchInlineSnapshot( + `"mockCredentials{nonePrincipal}"`, + ); + }); }); diff --git a/packages/backend-test-utils/src/next/services/mockCredentials.ts b/packages/backend-test-utils/src/services/mockCredentials.ts similarity index 88% rename from packages/backend-test-utils/src/next/services/mockCredentials.ts rename to packages/backend-test-utils/src/services/mockCredentials.ts index 231c108ac4..d749f7edb1 100644 --- a/packages/backend-test-utils/src/next/services/mockCredentials.ts +++ b/packages/backend-test-utils/src/services/mockCredentials.ts @@ -76,10 +76,19 @@ export namespace mockCredentials { * Creates a mocked credentials object for a unauthenticated principal. */ export function none(): BackstageCredentials { - return { + const result = { $$type: '@backstage/BackstageCredentials', principal: { type: 'none' }, - }; + } as const; + Object.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + writable: true, + value: () => `mockCredentials{nonePrincipal}`, + }, + }); + return result; } /** @@ -111,16 +120,32 @@ export namespace mockCredentials { options?: { actor?: { subject: string } }, ): BackstageCredentials { validateUserEntityRef(userEntityRef); - return { + const result = { $$type: '@backstage/BackstageCredentials', principal: { type: 'user', userEntityRef, ...(options?.actor && { - actor: { type: 'service', subject: options.actor.subject }, + actor: { type: 'service', subject: options.actor.subject } as const, }), }, - }; + } as const; + Object.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + value: () => + `mockCredentials{userPrincipal{${userEntityRef}${ + options?.actor ? `,actor={${options.actor.subject}}` : '' + }}}`, + }, + token: { + enumerable: false, + configurable: true, + value: user.token(), + }, + }); + return result; } /** @@ -223,14 +248,27 @@ export namespace mockCredentials { subject: string = DEFAULT_MOCK_SERVICE_SUBJECT, accessRestrictions?: BackstagePrincipalAccessRestrictions, ): BackstageCredentials { - return { + const result = { $$type: '@backstage/BackstageCredentials', principal: { type: 'service', subject, ...(accessRestrictions ? { accessRestrictions } : {}), }, - }; + } as const; + Object.defineProperties(result, { + toString: { + enumerable: false, + configurable: true, + value: () => + `mockCredentials{servicePrincipal{${subject}${ + accessRestrictions + ? `,accessRestrictions=${JSON.stringify(accessRestrictions)}` + : '' + }}}`, + }, + }); + return result; } /** diff --git a/packages/backend-test-utils/src/next/services/mockServices.test.ts b/packages/backend-test-utils/src/services/mockServices.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/mockServices.test.ts rename to packages/backend-test-utils/src/services/mockServices.test.ts diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/services/mockServices.ts similarity index 92% rename from packages/backend-test-utils/src/next/services/mockServices.ts rename to packages/backend-test-utils/src/services/mockServices.ts index f68f1337b9..06bf8f9a29 100644 --- a/packages/backend-test-utils/src/next/services/mockServices.ts +++ b/packages/backend-test-utils/src/services/mockServices.ts @@ -36,6 +36,7 @@ import { DiscoveryService, HttpAuthService, LoggerService, + PermissionsService, RootConfigService, ServiceFactory, ServiceRef, @@ -45,6 +46,7 @@ import { } from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { EventsService, eventsServiceRef } from '@backstage/plugin-events-node'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { JsonObject } from '@backstage/types'; import { Knex } from 'knex'; import { MockAuthService } from './MockAuthService'; @@ -53,6 +55,8 @@ import { MockRootLoggerService } from './MockRootLoggerService'; import { MockUserInfoService } from './MockUserInfoService'; import { mockCredentials } from './mockCredentials'; import { MockEventsService } from './MockEventsService'; +import { MockPermissionsService } from './MockPermissionsService'; +import { simpleMock } from './simpleMock'; /** @internal */ function createLoggerMock() { @@ -89,43 +93,6 @@ function simpleFactoryWithOptions< ((...options: TOptions) => ServiceFactory); } -/** @public */ -export type ServiceMock = { - factory: ServiceFactory; -} & { - [Key in keyof TService]: TService[Key] extends ( - ...args: infer Args - ) => infer Return - ? TService[Key] & jest.MockInstance - : TService[Key]; -}; - -/** @internal */ -function simpleMock( - ref: ServiceRef, - mockFactory: () => jest.Mocked, -): (partialImpl?: Partial) => ServiceMock { - return partialImpl => { - const mock = mockFactory(); - if (partialImpl) { - for (const [key, impl] of Object.entries(partialImpl)) { - if (typeof impl === 'function') { - (mock as any)[key].mockImplementation(impl); - } else { - (mock as any)[key] = impl; - } - } - } - return Object.assign(mock, { - factory: createServiceFactory({ - service: ref, - deps: {}, - factory: () => mock, - }), - }) as ServiceMock; - }; -} - /** * Mock implementations of the core services, to be used in tests. * @@ -473,8 +440,37 @@ export namespace mockServices { ); } + /** + * Creates a functional mock implementation of the + * {@link @backstage/backend-plugin-api#PermissionsService}. + */ + export function permissions(options?: { + result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; + }): PermissionsService { + return new MockPermissionsService(options); + } export namespace permissions { - export const factory = () => permissionsServiceFactory; + /** + * Creates a mock factory for the + * {@link @backstage/backend-plugin-api#coreServices.permissions}. Just + * returns the given `result` if you supply one. Otherwise, it returns the + * regular default permissions factory. + */ + export const factory = (options?: { + result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; + }) => + options?.result + ? createServiceFactory({ + service: coreServices.permissions, + deps: {}, + factory: () => new MockPermissionsService(options), + }) + : permissionsServiceFactory; + /** + * Creates a mock of the + * {@link @backstage/backend-plugin-api#coreServices.permissions}, + * optionally with some given method implementations. + */ export const mock = simpleMock(coreServices.permissions, () => ({ authorize: jest.fn(), authorizeConditional: jest.fn(), diff --git a/packages/backend-test-utils/src/services/simpleMock.ts b/packages/backend-test-utils/src/services/simpleMock.ts new file mode 100644 index 0000000000..484969f235 --- /dev/null +++ b/packages/backend-test-utils/src/services/simpleMock.ts @@ -0,0 +1,57 @@ +/* + * 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, + ServiceFactory, + ServiceRef, +} from '@backstage/backend-plugin-api'; + +/** @public */ +export type ServiceMock = { + factory: ServiceFactory; +} & { + [Key in keyof TService]: TService[Key] extends ( + ...args: infer Args + ) => infer Return + ? TService[Key] & jest.MockInstance + : TService[Key]; +}; + +/** @internal */ +export function simpleMock( + ref: ServiceRef, + mockFactory: () => jest.Mocked, +): (partialImpl?: Partial) => ServiceMock { + return partialImpl => { + const mock = mockFactory(); + if (partialImpl) { + for (const [key, impl] of Object.entries(partialImpl)) { + if (typeof impl === 'function') { + (mock as any)[key].mockImplementation(impl); + } else { + (mock as any)[key] = impl; + } + } + } + return Object.assign(mock, { + factory: createServiceFactory({ + service: ref, + deps: {}, + factory: () => mock, + }), + }) as ServiceMock; + }; +} diff --git a/packages/backend-test-utils/src/util/errorHandler.ts b/packages/backend-test-utils/src/util/errorHandler.ts index f534823f50..79f4d8fd04 100644 --- a/packages/backend-test-utils/src/util/errorHandler.ts +++ b/packages/backend-test-utils/src/util/errorHandler.ts @@ -15,7 +15,7 @@ */ import { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter'; -import { mockServices } from '../next'; +import { mockServices } from '../services'; /** * A mock for error handler middleware that can be used in router tests. diff --git a/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.test.ts b/packages/backend-test-utils/src/wiring/ServiceFactoryTester.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.test.ts rename to packages/backend-test-utils/src/wiring/ServiceFactoryTester.test.ts diff --git a/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts b/packages/backend-test-utils/src/wiring/ServiceFactoryTester.ts similarity index 97% rename from packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts rename to packages/backend-test-utils/src/wiring/ServiceFactoryTester.ts index 83a6a42307..2b49abc88f 100644 --- a/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts +++ b/packages/backend-test-utils/src/wiring/ServiceFactoryTester.ts @@ -20,7 +20,7 @@ import { defaultServiceFactories } from './TestBackend'; // This is a relative import in order to make sure that the implementation is duplicated // rather than leading to an import from @backstage/backend-app-api. // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { ServiceRegistry } from '../../../../backend-app-api/src/wiring/ServiceRegistry'; +import { ServiceRegistry } from '../../../backend-app-api/src/wiring/ServiceRegistry'; /** * Options for {@link ServiceFactoryTester}. diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts b/packages/backend-test-utils/src/wiring/TestBackend.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/wiring/TestBackend.test.ts rename to packages/backend-test-utils/src/wiring/TestBackend.test.ts diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/wiring/TestBackend.ts similarity index 97% rename from packages/backend-test-utils/src/next/wiring/TestBackend.ts rename to packages/backend-test-utils/src/wiring/TestBackend.ts index 20d02cf880..cdfa1fc695 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/wiring/TestBackend.ts @@ -31,7 +31,7 @@ import express from 'express'; import { InternalBackendFeature, InternalBackendRegistrations, -} from '../../../../backend-plugin-api/src/wiring/types'; +} from '../../../backend-plugin-api/src/wiring/types'; import { DefaultRootHttpRouter, ExtendedHttpServer, @@ -40,6 +40,10 @@ import { createHttpServer, } from '@backstage/backend-defaults/rootHttpRouter'; import { HostDiscovery } from '@backstage/backend-defaults/discovery'; +import { + actionsRegistryServiceMock, + actionsServiceMock, +} from '../alpha/services'; /** @public */ export interface TestBackendOptions { @@ -84,6 +88,10 @@ export const defaultServiceFactories = [ mockServices.userInfo.factory(), mockServices.urlReader.factory(), mockServices.events.factory(), + + // Alpha services + actionsRegistryServiceMock.factory(), + actionsServiceMock.factory(), ]; /** diff --git a/packages/backend-test-utils/src/next/wiring/index.ts b/packages/backend-test-utils/src/wiring/index.ts similarity index 100% rename from packages/backend-test-utils/src/next/wiring/index.ts rename to packages/backend-test-utils/src/wiring/index.ts diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index b7d8283cd1..0547c9289d 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,420 @@ # example-backend +## 0.0.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.1-next.1 + - @backstage/plugin-kubernetes-backend@0.20.0-next.1 + - @backstage/plugin-scaffolder-backend@2.2.0-next.1 + - @backstage/plugin-proxy-backend@0.6.5-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.8.2-next.1 + +## 0.0.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-backend@0.19.9-next.0 + - @backstage/plugin-scaffolder-backend@2.2.0-next.0 + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-catalog-backend@3.0.1-next.0 + - @backstage/plugin-app-backend@0.5.5-next.0 + - @backstage/plugin-auth-backend@0.25.3-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.3.6-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-devtools-backend@0.5.8-next.0 + - @backstage/plugin-events-backend@0.5.5-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.3-next.0 + - @backstage/plugin-mcp-actions-backend@0.1.2-next.0 + - @backstage/plugin-notifications-backend@0.5.9-next.0 + - @backstage/plugin-permission-backend@0.7.3-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/plugin-proxy-backend@0.6.5-next.0 + - @backstage/plugin-search-backend@2.0.5-next.0 + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/plugin-signals-backend@0.3.7-next.0 + - @backstage/plugin-techdocs-backend@2.0.5-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.5-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.11-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.8.2-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.13-next.0 + - @backstage/plugin-search-backend-module-catalog@0.3.7-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.5-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.13-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.11-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.11-next.0 + - @backstage/plugin-search-backend-module-explore@0.3.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-permission-common@0.9.1 + +## 0.0.40 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0 + - @backstage/plugin-scaffolder-backend@2.1.0 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-defaults@0.11.1 + - @backstage/plugin-notifications-backend@0.5.8 + - @backstage/plugin-mcp-actions-backend@0.1.1 + - @backstage/plugin-auth-backend@0.25.2 + - @backstage/plugin-scaffolder-backend-module-github@0.8.1 + - @backstage/plugin-search-backend@2.0.4 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-app-backend@0.5.4 + - @backstage/plugin-auth-backend-module-github-provider@0.3.5 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.10 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.4 + - @backstage/plugin-catalog-backend-module-openapi@0.2.12 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.10 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.2 + - @backstage/plugin-devtools-backend@0.5.7 + - @backstage/plugin-events-backend@0.5.4 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.2 + - @backstage/plugin-kubernetes-backend@0.19.8 + - @backstage/plugin-permission-backend@0.7.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.10 + - @backstage/plugin-proxy-backend@0.6.4 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.12 + - @backstage/plugin-search-backend-module-catalog@0.3.6 + - @backstage/plugin-search-backend-module-explore@0.3.4 + - @backstage/plugin-search-backend-module-techdocs@0.4.4 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-signals-backend@0.3.6 + - @backstage/plugin-techdocs-backend@2.0.4 + +## 0.0.40-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.2-next.1 + - @backstage/plugin-scaffolder-backend@2.1.0-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.10-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.8.1-next.2 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.12-next.2 + +## 0.0.40-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/plugin-app-backend@0.5.4-next.0 + - @backstage/plugin-devtools-backend@0.5.7-next.1 + - @backstage/plugin-proxy-backend@0.6.4-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-backend@0.25.2-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.10-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-catalog-backend@3.0.0-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.4-next.0 + - @backstage/plugin-events-backend@0.5.4-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.2-next.0 + - @backstage/plugin-kubernetes-backend@0.19.8-next.0 + - @backstage/plugin-notifications-backend@0.5.8-next.1 + - @backstage/plugin-permission-backend@0.7.2-next.0 + - @backstage/plugin-scaffolder-backend@2.0.1-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.8.1-next.1 + - @backstage/plugin-search-backend@2.0.4-next.1 + - @backstage/plugin-search-backend-module-catalog@0.3.6-next.0 + - @backstage/plugin-search-backend-module-explore@0.3.4-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.4-next.1 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-signals-backend@0.3.6-next.0 + - @backstage/plugin-techdocs-backend@2.0.4-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.2-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.10-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.12-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.10-next.0 + - @backstage/plugin-mcp-actions-backend@0.1.1-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.3.5-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.12-next.1 + +## 0.0.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0-next.0 + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-scaffolder-backend@2.0.1-next.0 + - @backstage/plugin-notifications-backend@0.5.8-next.0 + - @backstage/plugin-mcp-actions-backend@0.1.1-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.8.1-next.0 + - @backstage/plugin-search-backend@2.0.4-next.0 + - @backstage/plugin-app-backend@0.5.3 + - @backstage/plugin-auth-backend@0.25.1 + - @backstage/plugin-auth-backend-module-github-provider@0.3.4 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/plugin-devtools-backend@0.5.7-next.0 + - @backstage/plugin-events-backend@0.5.3 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.1 + - @backstage/plugin-kubernetes-backend@0.19.7 + - @backstage/plugin-permission-backend@0.7.1 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/plugin-proxy-backend@0.6.3 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-signals-backend@0.3.5 + - @backstage/plugin-techdocs-backend@2.0.4-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.9 + - @backstage/plugin-catalog-backend-module-openapi@0.2.12-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.9 + - @backstage/plugin-search-backend-module-catalog@0.3.5 + - @backstage/plugin-search-backend-module-explore@0.3.3 + - @backstage/plugin-search-backend-module-techdocs@0.4.4-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.12-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.3 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.1 + - @backstage/plugin-permission-common@0.9.0 + +## 0.0.39 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-backend@0.5.7 + - @backstage/plugin-scaffolder-backend-module-github@0.8.0 + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-scaffolder-backend@2.0.0 + - @backstage/plugin-catalog-backend@2.1.0 + - @backstage/plugin-devtools-backend@0.5.6 + - @backstage/plugin-mcp-actions-backend@0.1.0 + - @backstage/plugin-events-backend@0.5.3 + - @backstage/plugin-search-backend@2.0.3 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.1 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.11 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/plugin-search-backend-module-techdocs@0.4.3 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-kubernetes-backend@0.19.7 + - @backstage/plugin-search-backend-module-catalog@0.3.5 + - @backstage/plugin-techdocs-backend@2.0.3 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-app-backend@0.5.3 + - @backstage/plugin-auth-backend@0.25.1 + - @backstage/plugin-auth-backend-module-github-provider@0.3.4 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.9 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.3 + - @backstage/plugin-catalog-backend-module-openapi@0.2.11 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.1 + - @backstage/plugin-permission-backend@0.7.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.9 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/plugin-proxy-backend@0.6.3 + - @backstage/plugin-search-backend-module-explore@0.3.3 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-signals-backend@0.3.5 + +## 0.0.39-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-github@0.8.0-next.2 + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/plugin-scaffolder-backend@2.0.0-next.2 + - @backstage/plugin-notifications-backend@0.5.7-next.2 + - @backstage/plugin-catalog-backend@2.0.1-next.2 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.11-next.2 + - @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-auth-node@0.6.4-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.2 + - @backstage/plugin-events-backend@0.5.3-next.1 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.1-next.1 + - @backstage/plugin-kubernetes-backend@0.19.7-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.2 + - @backstage/plugin-search-backend-module-catalog@0.3.5-next.1 + - @backstage/plugin-search-backend-module-explore@0.3.3-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.3-next.1 + - @backstage/plugin-search-backend-node@1.3.12-next.1 + - @backstage/plugin-signals-backend@0.3.5-next.1 + - @backstage/plugin-techdocs-backend@2.0.3-next.2 + +## 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 + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0 + - @backstage/plugin-catalog-backend@2.0.0 + - @backstage/plugin-scaffolder-backend-module-github@0.7.1 + - @backstage/backend-defaults@0.10.0 + - @backstage/plugin-auth-backend-module-github-provider@0.3.3 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.2 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-kubernetes-backend@0.19.6 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/plugin-scaffolder-backend@1.33.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-backend@0.7.0 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/plugin-techdocs-backend@2.0.2 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.10 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.0 + - @backstage/plugin-notifications-backend@0.5.6 + - @backstage/plugin-signals-backend@0.3.4 + - @backstage/plugin-catalog-backend-module-openapi@0.2.10 + - @backstage/plugin-app-backend@0.5.2 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.8 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8 + - @backstage/plugin-devtools-backend@0.5.5 + - @backstage/plugin-events-backend@0.5.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.8 + - @backstage/plugin-proxy-backend@0.6.2 + - @backstage/plugin-search-backend@2.0.2 + - @backstage/plugin-search-backend-module-catalog@0.3.4 + - @backstage/plugin-search-backend-module-explore@0.3.2 + - @backstage/plugin-search-backend-module-techdocs@0.4.2 + - @backstage/plugin-search-backend-node@1.3.11 + +## 0.0.38-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/plugin-catalog-backend@2.0.0-next.3 + - @backstage/plugin-auth-backend@0.25.0-next.2 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/plugin-auth-backend-module-github-provider@0.3.3-next.2 + - @backstage/plugin-scaffolder-backend@1.33.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-app-backend@0.5.2-next.2 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.8-next.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.2-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.2.10-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.0-next.2 + - @backstage/plugin-devtools-backend@0.5.5-next.3 + - @backstage/plugin-events-backend@0.5.2-next.2 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.0-next.2 + - @backstage/plugin-kubernetes-backend@0.19.6-next.3 + - @backstage/plugin-notifications-backend@0.5.6-next.2 + - @backstage/plugin-permission-backend@0.7.0-next.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.8-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + - @backstage/plugin-proxy-backend@0.6.2-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.3 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.10-next.3 + - @backstage/plugin-search-backend@2.0.2-next.3 + - @backstage/plugin-search-backend-module-catalog@0.3.4-next.2 + - @backstage/plugin-search-backend-module-explore@0.3.2-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.3 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-signals-backend@0.3.4-next.2 + - @backstage/plugin-techdocs-backend@2.0.2-next.3 + ## 0.0.38-next.2 ### Patch Changes diff --git a/packages/backend/catalog-info.yaml b/packages/backend/catalog-info.yaml index 0e95516a41..3fb9572dc6 100644 --- a/packages/backend/catalog-info.yaml +++ b/packages/backend/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend/package.json b/packages/backend/package.json index 70a47f794c..363f9743b8 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.38-next.2", + "version": "0.0.41-next.1", "backstage": { "role": "backend" }, @@ -47,6 +47,7 @@ "@backstage/plugin-events-backend": "workspace:^", "@backstage/plugin-events-backend-module-google-pubsub": "workspace:^", "@backstage/plugin-kubernetes-backend": "workspace:^", + "@backstage/plugin-mcp-actions-backend": "workspace:^", "@backstage/plugin-notifications-backend": "workspace:^", "@backstage/plugin-permission-backend": "workspace:^", "@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^", @@ -63,7 +64,7 @@ "@backstage/plugin-search-backend-node": "workspace:^", "@backstage/plugin-signals-backend": "workspace:^", "@backstage/plugin-techdocs-backend": "workspace:^", - "@opentelemetry/auto-instrumentations-node": "^0.54.0", + "@opentelemetry/auto-instrumentations-node": "^0.61.0", "@opentelemetry/exporter-prometheus": "^0.54.0", "@opentelemetry/sdk-node": "^0.54.0", "example-app": "link:../app" diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 23d814f1ae..f46e947b0d 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -62,4 +62,5 @@ backend.add(import('@backstage/plugin-notifications-backend')); backend.add(import('./instanceMetadata')); backend.add(import('@backstage/plugin-events-backend-module-google-pubsub')); +backend.add(import('@backstage/plugin-mcp-actions-backend')); backend.start(); diff --git a/packages/canon/.storybook/main.ts b/packages/canon/.storybook/main.ts deleted file mode 100644 index e58c0f182f..0000000000 --- a/packages/canon/.storybook/main.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { StorybookConfig } from '@storybook/react-webpack5'; -import { join, dirname } from 'path'; - -/** - * This function is used to resolve the absolute path of a package. - * It is needed in projects that use Yarn PnP or are set up within a monorepo. - */ -function getAbsolutePath(value: string): string { - return dirname(require.resolve(join(value, 'package.json'))); -} -const config: StorybookConfig = { - stories: [ - '../src/components/**/*.stories.@(js|jsx|mjs|ts|tsx)', - '../src/stories/**/*.stories.@(js|jsx|mjs|ts|tsx)', - ], - staticDirs: ['../static'], - addons: [ - getAbsolutePath('@storybook/addon-webpack5-compiler-swc'), - getAbsolutePath('@storybook/addon-essentials'), - getAbsolutePath('@storybook/addon-interactions'), - getAbsolutePath('@storybook/addon-themes'), - ], - framework: { - name: getAbsolutePath('@storybook/react-webpack5'), - options: {}, - }, - // https://storybook.js.org/docs/configure/integration/compilers#the-swc-compiler-doesnt-work-with-react - swc: () => ({ - jsc: { - transform: { - react: { - runtime: 'automatic', - }, - }, - }, - }), -}; -export default config; diff --git a/packages/canon/.storybook/preview.tsx b/packages/canon/.storybook/preview.tsx deleted file mode 100644 index 405cdfa093..0000000000 --- a/packages/canon/.storybook/preview.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import type { Preview, ReactRenderer } from '@storybook/react'; -import { withThemeByDataAttribute } from '@storybook/addon-themes'; -import '../src/css/styles.css'; - -const preview: Preview = { - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - backgrounds: { - disable: true, - }, - options: { - storySort: { - method: 'alphabetical', - order: ['Core Concepts', 'Components'], - }, - }, - viewport: { - viewports: { - initial: { - name: 'Initial', - styles: { - width: '320px', - height: '100%', - }, - }, - xs: { - name: 'Extra Small', - styles: { - width: '640px', - height: '100%', - }, - }, - sm: { - name: 'Small', - styles: { - width: '768px', - height: '100%', - }, - }, - md: { - name: 'Medium', - styles: { - width: '1024px', - height: '100%', - }, - }, - lg: { - name: 'Large', - styles: { - width: '1280px', - height: '100%', - }, - }, - xl: { - name: 'Extra Large', - styles: { - width: '1536px', - height: '100%', - }, - }, - }, - }, - }, - decorators: [ - withThemeByDataAttribute({ - themes: { - Light: 'light', - Dark: 'dark', - }, - defaultTheme: 'Light', - }), - Story => { - document.body.style.backgroundColor = 'var(--canon-bg)'; - - const docsStoryElements = document.getElementsByClassName('docs-story'); - Array.from(docsStoryElements).forEach(element => { - (element as HTMLElement).style.backgroundColor = 'var(--canon-bg)'; - }); - - return ; - }, - ], -}; - -export default preview; diff --git a/packages/canon/CHANGELOG.md b/packages/canon/CHANGELOG.md deleted file mode 100644 index cca1758025..0000000000 --- a/packages/canon/CHANGELOG.md +++ /dev/null @@ -1,171 +0,0 @@ -# @backstage/canon - -## 0.4.0-next.2 - -### Patch Changes - -- 6189bfd: Added new icon and onClear props to the TextField to make it easier to accessorize inputs. -- 97b25a1: Pin version of @base-ui-components/react. -- 185d3a8: Use the Field component from Base UI within the TextField. - -## 0.4.0-next.1 - -### Minor Changes - -- ea36f74: **Breaking Change** Icons on Button and IconButton now need to be imported and placed like this: - ); - }, -); - -export default Button; diff --git a/packages/canon/src/components/Button/index.tsx b/packages/canon/src/components/Button/index.tsx deleted file mode 100644 index 9051f1145e..0000000000 --- a/packages/canon/src/components/Button/index.tsx +++ /dev/null @@ -1,19 +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. - */ -export { Button } from './Button'; -export type { ButtonProps } from './types'; -export { buttonPropDefs } from './Button.props'; -export type { ButtonOwnProps } from './Button.props'; diff --git a/packages/canon/src/components/Button/styles.css b/packages/canon/src/components/Button/styles.css deleted file mode 100644 index a4afd85c9b..0000000000 --- a/packages/canon/src/components/Button/styles.css +++ /dev/null @@ -1,107 +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. - */ - -.canon-Button { - border: none; - display: inline-flex; - align-items: center; - justify-content: center; - user-select: none; - font-family: var(--canon-font-regular); - font-weight: var(--canon-font-weight-bold); - padding: 0; - cursor: pointer; - border-radius: var(--canon-radius-2); - gap: var(--canon-space-1_5); - - &:disabled { - cursor: not-allowed; - } -} - -.canon-Button[data-variant='primary'] { - background-color: var(--canon-bg-solid); - color: var(--canon-fg-solid); - transition: background-color 150ms ease, box-shadow 150ms ease; - - &:hover { - background-color: var(--canon-bg-solid-hover); - } - - &:active { - background-color: var(--canon-bg-solid-pressed); - } - - &:focus-visible { - outline: 2px solid var(--canon-ring); - outline-offset: 2px; - } - - &:disabled { - background-color: var(--canon-bg-solid-disabled); - color: var(--canon-fg-solid-disabled); - } -} - -.canon-Button[data-variant='secondary'] { - background-color: var(--canon-bg-surface-1); - box-shadow: inset 0 0 0 1px var(--canon-border); - color: var(--canon-fg-primary); - transition: box-shadow 150ms ease; - - &:hover { - box-shadow: inset 0 0 0 1px var(--canon-border-hover); - } - - &:active { - box-shadow: inset 0 0 0 1px var(--canon-border-pressed); - } - - &:focus-visible { - outline: none; - transition: none; - box-shadow: inset 0 0 0 2px var(--canon-ring); - } - - &:disabled { - box-shadow: inset 0 0 0 1px var(--canon-border-disabled); - color: var(--canon-fg-disabled); - } -} - -.canon-Button[data-size='medium'] { - font-size: var(--canon-font-size-4); - padding: 0 var(--canon-space-3); - height: 40px; -} - -.canon-Button[data-size='small'] { - font-size: var(--canon-font-size-3); - padding: 0 var(--canon-space-2); - height: 32px; -} - -.canon-ButtonIcon[data-size='small'], -.canon-ButtonIcon[data-size='small'] svg { - width: 1rem; - height: 1rem; -} - -.canon-ButtonIcon[data-size='medium'], -.canon-ButtonIcon[data-size='medium'] svg { - width: 1.25rem; - height: 1.25rem; -} diff --git a/packages/canon/src/components/Checkbox/styles.css b/packages/canon/src/components/Checkbox/styles.css deleted file mode 100644 index 8468980bf2..0000000000 --- a/packages/canon/src/components/Checkbox/styles.css +++ /dev/null @@ -1,52 +0,0 @@ -.canon-CheckboxRoot { - border: none; - display: flex; - align-items: center; - justify-content: center; - width: 1rem; - height: 1rem; - box-shadow: inset 0 0 0 1px var(--canon-border); - cursor: pointer; - border-radius: 2px; - transition: background-color 0.2s ease-in-out; - background-color: var(--canon-bg-surface-1); - padding: 0; - flex-shrink: 0; -} - -.canon-CheckboxRoot:focus-visible { - transition: none; - outline: 2px solid var(--canon-ring); - outline-offset: 2px; -} - -.canon-CheckboxRoot[data-checked] { - background-color: var(--canon-bg-solid); - box-shadow: none; - color: var(--canon-fg-solid); -} - -.canon-CheckboxLabel { - display: flex; - flex-direction: row; - align-items: center; - gap: var(--canon-space-2); - font-size: var(--canon-font-size-3); - font-family: var(--canon-font-regular); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-primary); - user-select: none; - - &:hover { - & .canon-CheckboxRoot:not([data-checked]) { - box-shadow: inset 0 0 0 1px var(--canon-border-hover); - } - } -} - -.canon-CheckboxIndicator { - display: flex; - align-items: center; - justify-content: center; - color: var(--canon-fg-solid); -} diff --git a/packages/canon/src/components/Container/styles.css b/packages/canon/src/components/Container/styles.css deleted file mode 100644 index 1ebf63987f..0000000000 --- a/packages/canon/src/components/Container/styles.css +++ /dev/null @@ -1,18 +0,0 @@ -.canon-Container { - max-width: 120rem; - padding: 0 var(--canon-space-4); - margin: 0 auto; - transition: padding 0.2s ease-in-out; -} - -@media (min-width: 640px) { - .canon-Container { - padding: 0 var(--canon-space-8); - } -} - -@media (min-width: 1024px) { - .canon-Container { - padding: 0 var(--canon-space-12); - } -} diff --git a/packages/canon/src/components/DataTable/DataTable.stories.tsx b/packages/canon/src/components/DataTable/DataTable.stories.tsx deleted file mode 100644 index 266aedc533..0000000000 --- a/packages/canon/src/components/DataTable/DataTable.stories.tsx +++ /dev/null @@ -1,145 +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 type { Meta, StoryObj } from '@storybook/react'; -import { DataTable } from '.'; -import { data, DataProps } from './mocked-components'; -import { columns } from './mocked-columns'; -import { - flexRender, - getCoreRowModel, - getPaginationRowModel, - useReactTable, - PaginationState, -} from '@tanstack/react-table'; -import { useState } from 'react'; - -const meta = { - title: 'Components/DataTable', -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Uncontrolled: Story = { - render: () => { - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - }); - - return ( - - - - - ); - }, -}; - -export const Controlled: Story = { - render: () => { - const [pagination, setPagination] = useState({ - pageIndex: 4, - pageSize: 5, - }); - - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - state: { - pagination, - }, - onPaginationChange: setPagination, - }); - - return ( - - - - - ); - }, -}; - -export const WithCustomTable: Story = { - render: () => { - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - }); - - return ( - - - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext(), - )} - - ); - })} - - ))} - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map(row => ( - - {row.getVisibleCells().map(cell => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ))} - - )) - ) : ( - - - No results. - - - )} - - - - - ); - }, -}; diff --git a/packages/canon/src/components/DataTable/DataTable.tsx b/packages/canon/src/components/DataTable/DataTable.tsx deleted file mode 100644 index 5a587df56f..0000000000 --- a/packages/canon/src/components/DataTable/DataTable.tsx +++ /dev/null @@ -1,51 +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 { forwardRef } from 'react'; -import { Table } from '../Table'; -import { DataTableRoot } from './Root/DataTableRoot'; -import { DataTablePagination } from './Pagination/DataTablePagination'; -import { Table as TanstackTable } from '@tanstack/react-table'; -import { DataTableTable } from './Table/DataTableTable'; - -const TableRoot = forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ); -TableRoot.displayName = Table.Root.displayName; - -/** - * DataTable component for displaying tabular data with pagination - * @public - */ -export const DataTable = { - Root: DataTableRoot as ( - props: { - table: TanstackTable; - } & React.HTMLAttributes, - ) => JSX.Element, - Pagination: DataTablePagination, - Table: DataTableTable, - TableRoot: TableRoot, - TableHeader: Table.Header, - TableBody: Table.Body, - TableRow: Table.Row, - TableCell: Table.Cell, - TableCellText: Table.CellText, - TableCellLink: Table.CellLink, - TableCellProfile: Table.CellProfile, - TableHead: Table.Head, -}; diff --git a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.stories.tsx b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.stories.tsx deleted file mode 100644 index a2f931aed6..0000000000 --- a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.stories.tsx +++ /dev/null @@ -1,55 +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 type { Meta, StoryObj } from '@storybook/react'; -import { DataTablePagination } from './DataTablePagination'; -import { - getCoreRowModel, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from '@tanstack/react-table'; -import { data, DataProps } from '../mocked-components'; -import { columns } from '../mocked-columns'; -import { DataTable } from '../DataTable'; - -const meta = { - title: 'Components/DataTable/Pagination', - component: DataTablePagination, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - render: () => { - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFilteredRowModel: getFilteredRowModel(), - }); - - return ( - - - - ); - }, -}; diff --git a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.styles.css b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.styles.css deleted file mode 100644 index 02d12e73bb..0000000000 --- a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.styles.css +++ /dev/null @@ -1,23 +0,0 @@ -.canon-DataTablePagination { - display: flex; - align-items: center; - justify-content: space-between; - padding-top: var(--canon-space-5); -} - -.canon-DataTablePagination--left { - display: flex; - align-items: center; - justify-content: space-between; -} - -.canon-DataTablePagination--right { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--canon-space-2); -} - -.canon-DataTablePagination--select { - min-width: 10.5rem; -} diff --git a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx deleted file mode 100644 index 2efa73bf14..0000000000 --- a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx +++ /dev/null @@ -1,96 +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 { forwardRef } from 'react'; -import { Text } from '../../Text'; -import { DataTablePaginationProps } from './types'; -import { IconButton } from '../../IconButton'; -import clsx from 'clsx'; -import { Select } from '../../Select'; -import { useDataTable } from '../Root/DataTableRoot'; -import { Icon } from '../../Icon'; - -/** @public */ -const DataTablePagination = forwardRef( - ( - props: DataTablePaginationProps, - ref: React.ForwardedRef, - ) => { - const { className, ...rest } = props; - const { table } = useDataTable(); - const pageIndex = table?.getState().pagination.pageIndex; - const pageSize = table?.getState().pagination.pageSize; - const rowCount = table?.getRowCount(); - const fromCount = (pageIndex ?? 0) * (pageSize ?? 10) + 1; - const toCount = Math.min( - ((pageIndex ?? 0) + 1) * (pageSize ?? 10), - rowCount, - ); - - return ( -
    -
    - {!table.options.manualPagination && ( - - ); - }} - /> - - - ); - }, -}; - -export const Controlled: Story = { - render: () => { - const { - handleSubmit, - control, - formState: { errors }, - } = useForm(); - - const [firstname, setFirstname] = useState('John'); - const [lastname, setLastname] = useState('Doe'); - const [city, setCity] = useState('london'); - - const onSubmit: SubmitHandler = data => { - console.log('data', data); - setFirstname(data.firstname); - setLastname(data.lastname); - setCity(data.city); - }; - - return ( -
    - { - return ( - { - field.onChange(e); - setFirstname(e.target.value); - }} - error={errors.firstname?.message} - /> - ); - }} - /> - { - return ( - { - field.onChange(e); - setLastname(e.target.value); - }} - error={errors.lastname?.message} - /> - ); - }} - /> - { - return ( - - - - - ); - } - - return null; -} /** - * Creates an app that is suitable for the public sign-in page, for use in the `index-public-experimental.tsx` file. - * - * @remarks - * - * This app has an override for the `app/layout` extension, which means that - * most extension typically installed in an app will be ignored. However, you - * can still for example install API and root element extensions. - * - * A typical setup of this app will only install a custom sign-in page. - * - * @example - * ```ts - * const app = createPublicSignInApp({ - * features: [signInPageModule], - * }); - * ``` - * * @public + * @deprecated Use {@link @backstage/plugin-app/alpha#appModulePublicSignIn} instead. */ export function createPublicSignInApp(options?: CreateAppOptions) { return createApp({ ...options, - features: [ - ...(options?.features ?? []), - // This is a rather than app plugin override in order for it to take precedence over any supplied app plugin override - createFrontendModule({ - pluginId: 'app', - extensions: [ - appPlugin.getExtension('app/layout').override({ - factory: () => [ - coreExtensionData.reactElement(), - ], - }), - ], - }), - ], + features: [...(options?.features ?? []), appModulePublicSignIn], }); } diff --git a/packages/frontend-defaults/src/discovery.test.ts b/packages/frontend-defaults/src/discovery.test.ts index d1738554a4..b4e595890a 100644 --- a/packages/frontend-defaults/src/discovery.test.ts +++ b/packages/frontend-defaults/src/discovery.test.ts @@ -27,7 +27,7 @@ Object.defineProperty(global, '__@backstage/discovered__', { }); const config = new ConfigReader({ - app: { experimental: { packages: 'all' } }, + app: { packages: 'all' }, }); describe('discoverAvailableFeatures', () => { diff --git a/packages/frontend-defaults/src/discovery.ts b/packages/frontend-defaults/src/discovery.ts index 6da6146389..71d831b58c 100644 --- a/packages/frontend-defaults/src/discovery.ts +++ b/packages/frontend-defaults/src/discovery.ts @@ -26,7 +26,10 @@ interface DiscoveryGlobal { } function readPackageDetectionConfig(config: Config) { - const packages = config.getOptional('app.experimental.packages'); + // The experimental key is deprecated, but supported still for backwards compatibility + const packages = + config.getOptional('app.packages') ?? + config.getOptional('app.experimental.packages'); if (packages === undefined || packages === null) { return undefined; } @@ -34,21 +37,16 @@ function readPackageDetectionConfig(config: Config) { if (typeof packages === 'string') { if (packages !== 'all') { throw new Error( - `Invalid app.experimental.packages mode, got '${packages}', expected 'all'`, + `Invalid app.packages mode, got '${packages}', expected 'all'`, ); } return {}; } if (typeof packages !== 'object' || Array.isArray(packages)) { - throw new Error( - "Invalid config at 'app.experimental.packages', expected object", - ); + throw new Error("Invalid config at 'app.packages', expected object"); } - const packagesConfig = new ConfigReader( - packages, - 'app.experimental.packages', - ); + const packagesConfig = new ConfigReader(packages, 'app.packages'); return { include: packagesConfig.getOptionalStringArray('include'), diff --git a/packages/frontend-defaults/src/index.ts b/packages/frontend-defaults/src/index.ts index 573cc9b08c..68d4c72568 100644 --- a/packages/frontend-defaults/src/index.ts +++ b/packages/frontend-defaults/src/index.ts @@ -20,11 +20,7 @@ * @packageDocumentation */ -export { - createApp, - type CreateAppOptions, - type CreateAppFeatureLoader, -} from './createApp'; +export { createApp, type CreateAppOptions } from './createApp'; export { createPublicSignInApp } from './createPublicSignInApp'; export { discoverAvailableFeatures } from './discovery'; export { resolveAsyncFeatures } from './resolution'; diff --git a/packages/frontend-defaults/src/resolution.test.ts b/packages/frontend-defaults/src/resolution.test.ts index b39e302b24..eb9e58f949 100644 --- a/packages/frontend-defaults/src/resolution.test.ts +++ b/packages/frontend-defaults/src/resolution.test.ts @@ -20,7 +20,6 @@ import { FrontendFeatureLoader, PageBlueprint, } from '@backstage/frontend-plugin-api'; -import { CreateAppFeatureLoader } from './createApp'; import { resolveAsyncFeatures } from './resolution'; import { mockApis } from '@backstage/test-utils'; @@ -42,7 +41,7 @@ describe('resolveAsyncFeatures', () => { extensions: [ PageBlueprint.make({ params: { - defaultPath: '/', + path: '/', loader: () => new Promise(() => {}), }, }), @@ -71,75 +70,6 @@ describe('resolveAsyncFeatures', () => { ]); }); - it('supports deprecated feature loaders', async () => { - const loader: CreateAppFeatureLoader = { - getLoaderName() { - return 'test-loader'; - }, - async load() { - return { - features: [ - createFrontendPlugin({ - pluginId: 'test', - extensions: [ - PageBlueprint.make({ - params: { - defaultPath: '/', - loader: () => new Promise(() => {}), - }, - }), - ], - }), - ], - }; - }, - }; - - const { features } = await resolveAsyncFeatures({ - config: mockApis.config(), - features: [loader], - }); - - expect(features).toMatchObject([ - { - $$type: '@backstage/FrontendPlugin', - id: 'test', - version: 'v1', - extensions: [ - { - $$type: '@backstage/Extension', - id: 'page:test', - version: 'v2', - attachTo: { - id: 'app/routes', - input: 'routes', - }, - }, - ], - }, - ]); - }); - - it('should propagate errors thrown by deprecated feature loaders', async () => { - const loader: CreateAppFeatureLoader = { - getLoaderName() { - return 'test-loader'; - }, - async load() { - throw new TypeError('boom'); - }, - }; - - await expect(() => - resolveAsyncFeatures({ - config: mockApis.config(), - features: [loader], - }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to read frontend features from loader 'test-loader', TypeError: boom"`, - ); - }); - it('supports feature loaders', async () => { const loader: FrontendFeatureLoader = createFrontendFeatureLoader({ async loader({ config: _ }) { @@ -149,7 +79,7 @@ describe('resolveAsyncFeatures', () => { extensions: [ PageBlueprint.make({ params: { - defaultPath: '/', + path: '/', loader: () => new Promise(() => {}), }, }), diff --git a/packages/frontend-defaults/src/resolution.ts b/packages/frontend-defaults/src/resolution.ts index 6ab26c3d1d..58f91c45be 100644 --- a/packages/frontend-defaults/src/resolution.ts +++ b/packages/frontend-defaults/src/resolution.ts @@ -20,40 +20,14 @@ import { FrontendFeature, FrontendFeatureLoader, } from '@backstage/frontend-plugin-api'; -import { CreateAppFeatureLoader } from './createApp'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { isInternalFrontendFeatureLoader } from '../../frontend-plugin-api/src/wiring/createFrontendFeatureLoader'; /** @public */ export async function resolveAsyncFeatures(options: { config: Config; - features?: ( - | FrontendFeature - | FrontendFeatureLoader - | CreateAppFeatureLoader - )[]; + features?: (FrontendFeature | FrontendFeatureLoader)[]; }): Promise<{ features: FrontendFeature[] }> { - const features: (FrontendFeature | FrontendFeatureLoader)[] = []; - - // Separate deprecated CreateAppFeatureLoader elements from the frontend features, - // and manage the deprecated elements first. - for (const item of options?.features ?? []) { - if ('load' in item) { - try { - const result = await item.load({ config: options.config }); - features.push(...result.features); - } catch (e) { - throw new Error( - `Failed to read frontend features from loader '${item.getLoaderName()}', ${stringifyError( - e, - )}`, - ); - } - } else { - features.push(item); - } - } - const loadedFeatures: FrontendFeature[] = []; const alreadyMetFeatureLoaders: FrontendFeatureLoader[] = []; const maxRecursionDepth = 5; @@ -96,7 +70,7 @@ export async function resolveAsyncFeatures(options: { } } - await applyFeatureLoaders(features, 1); + await applyFeatureLoaders(options.features ?? [], 1); return { features: loadedFeatures }; } diff --git a/packages/frontend-dynamic-feature-loader/CHANGELOG.md b/packages/frontend-dynamic-feature-loader/CHANGELOG.md index 1b377ed777..1c18e5df60 100644 --- a/packages/frontend-dynamic-feature-loader/CHANGELOG.md +++ b/packages/frontend-dynamic-feature-loader/CHANGELOG.md @@ -1,5 +1,68 @@ # @backstage/frontend-dynamic-feature-loader +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/config@1.3.3 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/frontend-plugin-api@0.10.4-next.0 + +## 0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/config@1.3.2 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/config@1.3.2 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/config@1.3.2 + ## 0.1.1-next.1 ### Patch Changes diff --git a/packages/frontend-dynamic-feature-loader/catalog-info.yaml b/packages/frontend-dynamic-feature-loader/catalog-info.yaml index 3f9ef427ed..a44650174a 100644 --- a/packages/frontend-dynamic-feature-loader/catalog-info.yaml +++ b/packages/frontend-dynamic-feature-loader/catalog-info.yaml @@ -3,8 +3,9 @@ kind: Component metadata: name: backstage-frontend-dynamic-feature-loader title: '@backstage/frontend-dynamic-feature-loader' - description: Backstage frontend feature loader to load new frontend system plugins exposed as module federation remotes. + description: Backstage frontend feature loader to load new frontend system + plugins exposed as module federation remotes. spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-dynamic-feature-loader/knip-report.md b/packages/frontend-dynamic-feature-loader/knip-report.md index 2661c35327..3d019810db 100644 --- a/packages/frontend-dynamic-feature-loader/knip-report.md +++ b/packages/frontend-dynamic-feature-loader/knip-report.md @@ -1,2 +1,8 @@ # Knip report +## Unused dependencies (1) + +| Name | Location | Severity | +| :---------------- | :----------- | :------- | +| @backstage/config | package.json | error | + diff --git a/packages/frontend-dynamic-feature-loader/package.json b/packages/frontend-dynamic-feature-loader/package.json index d3b05fdc12..3d66dcb1eb 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-next.1", + "version": "0.1.4-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-dynamic-feature-loader/src/loader.test.tsx b/packages/frontend-dynamic-feature-loader/src/loader.test.tsx index 4b872a5414..b2f6486766 100644 --- a/packages/frontend-dynamic-feature-loader/src/loader.test.tsx +++ b/packages/frontend-dynamic-feature-loader/src/loader.test.tsx @@ -127,10 +127,8 @@ describe('dynamicFrontendFeaturesLoader', () => { config: mockApis.config({ data: { app: { - experimental: { - packages: { - include: [], - }, + packages: { + include: [], }, }, backend: { @@ -204,10 +202,8 @@ describe('dynamicFrontendFeaturesLoader', () => { config: mockApis.config({ data: { app: { - experimental: { - packages: { - include: [], - }, + packages: { + include: [], }, }, backend: { @@ -330,10 +326,8 @@ describe('dynamicFrontendFeaturesLoader', () => { config: mockApis.config({ data: { app: { - experimental: { - packages: { - include: [], - }, + packages: { + include: [], }, }, backend: { @@ -447,10 +441,8 @@ describe('dynamicFrontendFeaturesLoader', () => { config: mockApis.config({ data: { app: { - experimental: { - packages: { - include: [], - }, + packages: { + include: [], }, }, backend: { @@ -540,10 +532,8 @@ describe('dynamicFrontendFeaturesLoader', () => { config: mockApis.config({ data: { app: { - experimental: { - packages: { - include: [], - }, + packages: { + include: [], }, }, backend: { @@ -604,10 +594,8 @@ describe('dynamicFrontendFeaturesLoader', () => { config: mockApis.config({ data: { app: { - experimental: { - packages: { - include: [], - }, + packages: { + include: [], }, }, backend: { @@ -653,10 +641,8 @@ describe('dynamicFrontendFeaturesLoader', () => { config: mockApis.config({ data: { app: { - experimental: { - packages: { - include: [], - }, + packages: { + include: [], }, }, backend: { @@ -759,10 +745,8 @@ describe('dynamicFrontendFeaturesLoader', () => { config: mockApis.config({ data: { app: { - experimental: { - packages: { - include: [], - }, + packages: { + include: [], }, }, backend: { @@ -889,10 +873,8 @@ describe('dynamicFrontendFeaturesLoader', () => { config: mockApis.config({ data: { app: { - experimental: { - packages: { - include: [], - }, + packages: { + include: [], }, }, backend: { @@ -1002,10 +984,8 @@ describe('dynamicFrontendFeaturesLoader', () => { config: mockApis.config({ data: { app: { - experimental: { - packages: { - include: [], - }, + packages: { + include: [], }, }, backend: { @@ -1112,10 +1092,8 @@ describe('dynamicFrontendFeaturesLoader', () => { config: mockApis.config({ data: { app: { - experimental: { - packages: { - include: [], - }, + packages: { + include: [], }, }, backend: { diff --git a/packages/frontend-internal/CHANGELOG.md b/packages/frontend-internal/CHANGELOG.md index 65ffbd826f..00009b787a 100644 --- a/packages/frontend-internal/CHANGELOG.md +++ b/packages/frontend-internal/CHANGELOG.md @@ -1,5 +1,64 @@ # @internal/frontend +## 0.0.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 0.0.11 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.4 + +## 0.0.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 0.0.10 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 0.0.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 0.0.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + +## 0.0.9 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + ## 0.0.9-next.1 ### Patch Changes diff --git a/packages/frontend-internal/catalog-info.yaml b/packages/frontend-internal/catalog-info.yaml index 630f53275a..20dcb6c970 100644 --- a/packages/frontend-internal/catalog-info.yaml +++ b/packages/frontend-internal/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-internal/knip-report.md b/packages/frontend-internal/knip-report.md index d5272513d9..edbbb970ac 100644 --- a/packages/frontend-internal/knip-report.md +++ b/packages/frontend-internal/knip-report.md @@ -1,11 +1,10 @@ # Knip report -## Unused dependencies (3) +## Unused dependencies (2) | Name | Location | Severity | | :------------------------ | :----------- | :------- | | @backstage/version-bridge | package.json | error | -| @backstage/types | package.json | error | | zod | package.json | error | ## Unused devDependencies (5) diff --git a/packages/frontend-internal/package.json b/packages/frontend-internal/package.json index 7f1615c5f5..609bb05f52 100644 --- a/packages/frontend-internal/package.json +++ b/packages/frontend-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/frontend", - "version": "0.0.9-next.1", + "version": "0.0.12-next.0", "backstage": { "role": "web-library", "inline": true diff --git a/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts b/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts index 44dfb002ad..83d037902b 100644 --- a/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts +++ b/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts @@ -15,11 +15,11 @@ */ import { - AnyExtensionDataRef, ApiHolder, AppNode, ExtensionAttachToSpec, ExtensionDataValue, + ExtensionDataRef, ExtensionDefinition, ExtensionDefinitionParameters, ExtensionInput, @@ -43,13 +43,13 @@ export const OpaqueExtensionDefinition = OpaqueType.create<{ [inputName in string]: { $$type: '@backstage/ExtensionInput'; extensionData: { - [name in string]: AnyExtensionDataRef; + [name in string]: ExtensionDataRef; }; config: { optional: boolean; singleton: boolean }; }; }; readonly output: { - [name in string]: AnyExtensionDataRef; + [name in string]: ExtensionDataRef; }; factory(context: { node: AppNode; @@ -72,18 +72,18 @@ export const OpaqueExtensionDefinition = OpaqueType.create<{ readonly configSchema?: PortableSchema; readonly inputs: { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean } >; }; - readonly output: Array; + readonly output: Array; factory(context: { node: AppNode; apis: ApiHolder; config: object; inputs: ResolvedExtensionInputs<{ [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean } >; }>; 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-internal/src/wiring/InternalSwappableComponentRef.ts b/packages/frontend-internal/src/wiring/InternalSwappableComponentRef.ts new file mode 100644 index 0000000000..2a5da3fa73 --- /dev/null +++ b/packages/frontend-internal/src/wiring/InternalSwappableComponentRef.ts @@ -0,0 +1,30 @@ +/* + * 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 { SwappableComponentRef } from '@backstage/frontend-plugin-api'; +import { OpaqueType } from '@internal/opaque'; + +export const OpaqueSwappableComponentRef = OpaqueType.create<{ + public: SwappableComponentRef; + versions: { + readonly version: 'v1'; + readonly transformProps?: (props: object) => object; + readonly defaultComponent: (props: object) => JSX.Element | null; + }; +}>({ + versions: ['v1'], + type: '@backstage/SwappableComponentRef', +}); diff --git a/packages/frontend-internal/src/wiring/createExtensionDataContainer.ts b/packages/frontend-internal/src/wiring/createExtensionDataContainer.ts index e25846a643..34eeca75de 100644 --- a/packages/frontend-internal/src/wiring/createExtensionDataContainer.ts +++ b/packages/frontend-internal/src/wiring/createExtensionDataContainer.ts @@ -15,20 +15,24 @@ */ import { - AnyExtensionDataRef, ExtensionDataContainer, ExtensionDataRef, ExtensionDataValue, } from '@backstage/frontend-plugin-api'; -export function createExtensionDataContainer( +export function createExtensionDataContainer( values: Iterable< UData extends ExtensionDataRef ? ExtensionDataValue : never >, + contextName: string, declaredRefs?: ExtensionDataRef[], ): ExtensionDataContainer { + if (typeof values !== 'object' || !values?.[Symbol.iterator]) { + throw new Error(`${contextName} did not provide an iterable object`); + } + const container = new Map>(); const verifyRefs = declaredRefs && new Map(declaredRefs.map(ref => [ref.id, ref])); diff --git a/packages/frontend-internal/src/wiring/index.ts b/packages/frontend-internal/src/wiring/index.ts index 6a7cf35e27..b61294cb1f 100644 --- a/packages/frontend-internal/src/wiring/index.ts +++ b/packages/frontend-internal/src/wiring/index.ts @@ -15,5 +15,6 @@ */ export { createExtensionDataContainer } from './createExtensionDataContainer'; +export { OpaqueSwappableComponentRef } from './InternalSwappableComponentRef'; export { OpaqueExtensionDefinition } from './InternalExtensionDefinition'; export { OpaqueFrontendPlugin } from './InternalFrontendPlugin'; diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index ce9e73745f..82e52147ed 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,454 @@ # @backstage/frontend-plugin-api +## 0.11.0-next.2 + +### Minor Changes + +- fda1bbc: **BREAKING**: The component system has been overhauled to use `SwappableComponent` instead of `ComponentRef`. Several APIs have been removed and replaced: + + - Removed: `createComponentRef`, `createComponentExtension`, `ComponentRef`, `ComponentsApi`, `componentsApiRef`, `useComponentRef`, `coreComponentRefs` + - Added: `createSwappableComponent`, `SwappableComponentBlueprint`, `SwappableComponentRef`, `SwappableComponentsApi`, `swappableComponentsApiRef` + + **BREAKING**: The default `componentRefs` and exported `Core*Props` have been removed and have replacement `SwappableComponents` and revised type names instead. + + - The `errorBoundaryFallback` component and `CoreErrorBoundaryFallbackProps` type have been replaced with `ErrorDisplay` swappable component and `CoreErrorDisplayProps` respectively. + - The `progress` component and `CoreProgressProps` type have been replaced with `Progress` swappable component and `ProgressProps` respectively. + - The `notFoundErrorPage` component and `CoreNotFoundErrorPageProps` type have been replaced with `NotFoundErrorPage` swappable component and `NotFoundErrorPageProps` respectively. + + **Migration for creating swappable components:** + + ```tsx + // OLD: Using createComponentRef and createComponentExtension + import { + createComponentRef, + createComponentExtension, + } from '@backstage/frontend-plugin-api'; + + const myComponentRef = createComponentRef<{ title: string }>({ + id: 'my-plugin.my-component', + }); + + const myComponentExtension = createComponentExtension({ + ref: myComponentRef, + loader: { + lazy: () => import('./MyComponent').then(m => m.MyComponent), + }, + }); + + // NEW: Using createSwappableComponent and SwappableComponentBlueprint + import { + createSwappableComponent, + SwappableComponentBlueprint, + } from '@backstage/frontend-plugin-api'; + + const MySwappableComponent = createSwappableComponent({ + id: 'my-plugin.my-component', + loader: () => import('./MyComponent').then(m => m.MyComponent), + }); + + const myComponentExtension = SwappableComponentBlueprint.make({ + name: 'my-component', + params: { + component: MySwappableComponent, + loader: () => import('./MyComponent').then(m => m.MyComponent), + }, + }); + ``` + + **Migration for using components:** + + ```tsx + // OLD: Using ComponentsApi and useComponentRef + import { + useComponentRef, + componentsApiRef, + useApi, + coreComponentRefs, + } from '@backstage/frontend-plugin-api'; + + const MyComponent = useComponentRef(myComponentRef); + const ProgressComponent = useComponentRef(coreComponentRefs.progress); + + + // NEW: Direct component usage + import { Progress } from '@backstage/frontend-plugin-api'; + + // Use directly as React Component + + + ``` + + **Migration for core component references:** + + ```tsx + // OLD: Core component refs + import { coreComponentRefs } from '@backstage/frontend-plugin-api'; + + coreComponentRefs.progress + coreComponentRefs.notFoundErrorPage + coreComponentRefs.errorBoundaryFallback + + // NEW: Direct swappable component imports + import { Progress, NotFoundErrorPage, ErrorDisplay } from '@backstage/frontend-plugin-api'; + + // Use directly as React components + + + + ``` + +- 6a75e00: **BREAKING**: Removed the deprecated `createFrontendPlugin` variant where the plugin ID is passed via an `id` option. To update existing code, switch to using the `pluginId` option instead. +- 1e6410b: **BREAKING**: The `ResolveInputValueOverrides` type is no longer exported. + +### Patch Changes + +- 9831f4e: Adjusted the dialog API types to have more sensible defaults +- 1c2cc37: Improved runtime error message clarity when extension factories don't return an iterable object. +- 24558f0: Added inline documentation for `createExtension`, `createExtensionBlueprint`, `createFrontendPlugin`, and `createFrontendModule`. +- Updated dependencies + - @backstage/core-components@0.17.5-next.2 + +## 0.11.0-next.1 + +### Minor Changes + +- c5f88b5: **BREAKING**: Remove deprecated `source` property from the `AppNodeSpec` type, use `AppNodeSpec.plugin` instead. +- e4ddf22: **BREAKING**: The `defaultPath` param of `PageBlueprint` has been renamed to `path`. This change does not affect the compatibility of extensions created with older versions of this blueprint. +- 37f2989: **BREAKING**: Removed the `routable` property from `ExtensionBoundary`. This property was never needed in practice and is instead inferred from whether or not the extension outputs a route reference. It can be safely removed. +- 3243fa6: **BREAKING**: Removed the ability to define a default extension `name` in blueprints. This option had no practical purpose as blueprints already use the `kind` to identity the source of the extension. +- a082429: **BREAKING**: The separate `RouteResolutionApiResolveOptions` type has been removed. +- 5d31d66: **BREAKING**: In an attempt to align some of the API's around providing components to `Blueprints`, we've renamed the parameters for both the `RouterBlueprint` and `AppRootWrapperBlueprint` from `Component` to `component`. + + ```tsx + // old + RouterBlueprint.make({ + params: { + Component: ({ children }) =>
    {children}
    , + }, + }); + + // new + RouterBlueprint.make({ + params: { + component: ({ children }) =>
    {children}
    , + }, + }); + ``` + + ```tsx + // old + AppRootWrapperBlueprint.make({ + params: { + Component: ({ children }) =>
    {children}
    , + }, + }); + + // new + AppRootWrapperBlueprint.make({ + params: { + component: ({ children }) =>
    {children}
    , + }, + }); + ``` + + As part of this change, the type for `component` has also changed from `ComponentType>` to `(props: { children: ReactNode }) => JSX.Element | null` which is not breaking, just a little more reflective of the actual expected component. + +- 45ead4a: **BREAKING**: The `AnyRoutes` and `AnyExternalRoutes` types have been removed and their usage has been inlined instead. + + Existing usage can be replaced according to their previous definitions: + + ```ts + type AnyRoutes = { [name in string]: RouteRef | SubRouteRef }; + type AnyExternalRoutes = { [name in string]: ExternalRouteRef }; + ``` + +- 121899a: **BREAKING**: The `element` param for `AppRootElementBlueprint` no longer accepts a component. If you are currently passing a component such as `element: () => ` or `element: MyComponent`, simply switch to `element: `. +- a321f3b: **BREAKING**: The `CommonAnalyticsContext` has been removed, and inlined into `AnalyticsContextValue` instead. + +### Patch Changes + +- d9e00e3: Add support for a new `aliasFor` option for `createRouteRef`. This allows for the creation of a new route ref that acts as an alias for an existing route ref that is installed in the app. This is particularly useful when creating modules that override existing plugin pages, without referring to the existing plugin. For example: + + ```tsx + export default createFrontendModule({ + pluginId: 'catalog', + extensions: [ + PageBlueprint.make({ + params: { + defaultPath: '/catalog', + routeRef: createRouteRef({ aliasFor: 'catalog.catalogIndex' }), + loader: () => + import('./CustomCatalogIndexPage').then(m => ( + + )), + }, + }), + ], + }); + ``` + +- 93b5e38: Plugins should now use the new `AnalyticsImplementationBlueprint` to define and provide concrete analytics implementations. For example: + + ```ts + import { AnalyticsImplementationBlueprint } from '@backstage/frontend-plugin-api'; + + const AcmeAnalytics = AnalyticsImplementationBlueprint.make({ + name: 'acme-analytics', + params: define => + define({ + deps: { config: configApiRef }, + factory: ({ config }) => AcmeAnalyticsImpl.fromConfig(config), + }), + }); + ``` + +- 948de17: Tweaked the return types from `createExtension` and `createExtensionBlueprint` to avoid the forwarding of `ConfigurableExtensionDataRef` into exported types. +- 147482b: Updated the recommended naming of the blueprint param callback from `define` to `defineParams`, making the syntax `defineParams => defineParams(...)`. +- 3c3c882: Added added defaults for all type parameters of `ExtensionDataRef` and deprecated `AnyExtensionDataRef`, as it is now redundant. +- Updated dependencies + - @backstage/core-components@0.17.5-next.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 0.11.0-next.0 + +### Minor Changes + +- 29786f6: **BREAKING**: The `NavLogoBlueprint` has been removed and replaced by `NavContentBlueprint`, which instead replaces the entire navbar. The default navbar has also been switched to a more minimal implementation. + + To use `NavContentBlueprint` to install new logos, you can use it as follows: + + ```tsx + NavContentBlueprint.make({ + params: { + component: ({ items }) => { + return compatWrapper( + + + + {/* Other sidebar content */} + + + {items.map((item, index) => ( + + ))} + + + {/* Other sidebar content */} + , + ); + }, + }, + }); + ``` + +- 805c298: **BREAKING**: The `ApiBlueprint` has been updated to use the new advanced type parameters through the new `defineParams` blueprint option. This is an immediate breaking change that requires all existing usages of `ApiBlueprint` to switch to the new callback format. Existing extensions created with the old format are still compatible with the latest version of the plugin API however, meaning that this does not break existing plugins. + + To update existing usages of `ApiBlueprint`, you remove the outer level of the `params` object and replace `createApiFactory(...)` with `define => define(...)`. + + For example, the following old usage: + + ```ts + ApiBlueprint.make({ + name: 'error', + params: { + factory: createApiFactory({ + api: errorApiRef, + deps: { alertApi: alertApiRef }, + factory: ({ alertApi }) => { + return ...; + }, + }) + }, + }) + ``` + + is migrated to the following: + + ```ts + ApiBlueprint.make({ + name: 'error', + params: define => + define({ + api: errorApiRef, + deps: { alertApi: alertApiRef }, + factory: ({ alertApi }) => { + return ...; + }, + }), + }) + ``` + +- 805c298: Added support for advanced parameter types in extension blueprints. The primary purpose of this is to allow extension authors to use type inference in the definition of the blueprint parameters. This often removes the need for extra imports and improves discoverability of blueprint parameters. + + This feature is introduced through the new `defineParams` option of `createExtensionBlueprint`, along with accompanying `createExtensionBlueprintParams` function to help implement the new format. + + The following is an example of how to create an extension blueprint that uses the new option: + + ```ts + const ExampleBlueprint = createExtensionBlueprint({ + kind: 'example', + attachTo: { id: 'example', input: 'example' }, + output: [exampleComponentDataRef, exampleFetcherDataRef], + defineParams(params: { + component(props: ExampleProps): JSX.Element | null; + fetcher(options: FetchOptions): Promise>; + }) { + // The returned params must be wrapped with `createExtensionBlueprintParams` + return createExtensionBlueprintParams(params); + }, + *factory(params) { + // These params are now inferred + yield exampleComponentDataRef(params.component); + yield exampleFetcherDataRef(params.fetcher); + }, + }); + ``` + + Usage of the above example looks as follows: + + ```ts + const example = ExampleBlueprint.make({ + params: define => define({ + component: ..., + fetcher: ..., + }), + }); + ``` + + This `define => define()` is also known as the "callback syntax" and is required if a blueprint is created with the new `defineParams` option. The callback syntax can also optionally be used for other blueprints too, which means that it is not a breaking change to remove the `defineParams` option, as long as the external parameter types remain compatible. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 0.10.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + +## 0.10.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## 0.10.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 0.10.3 + +### Patch Changes + +- 0169b23: Internal tweak to avoid circular dependencies +- 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`. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 0.10.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 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 + +- 173db8f: The `source` property of `AppNodeSpec` has been renamed to `plugin`. The old property has been deprecated and will be removed in a future release. +- fb58f20: The `id` option of `createFrontendPlugin` has been renamed to `pluginId` in order to better align with similar APIs in the frontend and backend systems. + + The old `id` option is deprecated and will be removed in a future release. + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + ## 0.10.2-next.1 ### Patch Changes diff --git a/packages/frontend-plugin-api/catalog-info.yaml b/packages/frontend-plugin-api/catalog-info.yaml index 4531f6eca3..8ce7aacabd 100644 --- a/packages/frontend-plugin-api/catalog-info.yaml +++ b/packages/frontend-plugin-api/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 1e3161489c..0e818bc4dc 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-next.1", + "version": "0.11.0-next.2", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index cd5d89401d..469733987e 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -8,6 +8,7 @@ import { alertApiRef } from '@backstage/core-plugin-api'; import { AlertMessage } from '@backstage/core-plugin-api'; import { AnyApiFactory } from '@backstage/core-plugin-api'; import { AnyApiRef } from '@backstage/core-plugin-api'; +import { AnyRouteRefParams as AnyRouteRefParams_2 } from '@backstage/frontend-plugin-api'; import { ApiFactory } from '@backstage/core-plugin-api'; import { ApiHolder } from '@backstage/core-plugin-api'; import { ApiRef } from '@backstage/core-plugin-api'; @@ -26,6 +27,7 @@ import { bitbucketServerAuthApiRef } from '@backstage/core-plugin-api'; import { ComponentType } from 'react'; import { ConfigApi } from '@backstage/core-plugin-api'; import { configApiRef } from '@backstage/core-plugin-api'; +import { ConfigurableExtensionDataRef as ConfigurableExtensionDataRef_2 } from '@backstage/frontend-plugin-api'; import { createApiFactory } from '@backstage/core-plugin-api'; import { createApiRef } from '@backstage/core-plugin-api'; import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; @@ -38,6 +40,8 @@ import { ErrorApiError } from '@backstage/core-plugin-api'; import { ErrorApiErrorContext } from '@backstage/core-plugin-api'; import { errorApiRef } from '@backstage/core-plugin-api'; import { Expand } from '@backstage/types'; +import { ExtensionBlueprint as ExtensionBlueprint_2 } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef as ExtensionDataRef_2 } from '@backstage/frontend-plugin-api'; import { FeatureFlag } from '@backstage/core-plugin-api'; import { FeatureFlagsApi } from '@backstage/core-plugin-api'; import { featureFlagsApiRef } from '@backstage/core-plugin-api'; @@ -48,7 +52,8 @@ import { fetchApiRef } from '@backstage/core-plugin-api'; import { githubAuthApiRef } from '@backstage/core-plugin-api'; import { gitlabAuthApiRef } from '@backstage/core-plugin-api'; import { googleAuthApiRef } from '@backstage/core-plugin-api'; -import { IconComponent as IconComponent_2 } from '@backstage/core-plugin-api'; +import { IconComponent as IconComponent_2 } from '@backstage/frontend-plugin-api'; +import { IconComponent as IconComponent_3 } from '@backstage/core-plugin-api'; import { IdentityApi } from '@backstage/core-plugin-api'; import { identityApiRef } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; @@ -67,8 +72,8 @@ import { OpenIdConnectApi } from '@backstage/core-plugin-api'; import { PendingOAuthRequest } from '@backstage/core-plugin-api'; import { ProfileInfo } from '@backstage/core-plugin-api'; import { ProfileInfoApi } from '@backstage/core-plugin-api'; -import { PropsWithChildren } from 'react'; import { ReactNode } from 'react'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; import { SessionApi } from '@backstage/core-plugin-api'; import { SessionState } from '@backstage/core-plugin-api'; import { SignInPageProps } from '@backstage/core-plugin-api'; @@ -110,9 +115,12 @@ export const AnalyticsContext: (options: { }) => JSX_2.Element; // @public -export type AnalyticsContextValue = CommonAnalyticsContext & { - [param in string]: string | boolean | number | undefined; -}; +export interface AnalyticsContextValue { + // (undocumented) + [key: string]: string | boolean | number | undefined; + extensionId: string; + pluginId: string; +} // @public export type AnalyticsEvent = { @@ -128,6 +136,44 @@ export type AnalyticsEventAttributes = { [attribute in string]: string | boolean | number; }; +// @public +export type AnalyticsImplementation = { + captureEvent(event: AnalyticsEvent): void; +}; + +// @public +export const AnalyticsImplementationBlueprint: ExtensionBlueprint<{ + kind: 'analytics'; + params: ( + params: AnalyticsImplementationFactory, + ) => ExtensionBlueprintParams>; + output: ExtensionDataRef< + AnalyticsImplementationFactory<{}>, + 'core.analytics.factory', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + factory: ConfigurableExtensionDataRef< + AnalyticsImplementationFactory<{}>, + 'core.analytics.factory', + {} + >; + }; +}>; + +// @public (undocumented) +export type AnalyticsImplementationFactory< + Deps extends { + [name in string]: unknown; + } = {}, +> = { + deps: TypesToApiRefs; + factory(deps: Deps): AnalyticsImplementation; +}; + // @public export type AnalyticsTracker = { captureEvent: ( @@ -144,19 +190,8 @@ export { AnyApiFactory }; export { AnyApiRef }; -// @public (undocumented) -export type AnyExtensionDataRef = ExtensionDataRef< - unknown, - string, - { - optional?: true; - } ->; - -// @public (undocumented) -export type AnyExternalRoutes = { - [name in string]: ExternalRouteRef; -}; +// @public @deprecated (undocumented) +export type AnyExtensionDataRef = ExtensionDataRef; // @public export type AnyRouteRefParams = @@ -165,19 +200,17 @@ export type AnyRouteRefParams = } | undefined; -// @public (undocumented) -export type AnyRoutes = { - [name in string]: RouteRef | SubRouteRef; -}; - // @public export const ApiBlueprint: ExtensionBlueprint<{ kind: 'api'; - name: undefined; - params: { - factory: AnyApiFactory; - }; - output: ConfigurableExtensionDataRef; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; + output: ExtensionDataRef; inputs: {}; config: {}; configInput: {}; @@ -235,17 +268,16 @@ export interface AppNodeSpec { // (undocumented) readonly id: string; // (undocumented) - readonly source?: FrontendPlugin; + readonly plugin: FrontendPlugin; } // @public export const AppRootElementBlueprint: ExtensionBlueprint<{ kind: 'app-root-element'; - name: undefined; params: { - element: JSX.Element | (() => JSX.Element); + element: JSX.Element; }; - output: ConfigurableExtensionDataRef; + output: ExtensionDataRef; inputs: {}; config: {}; configInput: {}; @@ -255,14 +287,12 @@ export const AppRootElementBlueprint: ExtensionBlueprint<{ // @public export const AppRootWrapperBlueprint: ExtensionBlueprint<{ kind: 'app-root-wrapper'; - name: undefined; params: { - Component: ComponentType>; + Component?: [error: 'Use the `component` parameter instead']; + component: (props: { children: ReactNode }) => JSX.Element | null; }; - output: ConfigurableExtensionDataRef< - ComponentType<{ - children?: ReactNode | undefined; - }>, + output: ExtensionDataRef< + (props: { children: ReactNode }) => JSX.Element | null, 'app.root.wrapper', {} >; @@ -271,9 +301,7 @@ export const AppRootWrapperBlueprint: ExtensionBlueprint<{ configInput: {}; dataRefs: { component: ConfigurableExtensionDataRef< - ComponentType<{ - children?: ReactNode | undefined; - }>, + (props: { children: ReactNode }) => JSX.Element | null, 'app.root.wrapper', {} >; @@ -295,7 +323,7 @@ export interface AppTree { // @public export interface AppTreeApi { - getNodesByRoutePath(sourcePath: string): { + getNodesByRoutePath(routePath: string): { nodes: AppNode[]; }; getTree(): { @@ -322,27 +350,6 @@ export { bitbucketAuthApiRef }; export { bitbucketServerAuthApiRef }; -// @public -export type CommonAnalyticsContext = { - pluginId: string; - extensionId: string; -}; - -// @public (undocumented) -export type ComponentRef = { - id: string; - T: T; -}; - -// @public -export interface ComponentsApi { - // (undocumented) - getComponent(ref: ComponentRef): ComponentType; -} - -// @public -export const componentsApiRef: ApiRef; - export { ConfigApi }; export { configApiRef }; @@ -367,20 +374,6 @@ export interface ConfigurableExtensionDataRef< >; } -// @public (undocumented) -export const coreComponentRefs: { - progress: ComponentRef; - notFoundErrorPage: ComponentRef; - errorBoundaryFallback: ComponentRef; -}; - -// @public (undocumented) -export type CoreErrorBoundaryFallbackProps = { - plugin?: FrontendPlugin; - error: Error; - resetError: () => void; -}; - // @public (undocumented) export const coreExtensionData: { reactElement: ConfigurableExtensionDataRef< @@ -390,85 +383,22 @@ export const coreExtensionData: { >; routePath: ConfigurableExtensionDataRef; routeRef: ConfigurableExtensionDataRef< - RouteRef, + RouteRef, 'core.routing.ref', {} >; }; -// @public (undocumented) -export type CoreNotFoundErrorPageProps = { - children?: ReactNode; -}; - -// @public (undocumented) -export type CoreProgressProps = {}; - export { createApiFactory }; export { createApiRef }; -// @public (undocumented) -export function createComponentExtension(options: { - ref: ComponentRef; - name?: string; - disabled?: boolean; - loader: - | { - lazy: () => Promise>; - } - | { - sync: () => ComponentType; - }; -}): ExtensionDefinition<{ - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - { - ref: ComponentRef; - impl: ComponentType; - }, - 'core.component.component', - {} - >; - inputs: { - [x: string]: ExtensionInput< - AnyExtensionDataRef, - { - optional: boolean; - singleton: boolean; - } - >; - }; - params: never; - kind: 'component'; - name: string; -}>; - -// @public (undocumented) -export namespace createComponentExtension { - const // (undocumented) - componentDataRef: ConfigurableExtensionDataRef< - { - ref: ComponentRef; - impl: ComponentType; - }, - 'core.component.component', - {} - >; -} - -// @public (undocumented) -export function createComponentRef(options: { - id: string; -}): ComponentRef; - -// @public (undocumented) +// @public export function createExtension< - UOutput extends AnyExtensionDataRef, + UOutput extends ExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; @@ -503,7 +433,13 @@ export function createExtension< [key in keyof TConfigSchema]: ReturnType; }> >; - output: UOutput; + output: UOutput extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? ExtensionDataRef + : never; inputs: TInputs; params: never; kind: string | undefined extends TKind ? undefined : TKind; @@ -512,11 +448,11 @@ export function createExtension< // @public export function createExtensionBlueprint< - TParams extends object, - UOutput extends AnyExtensionDataRef, + TParams extends object | ExtensionBlueprintDefineParams, + UOutput extends ExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; @@ -528,14 +464,12 @@ export function createExtensionBlueprint< }, UFactoryOutput extends ExtensionDataValue, TKind extends string, - TName extends string | undefined = undefined, TDataRefs extends { - [name in string]: AnyExtensionDataRef; + [name in string]: ExtensionDataRef; } = never, >( options: CreateExtensionBlueprintOptions< TKind, - TName, TParams, UOutput, TInputs, @@ -545,9 +479,14 @@ export function createExtensionBlueprint< >, ): ExtensionBlueprint<{ kind: TKind; - name: TName; params: TParams; - output: UOutput; + output: UOutput extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? ExtensionDataRef + : never; inputs: string extends keyof TInputs ? {} : TInputs; config: string extends keyof TConfigSchema ? {} @@ -567,12 +506,11 @@ export function createExtensionBlueprint< // @public (undocumented) export type CreateExtensionBlueprintOptions< TKind extends string, - TName extends string | undefined, - TParams, - UOutput extends AnyExtensionDataRef, + TParams extends object | ExtensionBlueprintDefineParams, + UOutput extends ExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; @@ -584,7 +522,7 @@ export type CreateExtensionBlueprintOptions< }, UFactoryOutput extends ExtensionDataValue, TDataRefs extends { - [name in string]: AnyExtensionDataRef; + [name in string]: ExtensionDataRef; }, > = { kind: TKind; @@ -592,12 +530,16 @@ export type CreateExtensionBlueprintOptions< disabled?: boolean; inputs?: TInputs; output: Array; - name?: TName; config?: { schema: TConfigSchema; }; + defineParams?: TParams extends ExtensionBlueprintDefineParams + ? TParams + : 'The defineParams option must be a function if provided, see the docs for details'; factory( - params: TParams, + params: TParams extends ExtensionBlueprintDefineParams + ? ReturnType['T'] + : TParams, context: { node: AppNode; apis: ApiHolder; @@ -610,6 +552,11 @@ export type CreateExtensionBlueprintOptions< dataRefs?: TDataRefs; } & VerifyExtensionFactoryOutput; +// @public +export function createExtensionBlueprintParams( + params: T, +): ExtensionBlueprintParams; + // @public (undocumented) export function createExtensionDataRef(): { with(options: { @@ -650,10 +597,10 @@ export function createExtensionInput< export type CreateExtensionOptions< TKind extends string | undefined, TName extends string | undefined, - UOutput extends AnyExtensionDataRef, + UOutput extends ExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; @@ -741,7 +688,7 @@ export interface CreateFrontendFeatureLoaderOptions { >; } -// @public (undocumented) +// @public export function createFrontendModule< TId extends string, TExtensions extends readonly ExtensionDefinition[] = [], @@ -760,11 +707,15 @@ export interface CreateFrontendModuleOptions< pluginId: TPluginId; } -// @public (undocumented) +// @public export function createFrontendPlugin< TId extends string, - TRoutes extends AnyRoutes = {}, - TExternalRoutes extends AnyExternalRoutes = {}, + TRoutes extends { + [name in string]: RouteRef | SubRouteRef; + } = {}, + TExternalRoutes extends { + [name in string]: ExternalRouteRef; + } = {}, TExtensions extends readonly ExtensionDefinition[] = [], >( options: PluginOptions, @@ -774,25 +725,6 @@ export function createFrontendPlugin< MakeSortedExtensionsMap >; -// @public @deprecated (undocumented) -export function createFrontendPlugin< - TId extends string, - TRoutes extends AnyRoutes = {}, - TExternalRoutes extends AnyExternalRoutes = {}, - TExtensions extends readonly ExtensionDefinition[] = [], ->( - options: Omit< - PluginOptions, - 'pluginId' - > & { - id: string; - }, -): FrontendPlugin< - TRoutes, - TExternalRoutes, - MakeSortedExtensionsMap ->; - // @public export function createRouteRef< TParams extends @@ -802,7 +734,10 @@ export function createRouteRef< | undefined = undefined, TParamKeys extends string = string, >(config?: { - readonly params: string extends TParamKeys ? (keyof TParams)[] : TParamKeys[]; + readonly params?: string extends TParamKeys + ? (keyof TParams)[] + : TParamKeys[]; + aliasFor?: string; }): RouteRef< keyof TParams extends never ? undefined @@ -822,6 +757,32 @@ export function createSubRouteRef< parent: RouteRef; }): MakeSubRouteRef, ParentParams>; +// @public +export function createSwappableComponent< + TInnerComponentProps extends {}, + TExternalComponentProps extends {} = TInnerComponentProps, +>( + options: CreateSwappableComponentOptions< + TInnerComponentProps, + TExternalComponentProps + >, +): { + (props: TExternalComponentProps): JSX.Element | null; + ref: SwappableComponentRef; +}; + +// @public +export type CreateSwappableComponentOptions< + TInnerComponentProps extends {}, + TExternalComponentProps extends {} = TInnerComponentProps, +> = { + id: string; + loader?: + | (() => (props: TInnerComponentProps) => JSX.Element | null) + | (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>); + transformProps?: (props: TExternalComponentProps) => TInnerComponentProps; +}; + export { createTranslationMessages }; export { createTranslationRef }; @@ -830,14 +791,14 @@ export { createTranslationResource }; // @public export interface DialogApi { - show( + show( elementOrComponent: | JSX.Element | ((props: { dialog: DialogApiDialog; }) => JSX.Element), ): DialogApiDialog; - showModal( + showModal( elementOrComponent: | JSX.Element | ((props: { dialog: DialogApiDialog }) => JSX.Element), @@ -845,7 +806,7 @@ export interface DialogApi { } // @public -export interface DialogApiDialog { +export interface DialogApiDialog { close( ...args: undefined extends TResult ? [result?: TResult] : [result: TResult] ): void; @@ -872,6 +833,19 @@ export { ErrorApiErrorContext }; export { errorApiRef }; +// @public (undocumented) +export const ErrorDisplay: { + (props: ErrorDisplayProps): JSX.Element | null; + ref: SwappableComponentRef; +}; + +// @public (undocumented) +export type ErrorDisplayProps = { + plugin?: FrontendPlugin; + error: Error; + resetError: () => void; +}; + // @public (undocumented) export interface Extension { // (undocumented) @@ -904,14 +878,21 @@ export interface ExtensionBlueprint< // (undocumented) dataRefs: T['dataRefs']; // (undocumented) - make(args: { - name?: TNewName; + make< + TName extends string | undefined, + TParamsInput extends AnyParamsInput_2>, + >(args: { + name?: TName; attachTo?: ExtensionAttachToSpec; disabled?: boolean; - params: T['params']; + params: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `.make({ params: defineParams => defineParams() })`' + : T['params']; }): ExtensionDefinition<{ kind: T['kind']; - name: string | undefined extends TNewName ? T['name'] : TNewName; + name: string | undefined extends TName ? undefined : TName; config: T['config']; configInput: T['configInput']; output: T['output']; @@ -919,15 +900,15 @@ export interface ExtensionBlueprint< params: T['params']; }>; makeWithOverrides< - TNewName extends string | undefined, + TName extends string | undefined, TExtensionConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType; }, UFactoryOutput extends ExtensionDataValue, - UNewOutput extends AnyExtensionDataRef, + UNewOutput extends ExtensionDataRef, TExtraInputs extends { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; @@ -935,7 +916,7 @@ export interface ExtensionBlueprint< >; }, >(args: { - name?: TNewName; + name?: TName; attachTo?: ExtensionAttachToSpec; disabled?: boolean; inputs?: TExtraInputs & { @@ -950,11 +931,17 @@ export interface ExtensionBlueprint< }; }; factory( - originalFactory: ( - params: T['params'], + originalFactory: < + TParamsInput extends AnyParamsInput_2>, + >( + params: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : T['params'], context?: { config?: T['config']; - inputs?: ResolveInputValueOverrides>; + inputs?: ResolvedInputValueOverrides>; }, ) => ExtensionDataContainer>, context: { @@ -969,7 +956,7 @@ export interface ExtensionBlueprint< }, ): Iterable & VerifyExtensionFactoryOutput< - AnyExtensionDataRef extends UNewOutput + ExtensionDataRef extends UNewOutput ? NonNullable : UNewOutput, UFactoryOutput @@ -993,29 +980,34 @@ export interface ExtensionBlueprint< }> >) & T['configInput']; - output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; inputs: T['inputs'] & TExtraInputs; kind: T['kind']; - name: string | undefined extends TNewName ? T['name'] : TNewName; + name: string | undefined extends TName ? undefined : TName; params: T['params']; }>; } +// @public +export type ExtensionBlueprintDefineParams< + TParams extends object = object, + TInput = any, +> = (params: TInput) => ExtensionBlueprintParams; + // @public (undocumented) export type ExtensionBlueprintParameters = { kind: string; - name?: string; - params?: object; + params?: object | ExtensionBlueprintDefineParams; configInput?: { [K in string]: any; }; config?: { [K in string]: any; }; - output?: AnyExtensionDataRef; + output?: ExtensionDataRef; inputs?: { [KName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; @@ -1023,10 +1015,16 @@ export type ExtensionBlueprintParameters = { >; }; dataRefs?: { - [name in string]: AnyExtensionDataRef; + [name in string]: ExtensionDataRef; }; }; +// @public +export type ExtensionBlueprintParams = { + $$type: '@backstage/BlueprintParams'; + T: T; +}; + // @public (undocumented) export function ExtensionBoundary(props: ExtensionBoundaryProps): JSX_2.Element; @@ -1050,11 +1048,10 @@ export interface ExtensionBoundaryProps { children: ReactNode; // (undocumented) node: AppNode; - routable?: boolean; } // @public (undocumented) -export type ExtensionDataContainer = +export type ExtensionDataContainer = Iterable< UExtensionData extends ExtensionDataRef< infer IData, @@ -1077,11 +1074,13 @@ export type ExtensionDataContainer = // @public (undocumented) export type ExtensionDataRef< - TData, + TData = unknown, TId extends string = string, TConfig extends { optional?: true; - } = {}, + } = { + optional?: true; + }, > = { readonly $$type: '@backstage/ExtensionDataRef'; readonly id: TId; @@ -1113,16 +1112,17 @@ export type ExtensionDefinition< [key in string]: (zImpl: typeof z) => z.ZodType; }, UFactoryOutput extends ExtensionDataValue, - UNewOutput extends AnyExtensionDataRef, + UNewOutput extends ExtensionDataRef, TExtraInputs extends { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; } >; }, + TParamsInput extends AnyParamsInput>, >( args: Expand< { @@ -1140,15 +1140,23 @@ export type ExtensionDefinition< }; }; factory?( - originalFactory: ( + originalFactory: < + TFactoryParamsReturn extends AnyParamsInput< + NonNullable + >, + >( context?: Expand< { config?: T['config']; - inputs?: ResolveInputValueOverrides>; + inputs?: ResolvedInputValueOverrides>; } & ([T['params']] extends [never] ? {} : { - params?: Partial; + params?: TFactoryParamsReturn extends ExtensionBlueprintDefineParams + ? TFactoryParamsReturn + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; }) >, ) => ExtensionDataContainer>, @@ -1166,11 +1174,15 @@ export type ExtensionDefinition< } & ([T['params']] extends [never] ? {} : { - params?: Partial; + params?: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; }) > & VerifyExtensionFactoryOutput< - AnyExtensionDataRef extends UNewOutput + ExtensionDataRef extends UNewOutput ? NonNullable : UNewOutput, UFactoryOutput @@ -1178,7 +1190,7 @@ export type ExtensionDefinition< ): ExtensionDefinition<{ kind: T['kind']; name: T['name']; - output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; inputs: T['inputs'] & TExtraInputs; config: T['config'] & { [key in keyof TExtensionConfigSchema]: z.infer< @@ -1206,24 +1218,24 @@ export type ExtensionDefinitionParameters = { config?: { [K in string]: any; }; - output?: AnyExtensionDataRef; + output?: ExtensionDataRef; inputs?: { [KName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; } >; }; - params?: object; + params?: object | ExtensionBlueprintDefineParams; }; // @public (undocumented) export type ExtensionFactoryMiddleware = ( originalFactory: (contextOverrides?: { config?: JsonObject; - }) => ExtensionDataContainer, + }) => ExtensionDataContainer, context: { node: AppNode; apis: ApiHolder; @@ -1306,8 +1318,16 @@ export interface FrontendModule { // @public (undocumented) export interface FrontendPlugin< - TRoutes extends AnyRoutes = AnyRoutes, - TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, + TRoutes extends { + [name in string]: RouteRef | SubRouteRef; + } = { + [name in string]: RouteRef | SubRouteRef; + }, + TExternalRoutes extends { + [name in string]: ExternalRouteRef; + } = { + [name in string]: ExternalRouteRef; + }, TExtensionMap extends { [id in string]: ExtensionDefinition; } = { @@ -1322,14 +1342,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 }; @@ -1339,11 +1383,10 @@ export { googleAuthApiRef }; // @public (undocumented) export const IconBundleBlueprint: ExtensionBlueprint<{ kind: 'icon-bundle'; - name: undefined; params: { icons: { [key in string]: IconComponent }; }; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { [x: string]: IconComponent; }, @@ -1386,19 +1429,57 @@ export { identityApiRef }; export { microsoftAuthApiRef }; +// @public +export const NavContentBlueprint: ExtensionBlueprint_2<{ + kind: 'nav-content'; + params: { + component: NavContentComponent; + }; + output: ExtensionDataRef_2< + NavContentComponent, + 'core.nav-content.component', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + component: ConfigurableExtensionDataRef_2< + NavContentComponent, + 'core.nav-content.component', + {} + >; + }; +}>; + +// @public +export type NavContentComponent = ( + props: NavContentComponentProps, +) => JSX.Element | null; + +// @public +export interface NavContentComponentProps { + items: Array<{ + icon: IconComponent_2; + title: string; + routeRef: RouteRef_2; + to: string; + text: string; + }>; +} + // @public export const NavItemBlueprint: ExtensionBlueprint<{ kind: 'nav-item'; - name: undefined; params: { title: string; - icon: IconComponent_2; + icon: IconComponent_3; routeRef: RouteRef; }; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { title: string; - icon: IconComponent_2; + icon: IconComponent_3; routeRef: RouteRef; }, 'core.nav-item.target', @@ -1411,7 +1492,7 @@ export const NavItemBlueprint: ExtensionBlueprint<{ target: ConfigurableExtensionDataRef< { title: string; - icon: IconComponent_2; + icon: IconComponent_3; routeRef: RouteRef; }, 'core.nav-item.target', @@ -1420,36 +1501,16 @@ export const NavItemBlueprint: ExtensionBlueprint<{ }; }>; -// @public -export const NavLogoBlueprint: ExtensionBlueprint<{ - kind: 'nav-logo'; - name: undefined; - params: { - logoIcon: JSX.Element; - logoFull: JSX.Element; - }; - output: ConfigurableExtensionDataRef< - { - logoIcon?: JSX.Element; - logoFull?: JSX.Element; - }, - 'core.nav-logo.logo-elements', - {} - >; - inputs: {}; - config: {}; - configInput: {}; - dataRefs: { - logoElements: ConfigurableExtensionDataRef< - { - logoIcon?: JSX.Element; - logoFull?: JSX.Element; - }, - 'core.nav-logo.logo-elements', - {} - >; - }; -}>; +// @public (undocumented) +export const NotFoundErrorPage: { + (props: NotFoundErrorPageProps): JSX.Element | null; + ref: SwappableComponentRef; +}; + +// @public (undocumented) +export type NotFoundErrorPageProps = { + children?: ReactNode; +}; export { OAuthApi }; @@ -1472,16 +1533,16 @@ export { OpenIdConnectApi }; // @public export const PageBlueprint: ExtensionBlueprint<{ kind: 'page'; - name: undefined; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise; routeRef?: RouteRef; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { @@ -1503,8 +1564,12 @@ export { PendingOAuthRequest }; // @public (undocumented) export interface PluginOptions< TId extends string, - TRoutes extends AnyRoutes, - TExternalRoutes extends AnyExternalRoutes, + TRoutes extends { + [name in string]: RouteRef | SubRouteRef; + }, + TExternalRoutes extends { + [name in string]: ExternalRouteRef; + }, TExtensions extends readonly ExtensionDefinition[], > { // (undocumented) @@ -1514,6 +1579,8 @@ export interface PluginOptions< // (undocumented) featureFlags?: FeatureFlagConfig[]; // (undocumented) + info?: FrontendPluginInfoOptions; + // (undocumented) pluginId: TId; // (undocumented) routes?: TRoutes; @@ -1529,10 +1596,19 @@ export { ProfileInfo }; export { ProfileInfoApi }; +// @public (undocumented) +export const Progress: { + (props: ProgressProps): JSX.Element | null; + ref: SwappableComponentRef; +}; + +// @public (undocumented) +export type ProgressProps = {}; + // @public export type ResolvedExtensionInput< TExtensionInput extends ExtensionInput, -> = TExtensionInput['extensionData'] extends Array +> = TExtensionInput['extensionData'] extends Array ? { node: AppNode; } & ExtensionDataContainer @@ -1551,73 +1627,6 @@ export type ResolvedExtensionInputs< : Expand | undefined>; }; -// @public (undocumented) -export type ResolveInputValueOverrides< - TInputs extends { - [inputName in string]: ExtensionInput< - AnyExtensionDataRef, - { - optional: boolean; - singleton: boolean; - } - >; - } = { - [inputName in string]: ExtensionInput< - AnyExtensionDataRef, - { - optional: boolean; - singleton: boolean; - } - >; - }, -> = Expand< - { - [KName in keyof TInputs as TInputs[KName] extends ExtensionInput< - any, - { - optional: infer IOptional extends boolean; - singleton: boolean; - } - > - ? IOptional extends true - ? never - : KName - : never]: TInputs[KName] extends ExtensionInput< - infer IDataRefs, - { - optional: boolean; - singleton: infer ISingleton extends boolean; - } - > - ? ISingleton extends true - ? Iterable> - : Array>> - : never; - } & { - [KName in keyof TInputs as TInputs[KName] extends ExtensionInput< - any, - { - optional: infer IOptional extends boolean; - singleton: boolean; - } - > - ? IOptional extends true - ? KName - : never - : never]?: TInputs[KName] extends ExtensionInput< - infer IDataRefs, - { - optional: boolean; - singleton: infer ISingleton extends boolean; - } - > - ? ISingleton extends true - ? Iterable> - : Array>> - : never; - } ->; - // @public export type RouteFunc = ( ...[params]: TParams extends undefined @@ -1628,14 +1637,12 @@ export type RouteFunc = ( // @public (undocumented) export const RouterBlueprint: ExtensionBlueprint<{ kind: 'app-router-component'; - name: undefined; params: { - Component: ComponentType>; + Component?: [error: 'Use the `component` parameter instead']; + component: (props: { children: ReactNode }) => JSX.Element | null; }; - output: ConfigurableExtensionDataRef< - ComponentType<{ - children?: ReactNode | undefined; - }>, + output: ExtensionDataRef< + (props: { children: ReactNode }) => JSX.Element | null, 'app.router.wrapper', {} >; @@ -1644,9 +1651,7 @@ export const RouterBlueprint: ExtensionBlueprint<{ configInput: {}; dataRefs: { component: ConfigurableExtensionDataRef< - ComponentType<{ - children?: ReactNode | undefined; - }>, + (props: { children: ReactNode }) => JSX.Element | null, 'app.router.wrapper', {} >; @@ -1671,18 +1676,15 @@ export interface RouteResolutionApi { | RouteRef | SubRouteRef | ExternalRouteRef, - options?: RouteResolutionApiResolveOptions, + options?: { + sourcePath?: string; + }, ): RouteFunc | undefined; } // @public export const routeResolutionApiRef: ApiRef; -// @public (undocumented) -export type RouteResolutionApiResolveOptions = { - sourcePath?: string; -}; - export { SessionApi }; export { SessionState }; @@ -1690,11 +1692,10 @@ export { SessionState }; // @public export const SignInPageBlueprint: ExtensionBlueprint<{ kind: 'sign-in-page'; - name: undefined; params: { loader: () => Promise>; }; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< ComponentType, 'core.sign-in-page.component', {} @@ -1729,14 +1730,97 @@ export interface SubRouteRef< readonly T: TParams; } +// @public +export const SwappableComponentBlueprint: ExtensionBlueprint<{ + kind: 'component'; + params: >(params: { + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > + ? { + ref: Ref; + } & ((props: IExternalComponentProps) => JSX.Element | null) + : never; + loader: Ref extends SwappableComponentRef + ? + | (() => (props: IInnerComponentProps) => JSX.Element | null) + | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) + : never; + }) => ExtensionBlueprintParams<{ + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > + ? { + ref: Ref; + } & ((props: IExternalComponentProps) => JSX.Element | null) + : never; + loader: Ref extends SwappableComponentRef + ? + | (() => (props: IInnerComponentProps) => JSX.Element | null) + | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) + : never; + }>; + output: ExtensionDataRef< + { + ref: SwappableComponentRef; + loader: + | (() => (props: {}) => JSX.Element | null) + | (() => Promise<(props: {}) => JSX.Element | null>); + }, + 'core.swappableComponent', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + component: ConfigurableExtensionDataRef< + { + ref: SwappableComponentRef; + loader: + | (() => (props: {}) => JSX.Element | null) + | (() => Promise<(props: {}) => JSX.Element | null>); + }, + 'core.swappableComponent', + {} + >; + }; +}>; + +// @public (undocumented) +export type SwappableComponentRef< + TInnerComponentProps extends {} = {}, + TExternalComponentProps extends {} = TInnerComponentProps, +> = { + id: string; + TProps: TInnerComponentProps; + TExternalProps: TExternalComponentProps; + $$type: '@backstage/SwappableComponentRef'; +}; + +// @public +export interface SwappableComponentsApi { + // (undocumented) + getComponent< + TInnerComponentProps extends {}, + TExternalComponentProps extends {} = TInnerComponentProps, + >( + ref: SwappableComponentRef, + ): (props: TInnerComponentProps) => JSX.Element | null; +} + +// @public +export const swappableComponentsApiRef: ApiRef; + // @public export const ThemeBlueprint: ExtensionBlueprint<{ kind: 'theme'; - name: undefined; params: { theme: AppTheme; }; - output: ConfigurableExtensionDataRef; + output: ExtensionDataRef; inputs: {}; config: {}; configInput: {}; @@ -1748,11 +1832,10 @@ export const ThemeBlueprint: ExtensionBlueprint<{ // @public export const TranslationBlueprint: ExtensionBlueprint<{ kind: 'translation'; - name: undefined; params: { resource: TranslationResource | TranslationMessages; }; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< | TranslationResource | TranslationMessages< string, @@ -1805,9 +1888,7 @@ export { useApi }; export { useApiHolder }; // @public -export function useComponentRef( - ref: ComponentRef, -): ComponentType; +export function useAppNode(): AppNode | undefined; // @public export function useRouteRef( diff --git a/packages/frontend-plugin-api/src/analytics/index.ts b/packages/frontend-plugin-api/src/analytics/index.ts index 651e8e105e..988a4e0163 100644 --- a/packages/frontend-plugin-api/src/analytics/index.ts +++ b/packages/frontend-plugin-api/src/analytics/index.ts @@ -15,5 +15,5 @@ */ export { AnalyticsContext } from './AnalyticsContext'; -export type { AnalyticsContextValue, CommonAnalyticsContext } from './types'; +export type { AnalyticsContextValue } from './types'; export { useAnalytics } from './useAnalytics'; diff --git a/packages/frontend-plugin-api/src/analytics/types.ts b/packages/frontend-plugin-api/src/analytics/types.ts index 3e3a9ad5eb..e9628dc628 100644 --- a/packages/frontend-plugin-api/src/analytics/types.ts +++ b/packages/frontend-plugin-api/src/analytics/types.ts @@ -15,11 +15,11 @@ */ /** - * Common analytics context attributes. + * Analytics context envelope. * * @public */ -export type CommonAnalyticsContext = { +export interface AnalyticsContextValue { /** * The nearest known parent plugin where the event was captured. */ @@ -29,13 +29,6 @@ export type CommonAnalyticsContext = { * The nearest known parent extension where the event was captured. */ extensionId: string; -}; -/** - * Analytics context envelope. - * - * @public - */ -export type AnalyticsContextValue = CommonAnalyticsContext & { - [param in string]: string | boolean | number | undefined; -}; + [key: string]: string | boolean | number | undefined; +} diff --git a/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts index 15b09482b7..2040966464 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts @@ -16,6 +16,7 @@ import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; import { AnalyticsContextValue } from '../../analytics/types'; +import type { AnalyticsImplementationBlueprint } from '../../blueprints/'; /** * Represents an event worth tracking in an analytics system that could inform @@ -102,6 +103,26 @@ export type AnalyticsTracker = { ) => void; }; +/** + * Analytics implementations are used to track user behavior in a Backstage + * instance. + * + * @remarks + * + * To instrument your App or Plugin, retrieve an analytics tracker using the + * `useAnalytics()` hook. This will return a pre-configured `AnalyticsTracker` + * with relevant methods for instrumentation. + * + * @public + */ +export type AnalyticsImplementation = { + /** + * Primary event handler responsible for compiling and forwarding events to + * an analytics system. + */ + captureEvent(event: AnalyticsEvent): void; +}; + /** * The Analytics API is used to track user behavior in a Backstage instance. * @@ -124,6 +145,11 @@ export type AnalyticsApi = { /** * The API reference of {@link AnalyticsApi}. * + * @remarks + * + * To define a concrete Analytics Implementation, use + * {@link AnalyticsImplementationBlueprint} instead. + * * @public */ export const analyticsApiRef: ApiRef = createApiRef({ diff --git a/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts index c9d9896600..f4fd2f3a6f 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts @@ -37,7 +37,7 @@ export interface AppNodeSpec { readonly extension: Extension; readonly disabled: boolean; readonly config?: unknown; - readonly source?: FrontendPlugin; + readonly plugin: FrontendPlugin; } /** @@ -113,7 +113,7 @@ export interface AppTreeApi { /** * Get all nodes in the app that are mounted at a given route path. */ - getNodesByRoutePath(sourcePath: string): { nodes: AppNode[] }; + getNodesByRoutePath(routePath: string): { nodes: AppNode[] }; } /** diff --git a/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts deleted file mode 100644 index c5e9d2c44f..0000000000 --- a/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts +++ /dev/null @@ -1,49 +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 { ComponentType } from 'react'; -import { createApiRef, useApi } from '@backstage/core-plugin-api'; -import { ComponentRef } from '../../components'; - -/** - * API for looking up components based on component refs. - * - * @public - */ -export interface ComponentsApi { - // TODO: Should component refs also provide the default implementation so that we're guaranteed to get a component? - getComponent(ref: ComponentRef): ComponentType; -} - -/** - * The `ApiRef` of {@link ComponentsApi}. - * - * @public - */ -export const componentsApiRef = createApiRef({ - id: 'core.components', -}); - -/** - * @public - * Returns the component associated with the given ref. - */ -export function useComponentRef( - ref: ComponentRef, -): ComponentType { - const componentsApi = useApi(componentsApiRef); - return componentsApi.getComponent(ref); -} diff --git a/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts b/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts index 71cd70d3aa..801ec88c69 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts @@ -25,7 +25,7 @@ import { createApiRef } from '@backstage/core-plugin-api'; * * @public */ -export interface DialogApiDialog { +export interface DialogApiDialog { /** * Closes the dialog with that provided result. * @@ -109,7 +109,7 @@ export interface DialogApi { * @param elementOrComponent - The element or component to render in the dialog. If a component is provided, it will be provided with a `dialog` prop that contains the dialog handle. * @public */ - show( + show( elementOrComponent: | JSX.Element | ((props: { @@ -161,7 +161,7 @@ export interface DialogApi { * @param elementOrComponent - The element or component to render in the dialog. If a component is provided, it will be provided with a `dialog` prop that contains the dialog handle. * @public */ - showModal( + showModal( elementOrComponent: | JSX.Element | ((props: { dialog: DialogApiDialog }) => JSX.Element), diff --git a/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts b/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts index 30569bde15..6ae25a814e 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts @@ -41,17 +41,6 @@ export type RouteFunc = ( : readonly [params: TParams] ) => string; -/** - * @public - */ -export type RouteResolutionApiResolveOptions = { - /** - * An absolute path to use as a starting point when resolving the route. - * If no path is provided the route will be resolved from the root of the app. - */ - sourcePath?: string; -}; - /** * @public */ @@ -61,7 +50,13 @@ export interface RouteResolutionApi { | RouteRef | SubRouteRef | ExternalRouteRef, - options?: RouteResolutionApiResolveOptions, + options?: { + /** + * An absolute path to use as a starting point when resolving the route. + * If no path is provided the route will be resolved from the root of the app. + */ + sourcePath?: string; + }, ): RouteFunc | undefined; } diff --git a/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts new file mode 100644 index 0000000000..ed2b20ce80 --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts @@ -0,0 +1,41 @@ +/* + * 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 { SwappableComponentRef } from '../../components'; +import { createApiRef } from '@backstage/core-plugin-api'; + +/** + * API for looking up components based on component refs. + * + * @public + */ +export interface SwappableComponentsApi { + getComponent< + TInnerComponentProps extends {}, + TExternalComponentProps extends {} = TInnerComponentProps, + >( + ref: SwappableComponentRef, + ): (props: TInnerComponentProps) => JSX.Element | null; +} + +/** + * The `ApiRef` of {@link SwappableComponentsApi}. + * + * @public + */ +export const swappableComponentsApiRef = createApiRef({ + id: 'core.swappable-components', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/index.ts b/packages/frontend-plugin-api/src/apis/definitions/index.ts index 0da23fcacb..7533481d01 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/index.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/index.ts @@ -34,7 +34,7 @@ export * from './auth'; export * from './AlertApi'; export * from './AppThemeApi'; -export * from './ComponentsApi'; +export * from './SwappableComponentsApi'; export * from './ConfigApi'; export * from './DiscoveryApi'; export * from './ErrorApi'; diff --git a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.test.ts similarity index 53% rename from packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx rename to packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.test.ts index bbd96ea53b..b297b3a3a9 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,32 +14,36 @@ * limitations under the License. */ -import { NavLogoBlueprint } from './NavLogoBlueprint'; -import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { AnalyticsImplementationBlueprint } from './AnalyticsImplementationBlueprint'; -describe('NavLogoBlueprint', () => { +describe('AnalyticsBlueprint', () => { it('should create an extension with sensible defaults', () => { - const extension = NavLogoBlueprint.make({ - params: { - logoFull:
    Logo Full
    , - logoIcon:
    Logo Icon
    , - }, + const factory = { + deps: {}, + factory: () => ({ captureEvent: () => {} }), + }; + + const extension = AnalyticsImplementationBlueprint.make({ + params: define => define(factory), + name: 'test', }); expect(extension).toMatchInlineSnapshot(` { "$$type": "@backstage/ExtensionDefinition", "T": undefined, - "attachTo": { - "id": "app/nav", - "input": "logos", - }, + "attachTo": [ + { + "id": "api:app/analytics", + "input": "implementations", + }, + ], "configSchema": undefined, "disabled": false, "factory": [Function], "inputs": {}, - "kind": "nav-logo", - "name": undefined, + "kind": "analytics", + "name": "test", "output": [ [Function], ], @@ -49,24 +53,4 @@ describe('NavLogoBlueprint', () => { } `); }); - - it('should return a valid component ref', () => { - const logoFull =
    Logo Full
    ; - const logoIcon =
    Logo Icon
    ; - - const extension = NavLogoBlueprint.make({ - name: 'test', - params: { - logoFull, - logoIcon, - }, - }); - - const tester = createExtensionTester(extension); - - expect(tester.get(NavLogoBlueprint.dataRefs.logoElements)).toEqual({ - logoFull, - logoIcon, - }); - }); }); diff --git a/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts new file mode 100644 index 0000000000..91a0f27092 --- /dev/null +++ b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AnalyticsImplementation, TypesToApiRefs } from '../apis'; +import { + createExtensionBlueprint, + createExtensionBlueprintParams, + createExtensionDataRef, +} from '../wiring'; + +/** @public */ +export type AnalyticsImplementationFactory< + Deps extends { [name in string]: unknown } = {}, +> = { + deps: TypesToApiRefs; + factory(deps: Deps): AnalyticsImplementation; +}; + +const factoryDataRef = + createExtensionDataRef().with({ + id: 'core.analytics.factory', + }); + +/** + * Creates analytics implementations. + * + * @public + */ +export const AnalyticsImplementationBlueprint = createExtensionBlueprint({ + kind: 'analytics', + attachTo: [{ id: 'api:app/analytics', input: 'implementations' }], + output: [factoryDataRef], + dataRefs: { + factory: factoryDataRef, + }, + defineParams: ( + params: AnalyticsImplementationFactory, + ) => createExtensionBlueprintParams(params as AnalyticsImplementationFactory), + *factory(params) { + yield factoryDataRef(params); + }, +}); diff --git a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts index 862f802810..471e933b12 100644 --- a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts @@ -16,21 +16,19 @@ import { createExtensionInput } from '../wiring'; import { ApiBlueprint } from './ApiBlueprint'; -import { createApiFactory, createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '@backstage/core-plugin-api'; describe('ApiBlueprint', () => { it('should create an extension with sensible defaults', () => { const api = createApiRef<{ foo: string }>({ id: 'test' }); - const factory = createApiFactory({ - api, - deps: {}, - factory: () => ({ foo: 'bar' }), - }); const extension = ApiBlueprint.make({ - params: { - factory, - }, + params: defineParams => + defineParams({ + api, + deps: {}, + factory: () => ({ foo: 'bar' }), + }), name: 'test', }); @@ -58,6 +56,101 @@ describe('ApiBlueprint', () => { `); }); + it('should properly type the API factory', () => { + const fooApi = createApiRef<{ foo: string }>({ id: 'foo' }); + const barApi = createApiRef<{ bar: string }>({ id: 'bar' }); + + expect('test').not.toBe('failing without assertions'); + + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: fooApi, + deps: {}, + factory: () => ({ foo: 'foo' }), + }), + }); + + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: fooApi, + deps: {}, + // @ts-expect-error missing property + factory: () => ({}), + }), + }); + + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: fooApi, + deps: {}, + // @ts-expect-error wrong property + factory: () => ({ + bar: 'bar', + }), + }), + }); + + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: fooApi, + deps: {}, + factory: () => ({ + // @ts-expect-error wrong type + foo: 1, + }), + }), + }); + + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: fooApi, + deps: { bar: barApi }, + factory: ({ bar }) => ({ foo: bar.bar }), + }), + }); + + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: fooApi, + deps: { bar: barApi }, + factory: ({ bar }) => ({ + // @ts-expect-error not an available property + foo: bar.foo, + }), + }), + }); + + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: fooApi, + deps: { bar: barApi }, + factory: ({ bar }) => ({ + // @ts-expect-error not an available property + foo: bar.foo, + }), + }), + }); + + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: fooApi, + deps: {}, + factory: ({ bar }) => ({ + // @ts-expect-error unknown dep + foo: bar.bar, + }), + }), + }); + }); + it('should create an extension with custom factory', () => { const api = createApiRef<{ foo: string }>({ id: 'test' }); const factory = jest.fn(() => ({ foo: 'bar' })); @@ -73,13 +166,9 @@ describe('ApiBlueprint', () => { }, name: api.id, factory(originalFactory, { config: _config, inputs: _inputs }) { - return originalFactory({ - factory: createApiFactory({ - api, - deps: {}, - factory, - }), - }); + return originalFactory(defineParams => + defineParams({ api, deps: {}, factory }), + ); }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts index 1f28a3cf54..a066a362d9 100644 --- a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.ts @@ -14,8 +14,9 @@ * limitations under the License. */ +import { AnyApiFactory, ApiFactory } from '../apis/system'; import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; -import { AnyApiFactory } from '@backstage/core-plugin-api'; +import { createExtensionBlueprintParams } from '../wiring/createExtensionBlueprint'; const factoryDataRef = createExtensionDataRef().with({ id: 'core.api.factory', @@ -33,7 +34,14 @@ export const ApiBlueprint = createExtensionBlueprint({ dataRefs: { factory: factoryDataRef, }, - *factory(params: { factory: AnyApiFactory }) { - yield factoryDataRef(params.factory); + defineParams: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => createExtensionBlueprintParams(params as AnyApiFactory), + *factory(params) { + yield factoryDataRef(params); }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts index c823fa56cd..d1c71155f2 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/AppRootElementBlueprint.ts @@ -26,9 +26,7 @@ export const AppRootElementBlueprint = createExtensionBlueprint({ kind: 'app-root-element', attachTo: { id: 'app/root', input: 'elements' }, output: [coreExtensionData.reactElement], - *factory(params: { element: JSX.Element | (() => JSX.Element) }) { - yield coreExtensionData.reactElement( - typeof params.element === 'function' ? params.element() : params.element, - ); + *factory(params: { element: JSX.Element }) { + yield coreExtensionData.reactElement(params.element); }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx index d46975abd9..b4ad8ed2d2 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx @@ -27,7 +27,7 @@ describe('AppRootWrapperBlueprint', () => { it('should return an extension with sensible defaults', () => { const extension = AppRootWrapperBlueprint.make({ params: { - Component: () =>
    Hello
    , + component: () =>
    Hello
    , }, }); @@ -59,7 +59,7 @@ describe('AppRootWrapperBlueprint', () => { const extension = AppRootWrapperBlueprint.make({ name: 'test', params: { - Component: () =>
    Hello
    , + component: () =>
    Hello
    , }, }); @@ -80,7 +80,7 @@ describe('AppRootWrapperBlueprint', () => { }, *factory(originalFactory, { inputs, config }) { yield* originalFactory({ - Component: ({ children }) => ( + component: ({ children }) => (
    {children} {inputs.children.flatMap(c => diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx index 481be1bc0b..493fba05f6 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx @@ -14,11 +14,11 @@ * limitations under the License. */ -import { ComponentType, PropsWithChildren } from 'react'; +import { ReactNode } from 'react'; import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; const componentDataRef = createExtensionDataRef< - ComponentType> + (props: { children: ReactNode }) => JSX.Element | null >().with({ id: 'app.root.wrapper' }); /** @@ -35,12 +35,11 @@ export const AppRootWrapperBlueprint = createExtensionBlueprint({ dataRefs: { component: componentDataRef, }, - *factory(params: { Component: ComponentType> }) { - // todo(blam): not sure that this wrapping is even necessary anymore. - const Component = (props: PropsWithChildren<{}>) => { - return {props.children}; - }; - - yield componentDataRef(Component); + *factory(params: { + /** @deprecated use the `component` parameter instead */ + Component?: [error: 'Use the `component` parameter instead']; + component: (props: { children: ReactNode }) => JSX.Element | null; + }) { + yield componentDataRef(params.component); }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.test.tsx new file mode 100644 index 0000000000..a113a2f21d --- /dev/null +++ b/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.test.tsx @@ -0,0 +1,112 @@ +/* + * 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 { createRouteRef } from '@backstage/frontend-plugin-api'; +import { NavContentBlueprint } from './NavContentBlueprint'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; + +const routeRef = createRouteRef(); + +describe('NavContentBlueprint', () => { + it('should create an extension with sensible defaults', () => { + const extension = NavContentBlueprint.make({ + params: { + component: () =>
    Nav content
    , + }, + }); + + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "T": undefined, + "attachTo": { + "id": "app/nav", + "input": "content", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "nav-content", + "name": undefined, + "output": [ + [Function], + ], + "override": [Function], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should return a valid component', () => { + const extension = NavContentBlueprint.make({ + name: 'test', + params: { + component: () =>
    Nav content
    , + }, + }); + + const tester = createExtensionTester(extension); + + expect( + tester.get(NavContentBlueprint.dataRefs.component)({ items: [] }), + ).toEqual(
    Nav content
    ); + }); + + it('should return a valid component with items', () => { + const extension = NavContentBlueprint.make({ + name: 'test', + params: { + component: ({ items }) => ( +
    + Items: + {items.map((item, index) => ( + + {item.title} + + ))} +
    + ), + }, + }); + + const tester = createExtensionTester(extension); + + expect( + tester.get(NavContentBlueprint.dataRefs.component)({ + items: [ + { + to: '/', + text: 'Home', + title: 'Home', + icon: () => null, + routeRef, + }, + ], + }), + ).toEqual( +
    + Items: + {[ + + Home + , + ]} +
    , + ); + }); +}); diff --git a/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.ts new file mode 100644 index 0000000000..a6524ea49e --- /dev/null +++ b/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { IconComponent, RouteRef } from '@backstage/frontend-plugin-api'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; + +/** + * The props for the {@link NavContentComponent}. + * + * @public + */ +export interface NavContentComponentProps { + /** + * The nav items available to the component. These are all the items created + * with the {@link NavItemBlueprint} in the app. + * + * In addition to the original properties from the nav items, these also + * include a resolved route path as `to`, and duplicated `title` as `text` to + * simplify rendering. + */ + items: Array<{ + // Original props from nav items + icon: IconComponent; + title: string; + routeRef: RouteRef; + + // Additional props to simplify item rendering + to: string; + text: string; + }>; +} + +/** + * A component that renders the nav bar content, to be passed to the {@link NavContentBlueprint}. + * + * @public + */ +export type NavContentComponent = ( + props: NavContentComponentProps, +) => JSX.Element | null; + +const componentDataRef = createExtensionDataRef().with({ + id: 'core.nav-content.component', +}); + +/** + * Creates an extension that replaces the entire nav bar with your own component. + * + * @public + */ +export const NavContentBlueprint = createExtensionBlueprint({ + kind: 'nav-content', + attachTo: { id: 'app/nav', input: 'content' }, + output: [componentDataRef], + dataRefs: { + component: componentDataRef, + }, + *factory(params: { component: NavContentComponent }) { + yield componentDataRef(params.component); + }, +}); diff --git a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts deleted file mode 100644 index 4a37859975..0000000000 --- a/packages/frontend-plugin-api/src/blueprints/NavLogoBlueprint.ts +++ /dev/null @@ -1,48 +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 { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; - -const logoElementsDataRef = createExtensionDataRef<{ - logoIcon?: JSX.Element; - logoFull?: JSX.Element; -}>().with({ id: 'core.nav-logo.logo-elements' }); - -/** - * Creates an extension that replaces the logo in the nav bar with your own. - * - * @public - */ -export const NavLogoBlueprint = createExtensionBlueprint({ - kind: 'nav-logo', - attachTo: { id: 'app/nav', input: 'logos' }, - output: [logoElementsDataRef], - dataRefs: { - logoElements: logoElementsDataRef, - }, - *factory({ - logoIcon, - logoFull, - }: { - logoIcon: JSX.Element; - logoFull: JSX.Element; - }) { - yield logoElementsDataRef({ - logoIcon, - logoFull, - }); - }, -}); diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx index d3e1a6c954..680d93cd1c 100644 --- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx @@ -34,7 +34,7 @@ describe('PageBlueprint', () => { name: 'test-page', params: { loader: () => Promise.resolve(
    Test
    ), - defaultPath: '/test', + path: '/test', routeRef: mockRouteRef, }, }); @@ -90,7 +90,7 @@ describe('PageBlueprint', () => { name: 'test-page', params: { loader: () => Promise.resolve(
    Test
    ), - defaultPath: '/test', + path: '/test', routeRef: mockRouteRef, }, }); @@ -123,7 +123,7 @@ describe('PageBlueprint', () => { {inputs.cards.map(c => c.get(coreExtensionData.reactElement))}
    ), - defaultPath: '/test', + path: '/test', routeRef: mockRouteRef, }); }, diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx index 303e99daaa..92e2ab6172 100644 --- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx @@ -37,21 +37,23 @@ export const PageBlueprint = createExtensionBlueprint({ }, }, *factory( - { - defaultPath, - loader, - routeRef, - }: { - defaultPath: string; + params: { + /** + * @deprecated Use the `path` param instead. + */ + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise; routeRef?: RouteRef; }, { config, node }, ) { - yield coreExtensionData.routePath(config.path ?? defaultPath); - yield coreExtensionData.reactElement(ExtensionBoundary.lazy(node, loader)); - if (routeRef) { - yield coreExtensionData.routeRef(routeRef); + yield coreExtensionData.routePath(config.path ?? params.path); + yield coreExtensionData.reactElement( + ExtensionBoundary.lazy(node, params.loader), + ); + if (params.routeRef) { + yield coreExtensionData.routeRef(params.routeRef); } }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx index 00015d0c3d..f2e5199124 100644 --- a/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx @@ -27,7 +27,7 @@ describe('RouterBlueprint', () => { it('should return an extension when calling make with sensible defaults', () => { const extension = RouterBlueprint.make({ params: { - Component: props =>
    {props.children}
    , + component: props =>
    {props.children}
    , }, }); @@ -58,7 +58,7 @@ describe('RouterBlueprint', () => { it('should work with simple options', async () => { const extension = RouterBlueprint.make({ params: { - Component: ({ children }) => ( + component: ({ children }) => (
    {children}
    @@ -94,7 +94,7 @@ describe('RouterBlueprint', () => { }, *factory(originalFactory, { inputs, config }) { yield* originalFactory({ - Component: ({ children }) => ( + component: ({ children }) => (
    > + (props: { children: ReactNode }) => JSX.Element | null >().with({ id: 'app.router.wrapper' }); /** @public */ @@ -29,7 +29,11 @@ export const RouterBlueprint = createExtensionBlueprint({ dataRefs: { component: componentDataRef, }, - *factory({ Component }: { Component: ComponentType> }) { - yield componentDataRef(Component); + *factory(params: { + /** @deprecated use the `component` parameter instead */ + Component?: [error: 'Use the `component` parameter instead']; + component: (props: { children: ReactNode }) => JSX.Element | null; + }) { + yield componentDataRef(params.component); }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx index 0ca9bf4e92..5e4112a8d1 100644 --- a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx @@ -48,7 +48,7 @@ export const SignInPageBlueprint = createExtensionBlueprint({ ); yield componentDataRef(props => ( - + )); diff --git a/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.test.tsx new file mode 100644 index 0000000000..fae4bf7ebf --- /dev/null +++ b/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.test.tsx @@ -0,0 +1,141 @@ +/* + * 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 { renderInTestApp } from '@backstage/frontend-test-utils'; +import { createSwappableComponent } from '../components'; +import { SwappableComponentBlueprint } from './SwappableComponentBlueprint'; +import { PageBlueprint } from './PageBlueprint'; +import { waitFor, screen } from '@testing-library/react'; + +describe('SwappableComponentBlueprint', () => { + it('should allow defining a component override for a component ref', () => { + const Component = createSwappableComponent({ + id: 'test.component', + loader: () => (props: { hello: string }) =>
    {props.hello}
    , + }); + + const extension = SwappableComponentBlueprint.make({ + params: define => + define({ + component: Component, + loader: () => props => { + // @ts-expect-error + const t: number = props.hello; + + return
    Override {props.hello}
    ; + }, + }), + }); + + expect(extension).toBeDefined(); + }); + + it('should render default component refs in the app', async () => { + const TestComponent = createSwappableComponent({ + id: 'test.component', + loader: () => (props: { hello: string }) =>
    {props.hello}
    , + }); + + renderInTestApp(
    , { + extensions: [ + PageBlueprint.make({ + params: define => + define({ + // todo(blam): there's a bug that this path cannot be `/`? + path: '/test', + loader: async () => , + }), + }), + ], + initialRouteEntries: ['/test'], + }); + + await waitFor(() => expect(screen.getByText('test!')).toBeInTheDocument()); + }); + + it('should render a component ref without a default implementation', async () => { + const TestComponent = createSwappableComponent({ + id: 'test.component', + }); + + renderInTestApp(
    , { + extensions: [ + PageBlueprint.make({ + params: define => + define({ + path: '/test', + loader: async () => , + }), + }), + ], + initialRouteEntries: ['/test'], + }); + + await waitFor(() => + expect(screen.getByTestId('test.component')).toBeInTheDocument(), + ); + }); + + it('should render a component ref with an async loader implementation', async () => { + const TestComponent = createSwappableComponent({ + id: 'test.component', + loader: async () => (props: { hello: string }) => +
    {props.hello}
    , + }); + + renderInTestApp(
    , { + extensions: [ + PageBlueprint.make({ + params: define => + define({ + // todo(blam): there's a bug that this path cannot be `/`? + path: '/test', + loader: async () => , + }), + }), + ], + initialRouteEntries: ['/test'], + }); + + await waitFor(() => expect(screen.getByText('test!')).toBeInTheDocument()); + }); + + it('should render a component ref with an async loader implementation and prop transform', async () => { + const TestComponent = createSwappableComponent({ + id: 'test.component', + loader: async () => (props: { hello: string }) => +
    {props.hello}
    , + transformProps: ({ hello }) => ({ hello: `tr ${hello}` }), + }); + + renderInTestApp(
    , { + extensions: [ + PageBlueprint.make({ + params: define => + define({ + // todo(blam): there's a bug that this path cannot be `/`? + path: '/test', + loader: async () => , + }), + }), + ], + initialRouteEntries: ['/test'], + }); + + await waitFor(() => + expect(screen.getByText('tr test!')).toBeInTheDocument(), + ); + }); +}); diff --git a/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.ts new file mode 100644 index 0000000000..b72d14d87f --- /dev/null +++ b/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.ts @@ -0,0 +1,63 @@ +/* + * 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 { SwappableComponentRef } from '../components'; +import { + createExtensionBlueprint, + createExtensionBlueprintParams, + createExtensionDataRef, +} from '../wiring'; + +export const componentDataRef = createExtensionDataRef<{ + ref: SwappableComponentRef; + loader: + | (() => (props: {}) => JSX.Element | null) + | (() => Promise<(props: {}) => JSX.Element | null>); +}>().with({ id: 'core.swappableComponent' }); + +/** + * Blueprint for creating swappable components from a SwappableComponentRef and a loader + * + * @public + */ +export const SwappableComponentBlueprint = createExtensionBlueprint({ + kind: 'component', + attachTo: { id: 'api:app/swappable-components', input: 'components' }, + output: [componentDataRef], + dataRefs: { + component: componentDataRef, + }, + defineParams>(params: { + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > + ? { ref: Ref } & ((props: IExternalComponentProps) => JSX.Element | null) + : never; + loader: Ref extends SwappableComponentRef + ? + | (() => (props: IInnerComponentProps) => JSX.Element | null) + | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) + : never; + }) { + return createExtensionBlueprintParams(params); + }, + factory: params => [ + componentDataRef({ + ref: params.component.ref, + loader: params.loader, + }), + ], +}); diff --git a/packages/frontend-plugin-api/src/blueprints/index.ts b/packages/frontend-plugin-api/src/blueprints/index.ts index 85f7a99a2c..c8699b4232 100644 --- a/packages/frontend-plugin-api/src/blueprints/index.ts +++ b/packages/frontend-plugin-api/src/blueprints/index.ts @@ -14,14 +14,23 @@ * limitations under the License. */ +export { + AnalyticsImplementationBlueprint, + type AnalyticsImplementationFactory, +} from './AnalyticsImplementationBlueprint'; export { ApiBlueprint } from './ApiBlueprint'; export { AppRootElementBlueprint } from './AppRootElementBlueprint'; export { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint'; export { IconBundleBlueprint } from './IconBundleBlueprint'; +export { + NavContentBlueprint, + type NavContentComponent, + type NavContentComponentProps, +} from './NavContentBlueprint'; export { NavItemBlueprint } from './NavItemBlueprint'; -export { NavLogoBlueprint } from './NavLogoBlueprint'; export { PageBlueprint } from './PageBlueprint'; export { RouterBlueprint } from './RouterBlueprint'; export { SignInPageBlueprint } from './SignInPageBlueprint'; export { ThemeBlueprint } from './ThemeBlueprint'; export { TranslationBlueprint } from './TranslationBlueprint'; +export { SwappableComponentBlueprint } from './SwappableComponentBlueprint'; diff --git a/packages/frontend-plugin-api/src/components/AppNodeProvider.test.tsx b/packages/frontend-plugin-api/src/components/AppNodeProvider.test.tsx 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/DefaultSwappableComponents.tsx b/packages/frontend-plugin-api/src/components/DefaultSwappableComponents.tsx new file mode 100644 index 0000000000..7387bfad3c --- /dev/null +++ b/packages/frontend-plugin-api/src/components/DefaultSwappableComponents.tsx @@ -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 { + ErrorDisplayProps, + NotFoundErrorPageProps, + ProgressProps, +} from '../types'; +import { createSwappableComponent } from './createSwappableComponent'; + +/** + * @public + */ +export const Progress = createSwappableComponent({ + id: 'core-progress', +}); + +/** + * @public + */ +export const NotFoundErrorPage = + createSwappableComponent({ + id: 'core-not-found-error-page', + }); + +/** + * @public + */ +export const ErrorDisplay = createSwappableComponent({ + id: 'core-error-display', + loader: () => props => +
    {props.error.message}
    , +}); diff --git a/packages/frontend-plugin-api/src/components/ErrorBoundary.tsx b/packages/frontend-plugin-api/src/components/ErrorBoundary.tsx index 8842ff55cc..4cde84ee93 100644 --- a/packages/frontend-plugin-api/src/components/ErrorBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ErrorBoundary.tsx @@ -14,13 +14,12 @@ * limitations under the License. */ -import { Component, ComponentType, PropsWithChildren } from 'react'; +import { Component, PropsWithChildren } from 'react'; import { FrontendPlugin } from '../wiring'; -import { CoreErrorBoundaryFallbackProps } from '../types'; +import { ErrorDisplay } from './DefaultSwappableComponents'; type ErrorBoundaryProps = PropsWithChildren<{ plugin?: FrontendPlugin; - Fallback: ComponentType; }>; type ErrorBoundaryState = { error?: Error }; @@ -41,13 +40,15 @@ export class ErrorBoundary extends Component< render() { const { error } = this.state; - const { plugin, children, Fallback } = this.props; + const { plugin, children } = this.props; if (error) { return ( - ); diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx index 46b3956e0e..848ea64a2c 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx @@ -25,16 +25,17 @@ import { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api'; import { ErrorBoundary } from './ErrorBoundary'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/analytics/Tracker'; -import { AppNode, useComponentRef } from '../apis'; -import { coreComponentRefs } from './coreComponentRefs'; +import { AppNode } from '../apis'; import { coreExtensionData } from '../wiring'; +import { AppNodeProvider } from './AppNodeProvider'; +import { Progress } from './DefaultSwappableComponents'; type RouteTrackerProps = PropsWithChildren<{ - disableTracking?: boolean; + enabled?: boolean; }>; const RouteTracker = (props: RouteTrackerProps) => { - const { disableTracking, children } = props; + const { enabled, children } = props; const analytics = useAnalytics(); // This event, never exposed to end-users of the analytics API, @@ -42,9 +43,10 @@ const RouteTracker = (props: RouteTrackerProps) => { // navigation event when the route navigated to is a gathered // mountpoint. useEffect(() => { - if (disableTracking) return; - analytics.captureEvent(routableExtensionRenderedEvent, ''); - }, [analytics, disableTracking]); + if (enabled) { + analytics.captureEvent(routableExtensionRenderedEvent, ''); + } + }, [analytics, enabled]); return <>{children}; }; @@ -52,43 +54,35 @@ const RouteTracker = (props: RouteTrackerProps) => { /** @public */ export interface ExtensionBoundaryProps { node: AppNode; - /** - * This explicitly marks the extension as routable for the purpose of - * capturing analytics events. If not provided, the extension boundary will be - * marked as routable if it outputs a routePath. - */ - routable?: boolean; children: ReactNode; } /** @public */ export function ExtensionBoundary(props: ExtensionBoundaryProps) { - const { node, routable, children } = props; + const { node, children } = props; - const doesOutputRoutePath = Boolean( + const hasRoutePathOutput = Boolean( node.instance?.getData(coreExtensionData.routePath), ); - const plugin = node.spec.source; - const Progress = useComponentRef(coreComponentRefs.progress); - const fallback = useComponentRef(coreComponentRefs.errorBoundaryFallback); + const plugin = node.spec.plugin; // Skipping "routeRef" attribute in the new system, the extension "id" should provide more insight const attributes = { extensionId: node.spec.id, - pluginId: node.spec.source?.id ?? 'app', + pluginId: node.spec.plugin?.id ?? 'app', }; return ( - }> - - - - {children} - - - - + + }> + + + {children} + + + + ); } diff --git a/packages/frontend-plugin-api/src/components/coreComponentRefs.ts b/packages/frontend-plugin-api/src/components/coreComponentRefs.ts deleted file mode 100644 index 64412b8710..0000000000 --- a/packages/frontend-plugin-api/src/components/coreComponentRefs.ts +++ /dev/null @@ -1,43 +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 { - CoreErrorBoundaryFallbackProps, - CoreNotFoundErrorPageProps, - CoreProgressProps, -} from '../types'; -import { createComponentRef } from './createComponentRef'; - -const coreProgressComponentRef = createComponentRef({ - id: 'core.components.progress', -}); - -const coreNotFoundErrorPageComponentRef = - createComponentRef({ - id: 'core.components.notFoundErrorPage', - }); - -const coreErrorBoundaryFallbackComponentRef = - createComponentRef({ - id: 'core.components.errorBoundaryFallback', - }); - -/** @public */ -export const coreComponentRefs = { - progress: coreProgressComponentRef, - notFoundErrorPage: coreNotFoundErrorPageComponentRef, - errorBoundaryFallback: coreErrorBoundaryFallbackComponentRef, -}; diff --git a/packages/frontend-plugin-api/src/components/createSwappableComponent.test.tsx b/packages/frontend-plugin-api/src/components/createSwappableComponent.test.tsx new file mode 100644 index 0000000000..2a73a4da42 --- /dev/null +++ b/packages/frontend-plugin-api/src/components/createSwappableComponent.test.tsx @@ -0,0 +1,163 @@ +/* + * 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 { render, screen } from '@testing-library/react'; +import { createSwappableComponent } from './createSwappableComponent'; + +describe('createSwappableComponent', () => { + it('can be created and read', () => { + const { ref } = createSwappableComponent({ id: 'foo' }); + expect(ref.id).toBe('foo'); + expect(String(ref)).toBe('SwappableComponentRef{id=foo}'); + }); + + it('should allow defining a default component implementation', () => { + const Test = () =>
    test
    ; + + createSwappableComponent<{ foo: string }, { bar: string }>({ + id: 'foo', + loader: + () => + ({ foo }) => + , + }); + + createSwappableComponent<{ foo: string }, { bar: string }>({ + id: 'foo', + loader: + async () => + ({ foo }) => + , + }); + + createSwappableComponent<{ foo: string }, { bar: string }>({ + id: 'foo', + }); + + expect(Test).toBeDefined(); + }); + + it('should allow transformings props', () => { + createSwappableComponent<{ foo: string }, { bar: string }>({ + id: 'foo', + transformProps: props => ({ foo: props.bar }), + }); + + createSwappableComponent<{ foo: string }, { bar: string }>({ + id: 'foo', + // @ts-expect-error - this should be an error as foo is not a string + transformProps: props => ({ foo: 1 }), + }); + + expect(true).toBe(true); + }); + + describe('sync', () => { + it('should create a component from a ref for sync component', async () => { + const Component = createSwappableComponent({ + id: 'random', + loader: () => (props: { name: string }) => { + return
    {props.name}
    ; + }, + transformProps: (props: { id: string }) => ({ + name: props.id, + }), + }); + + render(); + + await expect(screen.findByTestId('test')).resolves.toHaveTextContent( + 'test', + ); + }); + + it('should render a fallback when theres no default implementation provided', async () => { + const Component = createSwappableComponent({ + id: 'random', + }); + + render(); + await expect(screen.findByTestId('random')).resolves.toBeInTheDocument(); + }); + + it('should map props from external to internal', async () => { + const Component = createSwappableComponent({ + id: 'random', + transformProps: (props: { name: string }) => ({ + uppercase: props.name.toUpperCase(), + }), + loader: () => props => { + // @ts-expect-error as uppercase is types as a string + const test: number = props.uppercase; + + return
    {props.uppercase}
    ; + }, + }); + + render(); + + await expect(screen.findByTestId('test')).resolves.toHaveTextContent( + 'TEST', + ); + }); + }); + + describe('async', () => { + it('should create a component from a ref for async component', async () => { + const Component = createSwappableComponent({ + id: 'random', + loader: async () => (props: { name: string }) => { + return
    {props.name}
    ; + }, + }); + + render(); + + await expect(screen.findByTestId('test')).resolves.toBeInTheDocument(); + }); + + it('should render a fallback when theres no default implementation provided', async () => { + const Component = createSwappableComponent({ + id: 'random', + }); + + render(); + + await expect(screen.findByTestId('random')).resolves.toBeInTheDocument(); + }); + + it('should map props from external to internal', async () => { + const Component = createSwappableComponent({ + id: 'random', + transformProps: (props: { name: string }) => ({ + uppercase: props.name.toUpperCase(), + }), + loader: async () => props => { + // @ts-expect-error as uppercase is types as a string + const test: number = props.uppercase; + + return
    {props.uppercase}
    ; + }, + }); + + render(); + + await expect(screen.findByTestId('test')).resolves.toHaveTextContent( + 'TEST', + ); + }); + }); +}); diff --git a/packages/frontend-plugin-api/src/components/createSwappableComponent.tsx b/packages/frontend-plugin-api/src/components/createSwappableComponent.tsx new file mode 100644 index 0000000000..716837f469 --- /dev/null +++ b/packages/frontend-plugin-api/src/components/createSwappableComponent.tsx @@ -0,0 +1,112 @@ +/* + * 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 { OpaqueSwappableComponentRef } from '@internal/frontend'; +import { swappableComponentsApiRef, useApi } from '../apis'; +import { lazy } from 'react'; + +/** @public */ +export type SwappableComponentRef< + TInnerComponentProps extends {} = {}, + TExternalComponentProps extends {} = TInnerComponentProps, +> = { + id: string; + TProps: TInnerComponentProps; + TExternalProps: TExternalComponentProps; + $$type: '@backstage/SwappableComponentRef'; +}; + +/** + * Options for creating an SwappableComponent. + * + * @public + */ +export type CreateSwappableComponentOptions< + TInnerComponentProps extends {}, + TExternalComponentProps extends {} = TInnerComponentProps, +> = { + id: string; + loader?: + | (() => (props: TInnerComponentProps) => JSX.Element | null) + | (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>); + transformProps?: (props: TExternalComponentProps) => TInnerComponentProps; +}; + +const useComponentRefApi = () => { + try { + return useApi(swappableComponentsApiRef); + } catch (e) { + return undefined; + } +}; + +/** + * Creates a SwappableComponent that can be used to render the component, optionally overridden by the app. + * + * @public + */ +export function createSwappableComponent< + TInnerComponentProps extends {}, + TExternalComponentProps extends {} = TInnerComponentProps, +>( + options: CreateSwappableComponentOptions< + TInnerComponentProps, + TExternalComponentProps + >, +): { + (props: TExternalComponentProps): JSX.Element | null; + ref: SwappableComponentRef; +} { + const FallbackComponent = (p: JSX.IntrinsicAttributes) => ( +
    + ); + + const ref = OpaqueSwappableComponentRef.createInstance('v1', { + id: options.id, + TProps: null as unknown as TInnerComponentProps, + TExternalProps: null as unknown as TExternalComponentProps, + toString() { + return `SwappableComponentRef{id=${options.id}}`; + }, + defaultComponent: lazy(async () => { + const Component = (await options.loader?.()) ?? FallbackComponent; + return { default: Component }; + }) as (typeof OpaqueSwappableComponentRef.TInternal)['defaultComponent'], + transformProps: + options.transformProps as (typeof OpaqueSwappableComponentRef.TInternal)['transformProps'], + }); + + const ComponentRefImpl = (props: TExternalComponentProps) => { + const api = useComponentRefApi(); + + if (!api) { + const internalRef = OpaqueSwappableComponentRef.toInternal(ref); + const Component = internalRef.defaultComponent; + const innerProps = internalRef.transformProps?.(props) ?? props; + return ; + } + + const Component = api.getComponent(ref); + return ; + }; + + Object.assign(ComponentRefImpl, { ref }); + + return ComponentRefImpl as { + (props: TExternalComponentProps): JSX.Element | null; + ref: SwappableComponentRef; + }; +} diff --git a/packages/frontend-plugin-api/src/components/index.ts b/packages/frontend-plugin-api/src/components/index.ts index 5066144000..450224bdc4 100644 --- a/packages/frontend-plugin-api/src/components/index.ts +++ b/packages/frontend-plugin-api/src/components/index.ts @@ -18,5 +18,10 @@ export { ExtensionBoundary, type ExtensionBoundaryProps, } from './ExtensionBoundary'; -export { coreComponentRefs } from './coreComponentRefs'; -export { createComponentRef, type ComponentRef } from './createComponentRef'; +export { + createSwappableComponent, + type CreateSwappableComponentOptions, + type SwappableComponentRef, +} from './createSwappableComponent'; +export { useAppNode } from './AppNodeProvider'; +export * from './DefaultSwappableComponents'; diff --git a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx deleted file mode 100644 index f561a73dc6..0000000000 --- a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx +++ /dev/null @@ -1,72 +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 { lazy, ComponentType } from 'react'; -import { createExtension, createExtensionDataRef } from '../wiring'; -import { ComponentRef } from '../components'; - -/** @public */ -export function createComponentExtension(options: { - ref: ComponentRef; - name?: string; - disabled?: boolean; - loader: - | { - lazy: () => Promise>; - } - | { - sync: () => ComponentType; - }; -}) { - return createExtension({ - kind: 'component', - name: options.name ?? options.ref.id, - attachTo: { id: 'api:app/components', input: 'components' }, - disabled: options.disabled, - output: [createComponentExtension.componentDataRef], - factory() { - if ('sync' in options.loader) { - return [ - createComponentExtension.componentDataRef({ - ref: options.ref, - impl: options.loader.sync() as ComponentType, - }), - ]; - } - const lazyLoader = options.loader.lazy; - const ExtensionComponent = lazy(() => - lazyLoader().then(Component => ({ - default: Component, - })), - ) as unknown as ComponentType; - - return [ - createComponentExtension.componentDataRef({ - ref: options.ref, - impl: ExtensionComponent, - }), - ]; - }, - }); -} - -/** @public */ -export namespace createComponentExtension { - export const componentDataRef = createExtensionDataRef<{ - ref: ComponentRef; - impl: ComponentType; - }>().with({ id: 'core.component.component' }); -} diff --git a/packages/frontend-plugin-api/src/index.ts b/packages/frontend-plugin-api/src/index.ts index 957ca50bba..9117dc0ef9 100644 --- a/packages/frontend-plugin-api/src/index.ts +++ b/packages/frontend-plugin-api/src/index.ts @@ -24,7 +24,6 @@ export * from './analytics'; export * from './apis'; export * from './blueprints'; export * from './components'; -export * from './extensions'; export * from './icons'; export * from './routing'; export * from './schema'; @@ -33,7 +32,7 @@ export * from './translation'; export * from './wiring'; export type { - CoreProgressProps, - CoreNotFoundErrorPageProps, - CoreErrorBoundaryFallbackProps, + ProgressProps, + NotFoundErrorPageProps, + ErrorDisplayProps, } from './types'; diff --git a/packages/frontend-plugin-api/src/routing/RouteRef.ts b/packages/frontend-plugin-api/src/routing/RouteRef.ts index 1678e14cd4..46076e6258 100644 --- a/packages/frontend-plugin-api/src/routing/RouteRef.ts +++ b/packages/frontend-plugin-api/src/routing/RouteRef.ts @@ -41,6 +41,8 @@ export interface InternalRouteRef< getParams(): string[]; getDescription(): string; + alias: string | undefined; + setId(id: string): void; } @@ -68,18 +70,28 @@ export class RouteRefImpl implements InternalRouteRef { declare readonly T: never; #id?: string; - #params: string[]; - #creationSite: string; + readonly #params: string[]; + readonly #creationSite: string; + readonly #alias?: string; - constructor(readonly params: string[] = [], creationSite: string) { + constructor( + readonly params: string[] = [], + creationSite: string, + alias?: string, + ) { this.#params = params; this.#creationSite = creationSite; + this.#alias = alias; } getParams(): string[] { return this.#params; } + get alias(): string | undefined { + return this.#alias; + } + getDescription(): string { if (this.#id) { return this.#id; @@ -121,7 +133,11 @@ export function createRouteRef< TParamKeys extends string = string, >(config?: { /** A list of parameter names that the path that this route ref is bound to must contain */ - readonly params: string extends TParamKeys ? (keyof TParams)[] : TParamKeys[]; + readonly params?: string extends TParamKeys + ? (keyof TParams)[] + : TParamKeys[]; + + aliasFor?: string; }): RouteRef< keyof TParams extends never ? undefined @@ -132,5 +148,6 @@ export function createRouteRef< return new RouteRefImpl( config?.params as string[] | undefined, describeParentCallSite(), + config?.aliasFor, ) as RouteRef; } diff --git a/packages/frontend-plugin-api/src/types.ts b/packages/frontend-plugin-api/src/types.ts index 9088a9d772..a8ddf43cad 100644 --- a/packages/frontend-plugin-api/src/types.ts +++ b/packages/frontend-plugin-api/src/types.ts @@ -18,15 +18,15 @@ import { ReactNode } from 'react'; import { FrontendPlugin } from './wiring'; /** @public */ -export type CoreProgressProps = {}; +export type ProgressProps = {}; /** @public */ -export type CoreNotFoundErrorPageProps = { +export type NotFoundErrorPageProps = { children?: ReactNode; }; /** @public */ -export type CoreErrorBoundaryFallbackProps = { +export type ErrorDisplayProps = { plugin?: FrontendPlugin; error: Error; resetError: () => void; diff --git a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts b/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts index cb48cb8df3..18466d8196 100644 --- a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts +++ b/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts @@ -15,7 +15,7 @@ */ import { JSX } from 'react'; -import { RouteRef } from '../routing'; +import { RouteRef } from '../routing/RouteRef'; import { createExtensionDataRef } from './createExtensionDataRef'; /** @public */ diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 04d7f15240..0c0b75a572 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -17,19 +17,22 @@ import { ApiHolder, AppNode } from '../apis'; import { Expand } from '@backstage/types'; import { - ResolveInputValueOverrides, + ResolvedInputValueOverrides, resolveInputOverrides, } from './resolveInputOverrides'; import { createExtensionDataContainer } from '@internal/frontend'; -import { - AnyExtensionDataRef, - ExtensionDataValue, -} from './createExtensionDataRef'; +import { ExtensionDataRef, ExtensionDataValue } from './createExtensionDataRef'; import { ExtensionInput } from './createExtensionInput'; import { z } from 'zod'; import { createSchemaFromZod } from '../schema/createSchemaFromZod'; import { OpaqueExtensionDefinition } from '@internal/frontend'; import { ExtensionDataContainer } from './types'; +import { + ExtensionBlueprint, + ExtensionBlueprintDefineParams, +} from './createExtensionBlueprint'; +import { FrontendPlugin } from './createFrontendPlugin'; +import { FrontendModule } from './createFrontendModule'; /** * This symbol is used to pass parameter overrides from the extension override to the blueprint factory @@ -43,7 +46,7 @@ export const ctxParamsSymbol = Symbol('params'); */ export type ResolvedExtensionInput< TExtensionInput extends ExtensionInput, -> = TExtensionInput['extensionData'] extends Array +> = TExtensionInput['extensionData'] extends Array ? { node: AppNode; } & ExtensionDataContainer @@ -90,7 +93,7 @@ type JoinStringUnion< /** @ignore */ export type VerifyExtensionFactoryOutput< - UDeclaredOutput extends AnyExtensionDataRef, + UDeclaredOutput extends ExtensionDataRef, UFactoryOutput extends ExtensionDataValue, > = ( UDeclaredOutput extends any @@ -119,10 +122,10 @@ export type ExtensionAttachToSpec = export type CreateExtensionOptions< TKind extends string | undefined, TName extends string | undefined, - UOutput extends AnyExtensionDataRef, + UOutput extends ExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean } >; }, @@ -154,16 +157,30 @@ export type ExtensionDefinitionParameters = { name?: string; configInput?: { [K in string]: any }; config?: { [K in string]: any }; - output?: AnyExtensionDataRef; + output?: ExtensionDataRef; inputs?: { [KName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean } >; }; - params?: object; + params?: object | ExtensionBlueprintDefineParams; }; +/** + * Same as the one in `createExtensionBlueprint`, but with `ParamsFactory` inlined. + * It can't be exported because it breaks API reports. + * @ignore + */ +type AnyParamsInput = + TParams extends ExtensionBlueprintDefineParams + ? IParams | ((define: TParams) => ReturnType) + : + | TParams + | (( + define: ExtensionBlueprintDefineParams, + ) => ReturnType>); + /** @public */ export type ExtensionDefinition< T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, @@ -176,13 +193,14 @@ export type ExtensionDefinition< [key in string]: (zImpl: typeof z) => z.ZodType; }, UFactoryOutput extends ExtensionDataValue, - UNewOutput extends AnyExtensionDataRef, + UNewOutput extends ExtensionDataRef, TExtraInputs extends { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean } >; }, + TParamsInput extends AnyParamsInput>, >( args: Expand< { @@ -200,14 +218,24 @@ export type ExtensionDefinition< }; }; factory?( - originalFactory: ( + originalFactory: < + TFactoryParamsReturn extends AnyParamsInput< + NonNullable + >, + >( context?: Expand< { config?: T['config']; - inputs?: ResolveInputValueOverrides>; + inputs?: ResolvedInputValueOverrides>; } & ([T['params']] extends [never] ? {} - : { params?: Partial }) + : { + params?: TFactoryParamsReturn extends ExtensionBlueprintDefineParams + ? TFactoryParamsReturn + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; + }) >, ) => ExtensionDataContainer>, context: { @@ -223,10 +251,16 @@ export type ExtensionDefinition< ): Iterable; } & ([T['params']] extends [never] ? {} - : { params?: Partial }) + : { + params?: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : Partial; + }) > & VerifyExtensionFactoryOutput< - AnyExtensionDataRef extends UNewOutput + ExtensionDataRef extends UNewOutput ? NonNullable : UNewOutput, UFactoryOutput @@ -234,7 +268,7 @@ export type ExtensionDefinition< ): ExtensionDefinition<{ kind: T['kind']; name: T['name']; - output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; inputs: T['inputs'] & TExtraInputs; config: T['config'] & { [key in keyof TExtensionConfigSchema]: z.infer< @@ -252,12 +286,46 @@ export type ExtensionDefinition< }>; }; -/** @public */ +/** + * Creates a new extension definition for installation in a Backstage app. + * + * @remarks + * + * This is a low-level function for creation of extensions with arbitrary inputs + * and outputs and is typically only intended to be used for advanced overrides + * or framework-level extensions. For most extension creation needs, it is + * recommended to use existing {@link ExtensionBlueprint}s instead. You can find + * blueprints both in the `@backstage/frontend-plugin-api` package as well as + * other plugin libraries. There is also a list of + * {@link https://backstage.io/docs/frontend-system/building-plugins/common-extension-blueprints | commonly used blueprints} + * in the frontend system documentation. + * + * Extension definitions that are created with this function can be installed in + * a Backstage app via a {@link FrontendPlugin} or {@link FrontendModule}. + * + * For more details on how extensions work, see the + * {@link https://backstage.io/docs/frontend-system/architecture/extensions | documentation for extensions}. + * + * @example + * + * ```ts + * const myExtension = createExtension({ + * name: 'example', + * attachTo: { id: 'app', input: 'root' }, + * output: [coreExtensionData.reactElement], + * factory() { + * return [coreExtensionData.reactElement(

    Hello, world!

    )]; + * }, + * }); + * ``` + * + * @public + */ export function createExtension< - UOutput extends AnyExtensionDataRef, + UOutput extends ExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean } >; }, @@ -287,7 +355,14 @@ export function createExtension< [key in keyof TConfigSchema]: ReturnType; }> >; - output: UOutput; + // This inference and remapping back to ExtensionDataRef eliminates any occurrences ConfigurationExtensionDataRef + output: UOutput extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? ExtensionDataRef + : never; inputs: TInputs; params: never; kind: string | undefined extends TKind ? undefined : TKind; @@ -378,7 +453,7 @@ export function createExtension< disabled: overrideOptions.disabled ?? options.disabled, inputs: { ...overrideOptions.inputs, ...options.inputs }, output: (overrideOptions.output ?? - options.output) as AnyExtensionDataRef[], + options.output) as ExtensionDataRef[], config: options.config || overrideOptions.config ? { @@ -412,6 +487,7 @@ export function createExtension< ) as any, [ctxParamsSymbol as any]: innerContext?.params, }) as Iterable, + 'original extension factory', options.output, ); }, @@ -423,6 +499,15 @@ export function createExtension< }, ); + if ( + typeof parentResult !== 'object' || + !parentResult?.[Symbol.iterator] + ) { + throw new Error( + 'extension factory override did not provide an iterable object', + ); + } + const deduplicatedResult = new Map< string, ExtensionDataValue diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index 5a44dc4b55..e4cc876ff0 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -15,7 +15,11 @@ */ import { coreExtensionData } from './coreExtensionData'; -import { createExtensionBlueprint } from './createExtensionBlueprint'; +import { + createExtensionBlueprint, + createExtensionBlueprintParams, + ExtensionBlueprintParams, +} from './createExtensionBlueprint'; import { createExtensionTester, renderInTestApp, @@ -387,7 +391,7 @@ describe('createExtensionBlueprint', () => { }); const mockInput = (node: string, ...data: ExtensionDataValue[]) => - Object.assign(createExtensionDataContainer(data), { + Object.assign(createExtensionDataContainer(data, 'mock'), { node, }); const mockParentInputs = { @@ -876,6 +880,21 @@ describe('createExtensionBlueprint', () => { test2: 'orig-2', }); + const extensionDef = blueprint.make({ + // Using defineParams is optional in this case + params: defineParams => + defineParams({ + test1: 'orig-1', + test2: 'orig-2', + }), + }); + + expect(getOutputs(extensionDef)).toEqual({ + test1: 'orig-1', + test2: 'orig-2', + }); + + // Plain override expect( getOutputs( extension.override({ @@ -896,6 +915,8 @@ describe('createExtensionBlueprint', () => { extension.override({ params: { test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', }, }), ), @@ -904,6 +925,58 @@ describe('createExtensionBlueprint', () => { test2: 'override-2', }); + // Partial override with original defineParams + expect( + getOutputs( + extensionDef.override({ + params: { + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }, + }), + ), + ).toEqual({ + test1: 'orig-1', + test2: 'override-2', + }); + + // Override with defineParams + expect( + getOutputs( + extension.override({ + params: defineParams => + defineParams({ + test1: 'override-1', + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }), + }), + ), + ).toEqual({ + test1: 'override-1', + test2: 'override-2', + }); + + // Override with defineParams with original defineParams + expect( + getOutputs( + extensionDef.override({ + params: defineParams => + defineParams({ + test1: 'override-1', + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }), + }), + ), + ).toEqual({ + test1: 'override-1', + test2: 'override-2', + }); + expect( getOutputs( extension.override({ @@ -930,6 +1003,8 @@ describe('createExtensionBlueprint', () => { return origFactory({ params: { test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', }, }); }, @@ -940,6 +1015,70 @@ describe('createExtensionBlueprint', () => { test2: 'override-2', }); + // Partial override via factory with original defineParams + expect( + getOutputs( + extensionDef.override({ + factory(origFactory) { + return origFactory({ + params: { + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }, + }); + }, + }), + ), + ).toEqual({ + test1: 'orig-1', + test2: 'override-2', + }); + + // Override via factory with defineParams + expect( + getOutputs( + extension.override({ + factory(origFactory) { + return origFactory({ + params: defineParams => + defineParams({ + test1: 'override-1', + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }), + }); + }, + }), + ), + ).toEqual({ + test1: 'override-1', + test2: 'override-2', + }); + + // Override via factory with defineParams with original defineParams + expect( + getOutputs( + extensionDef.override({ + factory(origFactory) { + return origFactory({ + params: defineParams => + defineParams({ + test1: 'override-1', + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }), + }); + }, + }), + ), + ).toEqual({ + test1: 'override-1', + test2: 'override-2', + }); + expect(() => getOutputs( extension.override({ @@ -991,6 +1130,21 @@ describe('createExtensionBlueprint', () => { test2: 'orig-2', }); + const extensionDef = blueprint.make({ + // Using defineParams is optional in this case + params: defineParams => + defineParams({ + test1: 'orig-1', + test2: 'orig-2', + }), + }); + + expect(getOutputs(extensionDef)).toEqual({ + test1: 'orig-1', + test2: 'orig-2', + }); + + // Plain override expect( getOutputs( extension.override({ @@ -1011,6 +1165,8 @@ describe('createExtensionBlueprint', () => { extension.override({ params: { test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', }, }), ), @@ -1019,6 +1175,59 @@ describe('createExtensionBlueprint', () => { test2: 'override-2', }); + // Partial override with original defineParams + expect( + getOutputs( + extensionDef.override({ + params: { + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }, + }), + ), + ).toEqual({ + test1: 'orig-1', + test2: 'override-2', + }); + + // Override with defineParams + expect( + getOutputs( + extension.override({ + params: defineParams => + defineParams({ + test1: 'override-1', + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }), + }), + ), + ).toEqual({ + test1: 'override-1', + test2: 'override-2', + }); + + // Override with defineParams with original defineParams + expect( + getOutputs( + extensionDef.override({ + params: defineParams => + defineParams({ + test1: 'override-1', + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }), + }), + ), + ).toEqual({ + test1: 'override-1', + test2: 'override-2', + }); + + // Override via factory expect( getOutputs( extension.override({ @@ -1045,6 +1254,8 @@ describe('createExtensionBlueprint', () => { return origFactory({ params: { test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', }, }); }, @@ -1055,6 +1266,70 @@ describe('createExtensionBlueprint', () => { test2: 'override-2', }); + // Partial override via factory with original defineParams + expect( + getOutputs( + extensionDef.override({ + factory(origFactory) { + return origFactory({ + params: { + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }, + }); + }, + }), + ), + ).toEqual({ + test1: 'orig-1', + test2: 'override-2', + }); + + // Override via factory with defineParams + expect( + getOutputs( + extension.override({ + factory(origFactory) { + return origFactory({ + params: defineParams => + defineParams({ + test1: 'override-1', + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }), + }); + }, + }), + ), + ).toEqual({ + test1: 'override-1', + test2: 'override-2', + }); + + // Override via factory with defineParams with original defineParams + expect( + getOutputs( + extensionDef.override({ + factory(origFactory) { + return origFactory({ + params: defineParams => + defineParams({ + test1: 'override-1', + test2: 'override-2', + // @ts-expect-error + test3: 'nonexistent', + }), + }); + }, + }), + ), + ).toEqual({ + test1: 'override-1', + test2: 'override-2', + }); + expect(() => getOutputs( extension.override({ @@ -1069,4 +1344,272 @@ describe('createExtensionBlueprint', () => { ), ).toThrow('Refused to override params and factory at the same time'); }); + + describe('with advanced parameter types', () => { + const testDataRef = createExtensionDataRef().with({ id: 'test' }); + + const TestExtensionBlueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: [testDataRef], + defineParams(params: { + a: A; + b: B; + }) { + return createExtensionBlueprintParams(params); + }, + factory(params) { + return [testDataRef(`${params.a} ${params.b}`)]; + }, + }); + + it('should allow creation of extension blueprints', () => { + TestExtensionBlueprint.make({ + // @ts-expect-error not using define func + params: { + a: 'x', + b: 'y', + }, + }); + + TestExtensionBlueprint.make({ + params: defineParams => + defineParams({ + a: 'x', + // @ts-expect-error b doesn't match a + b: 'y', + }), + }); + + TestExtensionBlueprint.make({ + params: defineParams => + defineParams({ + a: 'x', + b: 'x', + // @ts-expect-error extra param + c: 'y', + }), + }); + + const extension = TestExtensionBlueprint.make({ + params: defineParams => + defineParams({ + a: 'x', + b: 'x', + }), + }); + + expect(extension).toEqual({ + $$type: '@backstage/ExtensionDefinition', + T: undefined, + attachTo: { + id: 'test', + input: 'default', + }, + configSchema: undefined, + disabled: false, + inputs: {}, + kind: 'test-extension', + name: undefined, + namespace: undefined, + output: [testDataRef], + factory: expect.any(Function), + toString: expect.any(Function), + override: expect.any(Function), + version: 'v2', + }); + + expect(createExtensionTester(extension).get(testDataRef)).toBe('x x'); + + extension.override({ + // @ts-expect-error not using define func + params: { + a: 'z', + b: 'w', + }, + }); + + extension.override({ + params: defineParams => + defineParams({ + a: 'z', + // @ts-expect-error b doesn't match a + b: 'w', + }), + }); + + extension.override({ + params: defineParams => + defineParams({ + a: 'z', + b: 'z', + // @ts-expect-error extra param + c: 'w', + }), + }); + + const override = extension.override({ + params: defineParams => + defineParams({ + a: 'z', + b: 'z', + }), + }); + + expect(createExtensionTester(override).get(testDataRef)).toBe('z z'); + }); + + it('should allow overriding of the default factory', () => { + TestExtensionBlueprint.makeWithOverrides({ + factory(originalFactory) { + // @ts-expect-error not using define func + return originalFactory({ + a: 'x', + b: 'y', + }); + }, + }); + + TestExtensionBlueprint.makeWithOverrides({ + factory(originalFactory) { + return originalFactory(defineParams => + defineParams({ + a: 'x', + // @ts-expect-error b doesn't match a + b: 'y', + }), + ); + }, + }); + + const extension = TestExtensionBlueprint.makeWithOverrides({ + factory(originalFactory) { + return originalFactory(defineParams => + defineParams({ + a: 'x', + b: 'x', + }), + ); + }, + }); + + expect(extension).toEqual({ + $$type: '@backstage/ExtensionDefinition', + T: undefined, + attachTo: { + id: 'test', + input: 'default', + }, + configSchema: undefined, + disabled: false, + inputs: {}, + kind: 'test-extension', + name: undefined, + namespace: undefined, + output: [testDataRef], + factory: expect.any(Function), + toString: expect.any(Function), + override: expect.any(Function), + version: 'v2', + }); + + expect(createExtensionTester(extension).get(testDataRef)).toBe('x x'); + + extension.override({ + // @ts-expect-error not using define func + params: { + a: 'z', + b: 'w', + }, + }); + + extension.override({ + params: defineParams => + defineParams({ + a: 'z', + // @ts-expect-error b doesn't match a + b: 'w', + }), + }); + + const override = extension.override({ + params: defineParams => + defineParams({ + a: 'z', + b: 'z', + }), + }); + + expect(createExtensionTester(override).get(testDataRef)).toBe('z z'); + }); + + it('should allow the params definer to transform the params', () => { + const TestTransformExtensionBlueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: [testDataRef], + defineParams(params: { a: number; b: number }) { + return createExtensionBlueprintParams({ + x: params.a + 1, + y: params.b + 1, + }); + }, + factory(params) { + return [testDataRef(`${params.x} ${params.y}`)]; + }, + }); + + const extension = TestTransformExtensionBlueprint.make({ + params: defineParams => + defineParams({ + a: 0, + b: 10, + }), + }); + + expect(createExtensionTester(extension).get(testDataRef)).toBe(`1 11`); + + expect( + createExtensionTester( + extension.override({ + params: defineParams => + defineParams({ + a: 20, + b: 30, + }), + }), + ).get(testDataRef), + ).toBe(`21 31`); + }); + + it('should support overloads', () => { + const TestTransformExtensionBlueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: [testDataRef], + defineParams: (params => createExtensionBlueprintParams(params)) as { + (params: { x: 1 }): ExtensionBlueprintParams<{ x: number }>; + (params: { x: 2 }): ExtensionBlueprintParams<{ x: number }>; + }, + factory(params) { + return [testDataRef(`x: ${params.x}`)]; + }, + }); + + const extension = TestTransformExtensionBlueprint.make({ + params: defineParams => defineParams({ x: 1 }), + }); + + expect(createExtensionTester(extension).get(testDataRef)).toBe(`x: 1`); + + TestTransformExtensionBlueprint.make({ + params: defineParams => defineParams({ x: 2 }), + }); + + TestTransformExtensionBlueprint.make({ + // @ts-expect-error doesn't match any overload + params: defineParams => defineParams({ x: 3 }), + }); + }); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 57848a7980..1a24eed712 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -16,6 +16,7 @@ import { ApiHolder, AppNode } from '../apis'; import { Expand } from '@backstage/types'; +import { OpaqueType } from '@internal/opaque'; import { ExtensionAttachToSpec, ExtensionDefinition, @@ -26,46 +27,145 @@ import { } from './createExtension'; import { z } from 'zod'; import { ExtensionInput } from './createExtensionInput'; -import { - AnyExtensionDataRef, - ExtensionDataValue, -} from './createExtensionDataRef'; +import { ExtensionDataRef, ExtensionDataValue } from './createExtensionDataRef'; import { createExtensionDataContainer } from '@internal/frontend'; import { - ResolveInputValueOverrides, + ResolvedInputValueOverrides, resolveInputOverrides, } from './resolveInputOverrides'; import { ExtensionDataContainer } from './types'; +import { PageBlueprint } from '../blueprints/PageBlueprint'; + +/** + * A function used to define a parameter mapping function in order to facilitate + * advanced parameter typing for extension blueprints. + * + * @remarks + * + * This function is primarily intended to enable the use of inferred type + * parameters for blueprint params, but it can also be used to transoform the + * params before they are handed ot the blueprint. + * + * The function must return an object created with + * {@link createExtensionBlueprintParams}. + * + * @public + */ +export type ExtensionBlueprintDefineParams< + TParams extends object = object, + TInput = any, +> = (params: TInput) => ExtensionBlueprintParams; + +/** + * An opaque type that represents a set of parameters to be passed to a blueprint. + * + * @remarks + * + * Created with {@link createExtensionBlueprintParams}. + * + * @public + */ +export type ExtensionBlueprintParams = { + $$type: '@backstage/BlueprintParams'; + T: T; +}; + +const OpaqueBlueprintParams = OpaqueType.create<{ + public: ExtensionBlueprintParams; + versions: { + version: 'v1'; + params: object; + }; +}>({ + type: '@backstage/BlueprintParams', + versions: ['v1'], +}); + +/** + * Wraps a plain blueprint parameter object in an opaque {@link ExtensionBlueprintParams} object. + * + * This is used in the definition of the `defineParams` option of {@link ExtensionBlueprint}. + * + * @public + * @param params - The plain blueprint parameter object to wrap. + * @returns The wrapped blueprint parameter object. + */ +export function createExtensionBlueprintParams( + params: T, +): ExtensionBlueprintParams { + return OpaqueBlueprintParams.createInstance('v1', { T: null as any, params }); +} /** * @public */ export type CreateExtensionBlueprintOptions< TKind extends string, - TName extends string | undefined, - TParams, - UOutput extends AnyExtensionDataRef, + TParams extends object | ExtensionBlueprintDefineParams, + UOutput extends ExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean } >; }, TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, - TDataRefs extends { [name in string]: AnyExtensionDataRef }, + TDataRefs extends { [name in string]: ExtensionDataRef }, > = { kind: TKind; attachTo: ExtensionAttachToSpec; disabled?: boolean; inputs?: TInputs; output: Array; - name?: TName; config?: { schema: TConfigSchema; }; + /** + * This option is used to further refine the blueprint params. When this + * option is used, the blueprint will require params to be passed in callback + * form. This function can both transform the params before they are handed to + * the blueprint factory, but importantly it also allows you to define + * inferred type parameters for your blueprint params. + * + * @example + * Blueprint definition with inferred type parameters: + * ```ts + * const ExampleBlueprint = createExtensionBlueprint({ + * kind: 'example', + * attachTo: { id: 'example', input: 'example' }, + * output: [exampleComponentDataRef, exampleFetcherDataRef], + * defineParams(params: { + * component(props: ExampleProps): JSX.Element | null + * fetcher(options: FetchOptions): Promise> + * }) { + * return createExtensionBlueprintParams(params); + * }, + * *factory(params) { + * yield exampleComponentDataRef(params.component) + * yield exampleFetcherDataRef(params.fetcher) + * }, + * }); + * ``` + * + * @example + * Usage of the above example blueprint: + * ```ts + * const example = ExampleBlueprint.make({ + * params: defineParams => defineParams({ + * component: ..., + * fetcher: ..., + * }), + * }); + * ``` + */ + defineParams?: TParams extends ExtensionBlueprintDefineParams + ? TParams + : 'The defineParams option must be a function if provided, see the docs for details'; factory( - params: TParams, + params: TParams extends ExtensionBlueprintDefineParams + ? ReturnType['T'] + : TParams, context: { node: AppNode; apis: ApiHolder; @@ -82,20 +182,35 @@ export type CreateExtensionBlueprintOptions< /** @public */ export type ExtensionBlueprintParameters = { kind: string; - name?: string; - params?: object; + params?: object | ExtensionBlueprintDefineParams; configInput?: { [K in string]: any }; config?: { [K in string]: any }; - output?: AnyExtensionDataRef; + output?: ExtensionDataRef; inputs?: { [KName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean } >; }; - dataRefs?: { [name in string]: AnyExtensionDataRef }; + dataRefs?: { [name in string]: ExtensionDataRef }; }; +/** @ignore */ +type ParamsFactory = ( + defineParams: TDefiner, +) => ReturnType; + +/** + * Represents any form of params input that can be passed to a blueprint. + * This also includes the invalid form of passing a plain params object to a blueprint that uses a definition callback. + * + * @ignore + */ +type AnyParamsInput = + TParams extends ExtensionBlueprintDefineParams + ? IParams | ParamsFactory + : TParams | ParamsFactory>; + /** * @public */ @@ -104,14 +219,21 @@ export interface ExtensionBlueprint< > { dataRefs: T['dataRefs']; - make(args: { - name?: TNewName; + make< + TName extends string | undefined, + TParamsInput extends AnyParamsInput>, + >(args: { + name?: TName; attachTo?: ExtensionAttachToSpec; disabled?: boolean; - params: T['params']; + params: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `.make({ params: defineParams => defineParams() })`' + : T['params']; }): ExtensionDefinition<{ kind: T['kind']; - name: string | undefined extends TNewName ? T['name'] : TNewName; + name: string | undefined extends TName ? undefined : TName; config: T['config']; configInput: T['configInput']; output: T['output']; @@ -126,20 +248,20 @@ export interface ExtensionBlueprint< * optionally call the original factory with the same params. */ makeWithOverrides< - TNewName extends string | undefined, + TName extends string | undefined, TExtensionConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType; }, UFactoryOutput extends ExtensionDataValue, - UNewOutput extends AnyExtensionDataRef, + UNewOutput extends ExtensionDataRef, TExtraInputs extends { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean } >; }, >(args: { - name?: TNewName; + name?: TName; attachTo?: ExtensionAttachToSpec; disabled?: boolean; inputs?: TExtraInputs & { @@ -154,11 +276,17 @@ export interface ExtensionBlueprint< }; }; factory( - originalFactory: ( - params: T['params'], + originalFactory: < + TParamsInput extends AnyParamsInput>, + >( + params: TParamsInput extends ExtensionBlueprintDefineParams + ? TParamsInput + : T['params'] extends ExtensionBlueprintDefineParams + ? 'Error: This blueprint uses advanced parameter types and requires you to pass parameters as using the following callback syntax: `originalFactory(defineParams => defineParams())`' + : T['params'], context?: { config?: T['config']; - inputs?: ResolveInputValueOverrides>; + inputs?: ResolvedInputValueOverrides>; }, ) => ExtensionDataContainer>, context: { @@ -173,7 +301,7 @@ export interface ExtensionBlueprint< }, ): Iterable & VerifyExtensionFactoryOutput< - AnyExtensionDataRef extends UNewOutput + ExtensionDataRef extends UNewOutput ? NonNullable : UNewOutput, UFactoryOutput @@ -197,38 +325,149 @@ export interface ExtensionBlueprint< }> >) & T['configInput']; - output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + output: ExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; inputs: T['inputs'] & TExtraInputs; kind: T['kind']; - name: string | undefined extends TNewName ? T['name'] : TNewName; + name: string | undefined extends TName ? undefined : TName; params: T['params']; }>; } +function unwrapParamsFactory( + // Allow `Function` because `typeof === 'function'` allows it, but in practice this should always be a param factory + params: ParamsFactory | Function, + defineParams: ExtensionBlueprintDefineParams, + kind: string, +): TParams { + const paramDefinition = ( + params as ParamsFactory + )(defineParams); + try { + return OpaqueBlueprintParams.toInternal(paramDefinition).params as TParams; + } catch (e) { + throw new TypeError( + `Invalid invocation of blueprint with kind '${kind}', the parameter definition callback function did not return a valid parameter definition object; Caused by: ${e.message}`, + ); + } +} + +function unwrapParams( + params: object | ParamsFactory | string, + ctx: { node: AppNode; [ctxParamsSymbol]?: any }, + defineParams: ExtensionBlueprintDefineParams | undefined, + kind: string, +): TParams { + const overrideParams = ctx[ctxParamsSymbol] as + | object + | ParamsFactory + | undefined; + + if (defineParams) { + if (overrideParams) { + if (typeof overrideParams !== 'function') { + throw new TypeError( + `Invalid extension override of blueprint with kind '${kind}', the override params were passed as a plain object, but this blueprint requires them to be passed in callback form`, + ); + } + return unwrapParamsFactory(overrideParams, defineParams, kind); + } + + if (typeof params !== 'function') { + throw new TypeError( + `Invalid invocation of blueprint with kind '${kind}', the parameters where passed as a plain object, but this blueprint requires them to be passed in callback form`, + ); + } + return unwrapParamsFactory(params, defineParams, kind); + } + + const base = + typeof params === 'function' + ? unwrapParamsFactory( + params, + createExtensionBlueprintParams, + kind, + ) + : (params as TParams); + const overrides = + typeof overrideParams === 'function' + ? unwrapParamsFactory( + overrideParams, + createExtensionBlueprintParams, + kind, + ) + : (overrideParams as Partial); + + return { + ...base, + ...overrides, + }; +} + /** - * A simpler replacement for wrapping up `createExtension` inside a kind or type. This allows for a cleaner API for creating - * types and instances of those types. + * Creates a new extension blueprint that encapsulates the creation of + * extensions of particular kinds. * + * @remarks + * + * For details on how blueprints work, see the + * {@link https://backstage.io/docs/frontend-system/architecture/extension-blueprints | documentation for extension blueprints} + * in the frontend system documentation. + * + * Extension blueprints make it much easier for users to create new extensions + * for your plugin. Rather than letting them use {@link createExtension} + * directly, you can define a set of parameters and default factory for your + * blueprint, removing a lot of the boilerplate and complexity that is otherwise + * needed to create an extension. + * + * Each blueprint has its own `kind` that helps identify and group the + * extensions that have been created with it. For example the + * {@link PageBlueprint} has the kind `'page'`, and extensions created with it + * will be given the ID `'page:[/]'`. Blueprints should always + * be exported as `Blueprint`. + * + * When creating a blueprint the type of the parameters are inferred from the + * `factory` function that you provide. The exception to that is when you need + * your blueprint to include inferred type parameters, in which case you need to + * use the `defineParams` option. See the documentation for the `defineParams` + * option for more details on how that works. + * + * @example + * ```tsx + * // In your plugin library + * export const GreetingBlueprint = createExtensionBlueprint({ + * kind: 'greeting', + * attachTo: { id: 'example', input: 'greetings' }, + * output: [coreExtensionData.reactElement], + * factory(params: { greeting: string }) { + * return [coreExtensionData.reactElement(

    {params.greeting}

    )]; + * }, + * }); + * + * // Someone using your blueprint in their plugin + * const exampleGreeting = GreetingBlueprint.make({ + * params: { + * greeting: 'Hello, world!', + * }, + * }); + * ``` * @public */ export function createExtensionBlueprint< - TParams extends object, - UOutput extends AnyExtensionDataRef, + TParams extends object | ExtensionBlueprintDefineParams, + UOutput extends ExtensionDataRef, TInputs extends { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean } >; }, TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, UFactoryOutput extends ExtensionDataValue, TKind extends string, - TName extends string | undefined = undefined, - TDataRefs extends { [name in string]: AnyExtensionDataRef } = never, + TDataRefs extends { [name in string]: ExtensionDataRef } = never, >( options: CreateExtensionBlueprintOptions< TKind, - TName, TParams, UOutput, TInputs, @@ -238,9 +477,15 @@ export function createExtensionBlueprint< >, ): ExtensionBlueprint<{ kind: TKind; - name: TName; params: TParams; - output: UOutput; + // This inference and remapping back to ExtensionDataRef eliminates any occurrences ConfigurationExtensionDataRef + output: UOutput extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? ExtensionDataRef + : never; inputs: string extends keyof TInputs ? {} : TInputs; config: string extends keyof TConfigSchema ? {} @@ -254,20 +499,24 @@ export function createExtensionBlueprint< >; dataRefs: TDataRefs; }> { + const defineParams = options.defineParams as + | ExtensionBlueprintDefineParams + | undefined; + return { dataRefs: options.dataRefs, make(args) { return createExtension({ kind: options.kind, - name: args.name ?? options.name, + name: args.name, attachTo: args.attachTo ?? options.attachTo, disabled: args.disabled ?? options.disabled, inputs: options.inputs, - output: options.output as AnyExtensionDataRef[], + output: options.output as ExtensionDataRef[], config: options.config, factory: ctx => options.factory( - { ...args.params, ...(ctx as any)[ctxParamsSymbol] }, + unwrapParams(args.params, ctx, defineParams, options.kind), ctx, ) as Iterable>, }) as ExtensionDefinition; @@ -275,11 +524,11 @@ export function createExtensionBlueprint< makeWithOverrides(args) { return createExtension({ kind: options.kind, - name: args.name ?? options.name, + name: args.name, attachTo: args.attachTo ?? options.attachTo, disabled: args.disabled ?? options.disabled, inputs: { ...args.inputs, ...options.inputs }, - output: (args.output ?? options.output) as AnyExtensionDataRef[], + output: (args.output ?? options.output) as ExtensionDataRef[], config: options.config || args.config ? { @@ -293,9 +542,17 @@ export function createExtensionBlueprint< const { node, config, inputs, apis } = ctx; return args.factory( (innerParams, innerContext) => { - return createExtensionDataContainer( + return createExtensionDataContainer< + UOutput extends ExtensionDataRef< + infer IData, + infer IId, + infer IConfig + > + ? ExtensionDataRef + : never + >( options.factory( - { ...innerParams, ...(ctx as any)[ctxParamsSymbol] }, + unwrapParams(innerParams, ctx, defineParams, options.kind), { apis, node, @@ -307,6 +564,7 @@ export function createExtensionBlueprint< ) as any, }, ) as Iterable, + 'original blueprint factory', options.output, ); }, @@ -322,9 +580,8 @@ export function createExtensionBlueprint< }, } as ExtensionBlueprint<{ kind: TKind; - name: TName; params: TParams; - output: UOutput; + output: any; inputs: string extends keyof TInputs ? {} : TInputs; config: string extends keyof TConfigSchema ? {} diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts index 94acb192a8..1bfbb01740 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts @@ -23,9 +23,9 @@ export type ExtensionDataValue = { /** @public */ export type ExtensionDataRef< - TData, + TData = unknown, TId extends string = string, - TConfig extends { optional?: true } = {}, + TConfig extends { optional?: true } = { optional?: true }, > = { readonly $$type: '@backstage/ExtensionDataRef'; readonly id: TId; @@ -39,12 +39,11 @@ export type ExtensionDataRefToValue = ? ExtensionDataValue : never; -/** @public */ -export type AnyExtensionDataRef = ExtensionDataRef< - unknown, - string, - { optional?: true } ->; +/** + * @deprecated Use `ExtensionDataRef` without type parameters instead. + * @public + */ +export type AnyExtensionDataRef = ExtensionDataRef; /** @public */ export interface ConfigurableExtensionDataRef< diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendFeatureLoader.test.ts b/packages/frontend-plugin-api/src/wiring/createFrontendFeatureLoader.test.ts index 63ce4cb284..50ea0749fd 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendFeatureLoader.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendFeatureLoader.test.ts @@ -46,7 +46,9 @@ function createTestAppRoot({ }) { return createApp({ features: [...features], - configLoader: async () => ({ config: mockApis.config({ data: config }) }), + advanced: { + configLoader: async () => ({ config: mockApis.config({ data: config }) }), + }, }).createRoot(); } diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts b/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts index bece1539fa..7921f98635 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts @@ -45,7 +45,45 @@ export interface InternalFrontendModule extends FrontendModule { readonly featureFlags: FeatureFlagConfig[]; } -/** @public */ +/** + * Creates a new module that can be installed in a Backstage app. + * + * @remarks + * + * Modules are used to add or override extensions for an existing plugin. If a + * module provides an extension with the same ID as one provided by the plugin, + * the extension provided by the module will always take precedence. + * + * Every module is created for a specific plugin by providing the + * unique ID of the plugin that the module should be installed for. If that + * plugin is not present in the app, the module will be ignored and have no + * effect. + * + * For more information on how modules work, see the + * {@link https://backstage.io/docs/frontend-system/architecture/extension-overrides#creating-a-frontend-module | documentation for modules} + * in the frontend system documentation. + * + * It is recommended to name the module variable of the form `Module`. + * + * @example + * + * ```tsx + * import { createFrontendModule } from '@backstage/frontend-plugin-api'; + * + * export const exampleModuleCustomPage = createFrontendModule({ + * pluginId: 'example', + * extensions: [ + * // Overrides the default page for the 'example' plugin + * PageBlueprint.make({ + * path: '/example', + * loader: () => import('./CustomPage').then(m => ), + * }), + * ], + * }); + * ``` + * + * @public + */ export function createFrontendModule< TId extends string, TExtensions extends readonly ExtensionDefinition[] = [], diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts index 359f9da417..a3226721a1 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts @@ -128,7 +128,9 @@ function createTestAppRoot({ }) { return createApp({ features: [...features], - configLoader: async () => ({ config: mockApis.config({ data: config }) }), + advanced: { + configLoader: async () => ({ config: mockApis.config({ data: config }) }), + }, }).createRoot(); } @@ -140,13 +142,6 @@ describe('createFrontendPlugin', () => { expect(String(plugin)).toBe('Plugin{id=test}'); }); - it('should create an empty plugin with deprecated id option', () => { - const plugin = createFrontendPlugin({ id: 'test' }); - - expect(plugin).toBeDefined(); - expect(String(plugin)).toBe('Plugin{id=test}'); - }); - it('should create a plugin with extension instances', async () => { const plugin = createFrontendPlugin({ pluginId: 'test', @@ -281,6 +276,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..f545195e81 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts @@ -23,13 +23,79 @@ import { Extension, resolveExtensionDefinition, } from './resolveExtensionDefinition'; -import { AnyExternalRoutes, AnyRoutes, FeatureFlagConfig } from './types'; +import { FeatureFlagConfig } from './types'; import { MakeSortedExtensionsMap } from './MakeSortedExtensionsMap'; +import { JsonObject } from '@backstage/types'; +import { RouteRef, SubRouteRef, ExternalRouteRef } from '../routing'; + +/** + * 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< - TRoutes extends AnyRoutes = AnyRoutes, - TExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, + TRoutes extends { [name in string]: RouteRef | SubRouteRef } = { + [name in string]: RouteRef | SubRouteRef; + }, + TExternalRoutes extends { [name in string]: ExternalRouteRef } = { + [name in string]: ExternalRouteRef; + }, TExtensionMap extends { [id in string]: ExtensionDefinition } = { [id in string]: ExtensionDefinition; }, @@ -38,17 +104,27 @@ 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; } /** @public */ export interface PluginOptions< TId extends string, - TRoutes extends AnyRoutes, - TExternalRoutes extends AnyExternalRoutes, + TRoutes extends { [name in string]: RouteRef | SubRouteRef }, + TExternalRoutes extends { [name in string]: ExternalRouteRef }, TExtensions extends readonly ExtensionDefinition[], > { pluginId: TId; @@ -56,13 +132,43 @@ export interface PluginOptions< externalRoutes?: TExternalRoutes; extensions?: TExtensions; featureFlags?: FeatureFlagConfig[]; + info?: FrontendPluginInfoOptions; } -/** @public */ +/** + * Creates a new plugin that can be installed in a Backstage app. + * + * @remarks + * + * Every plugin is created with a unique ID and a set of extensions + * that are installed as part of the plugin. + * + * For more information on how plugins work, see the + * {@link https://backstage.io/docs/frontend-system/building-plugins/index | documentation for plugins} + * in the frontend system documentation. + * + * @example + * + * ```tsx + * import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; + * + * export const examplePlugin = createFrontendPlugin({ + * pluginId: 'example', + * extensions: [ + * PageBlueprint.make({ + * path: '/example', + * loader: () => import('./ExamplePage').then(m => ), + * }), + * ], + * }); + * ``` + * + * @public + */ export function createFrontendPlugin< TId extends string, - TRoutes extends AnyRoutes = {}, - TExternalRoutes extends AnyExternalRoutes = {}, + TRoutes extends { [name in string]: RouteRef | SubRouteRef } = {}, + TExternalRoutes extends { [name in string]: ExternalRouteRef } = {}, TExtensions extends readonly ExtensionDefinition[] = [], >( options: PluginOptions, @@ -70,49 +176,9 @@ export function createFrontendPlugin< TRoutes, TExternalRoutes, MakeSortedExtensionsMap ->; -/** - * @public - * @deprecated The `id` option is deprecated, use `pluginId` instead. - */ -export function createFrontendPlugin< - TId extends string, - TRoutes extends AnyRoutes = {}, - TExternalRoutes extends AnyExternalRoutes = {}, - TExtensions extends readonly ExtensionDefinition[] = [], ->( - options: Omit< - PluginOptions, - 'pluginId' - > & { id: string }, -): FrontendPlugin< - TRoutes, - TExternalRoutes, - MakeSortedExtensionsMap ->; -export function createFrontendPlugin< - TId extends string, - TRoutes extends AnyRoutes = {}, - TExternalRoutes extends AnyExternalRoutes = {}, - TExtensions extends readonly ExtensionDefinition[] = [], ->( - options: - | PluginOptions - | (Omit< - PluginOptions, - 'pluginId' - > & { id: string }), -): FrontendPlugin< - TRoutes, - TExternalRoutes, - MakeSortedExtensionsMap > { - const pluginId = 'pluginId' in options ? options.pluginId : options.id; - if (!pluginId) { - throw new Error( - "Either 'id' or 'pluginId' must be provided to createFrontendPlugin", - ); - } + const pluginId = options.pluginId; + const extensions = new Array>(); const extensionDefinitionsById = new Map< string, @@ -150,6 +216,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 +252,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..ec6f8c4ffa 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, @@ -53,8 +55,6 @@ export { } from './createFrontendFeatureLoader'; export { type Extension } from './resolveExtensionDefinition'; export { - type AnyRoutes, - type AnyExternalRoutes, type ExtensionDataContainer, type FeatureFlagConfig, type ExtensionFactoryMiddleware, @@ -64,6 +64,8 @@ export { type CreateExtensionBlueprintOptions, type ExtensionBlueprint, type ExtensionBlueprintParameters, + type ExtensionBlueprintParams, + type ExtensionBlueprintDefineParams, createExtensionBlueprint, + createExtensionBlueprintParams, } from './createExtensionBlueprint'; -export { type ResolveInputValueOverrides } from './resolveInputOverrides'; diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts index b25a7b9375..a74d908024 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts @@ -113,6 +113,7 @@ describe('ResolveExtensionId', () => { kind: TKind; name: TName; output: any; + params: never; }>; const id1: 'k:ns' = {} as ResolveExtensionId< NamedExtension<'k', undefined>, diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts index 7e771c6c2c..a5b6859e1a 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts @@ -23,10 +23,7 @@ import { } from './createExtension'; import { PortableSchema } from '../schema'; import { ExtensionInput } from './createExtensionInput'; -import { - AnyExtensionDataRef, - ExtensionDataValue, -} from './createExtensionDataRef'; +import { ExtensionDataRef, ExtensionDataValue } from './createExtensionDataRef'; import { OpaqueExtensionDefinition } from '@internal/frontend'; /** @public */ @@ -50,13 +47,13 @@ export type InternalExtension = Extension< [inputName in string]: { $$type: '@backstage/ExtensionInput'; extensionData: { - [name in string]: AnyExtensionDataRef; + [name in string]: ExtensionDataRef; }; config: { optional: boolean; singleton: boolean }; }; }; readonly output: { - [name in string]: AnyExtensionDataRef; + [name in string]: ExtensionDataRef; }; factory(context: { apis: ApiHolder; @@ -73,18 +70,18 @@ export type InternalExtension = Extension< readonly version: 'v2'; readonly inputs: { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean } >; }; - readonly output: Array; + readonly output: Array; factory(options: { apis: ApiHolder; node: AppNode; config: TConfig; inputs: ResolvedExtensionInputs<{ [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean } >; }>; @@ -116,6 +113,7 @@ export type ResolveExtensionId< > = TExtension extends ExtensionDefinition<{ kind: infer IKind extends string | undefined; name: infer IName extends string | undefined; + params: any; }> ? [string] extends [IKind | IName] ? never diff --git a/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts b/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts index b93f7ce370..8ffec893ee 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveInputOverrides.ts @@ -19,23 +19,23 @@ import { Expand } from '@backstage/types'; import { ResolvedExtensionInput } from './createExtension'; import { createExtensionDataContainer } from '@internal/frontend'; import { - AnyExtensionDataRef, + ExtensionDataRef, ExtensionDataRefToValue, ExtensionDataValue, } from './createExtensionDataRef'; import { ExtensionInput } from './createExtensionInput'; import { ExtensionDataContainer } from './types'; -/** @public */ -export type ResolveInputValueOverrides< +/** @ignore */ +export type ResolvedInputValueOverrides< TInputs extends { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean } >; } = { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean } >; }, @@ -92,7 +92,7 @@ function expectItem(value: T | T[]): T { export function resolveInputOverrides( declaredInputs?: { [inputName in string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean } >; }, @@ -101,7 +101,7 @@ export function resolveInputOverrides( | ({ node: AppNode } & ExtensionDataContainer) | Array<{ node: AppNode } & ExtensionDataContainer>; }, - inputOverrides?: ResolveInputValueOverrides, + inputOverrides?: ResolvedInputValueOverrides, ) { if (!declaredInputs || !inputs || !inputOverrides) { return inputs; @@ -119,6 +119,7 @@ export function resolveInputOverrides( if (providedData) { const providedContainer = createExtensionDataContainer( providedData as Iterable>, + 'extension input override', declaredInput.extensionData, ); if (!originalInput) { @@ -157,6 +158,7 @@ export function resolveInputOverrides( newInputs[name] = providedData.map((data, i) => { const providedContainer = createExtensionDataContainer( data as Iterable>, + 'extension input override', declaredInput.extensionData, ); return Object.assign(providedContainer, { diff --git a/packages/frontend-plugin-api/src/wiring/types.ts b/packages/frontend-plugin-api/src/wiring/types.ts index 22bcbdadb7..460a399871 100644 --- a/packages/frontend-plugin-api/src/wiring/types.ts +++ b/packages/frontend-plugin-api/src/wiring/types.ts @@ -15,13 +15,8 @@ */ import { JsonObject } from '@backstage/types'; -import { ExternalRouteRef, RouteRef, SubRouteRef } from '../routing'; import { ExtensionDefinition } from './createExtension'; -import { - AnyExtensionDataRef, - ExtensionDataRef, - ExtensionDataValue, -} from './createExtensionDataRef'; +import { ExtensionDataRef, ExtensionDataValue } from './createExtensionDataRef'; import { ApiHolder, AppNode } from '../apis'; import { FrontendModule } from './createFrontendModule'; import { FrontendPlugin } from './createFrontendPlugin'; @@ -36,12 +31,6 @@ export type FeatureFlagConfig = { name: string; }; -/** @public */ -export type AnyRoutes = { [name in string]: RouteRef | SubRouteRef }; - -/** @public */ -export type AnyExternalRoutes = { [name in string]: ExternalRouteRef }; - /** @public */ export type ExtensionMap< TExtensionMap extends { [id in string]: ExtensionDefinition }, @@ -50,7 +39,7 @@ export type ExtensionMap< }; /** @public */ -export type ExtensionDataContainer = +export type ExtensionDataContainer = Iterable< UExtensionData extends ExtensionDataRef< infer IData, @@ -75,7 +64,7 @@ export type ExtensionDataContainer = export type ExtensionFactoryMiddleware = ( originalFactory: (contextOverrides?: { config?: JsonObject; - }) => ExtensionDataContainer, + }) => ExtensionDataContainer, context: { node: AppNode; apis: ApiHolder; diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index 3534a15653..b0b55df96c 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,139 @@ # @backstage/frontend-test-utils +## 0.3.5-next.2 + +### Patch Changes + +- df7bd3b: Updated import of the `FrontendFeature` type. +- 5d31d66: Updated the usage of the `RouterBlueprint` and `AppRootWrapperBlueprint` to use the lowercase `component` parameter +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/frontend-app-api@0.12.0-next.2 + - @backstage/plugin-app@0.2.0-next.1 + - @backstage/config@1.3.3 + - @backstage/test-utils@1.7.11-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.11.5-next.1 + - @backstage/plugin-app@0.2.0-next.0 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/test-utils@1.7.11-next.0 + - @backstage/config@1.3.3 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.11.5-next.0 + - @backstage/plugin-app@0.1.11 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/frontend-app-api@0.11.4 + - @backstage/plugin-app@0.1.11 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/test-utils@1.7.10 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/frontend-app-api@0.11.4-next.1 + - @backstage/test-utils@1.7.10-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + - @backstage/plugin-app@0.1.11-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.1.11-next.0 + - @backstage/test-utils@1.7.10-next.0 + - @backstage/config@1.3.2 + - @backstage/frontend-app-api@0.11.4-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.1.10 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/frontend-app-api@0.11.3 + - @backstage/config@1.3.2 + - @backstage/test-utils@1.7.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/plugin-app@0.1.10-next.1 + - @backstage/config@1.3.2 + - @backstage/frontend-app-api@0.11.3-next.1 + - @backstage/test-utils@1.7.8 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 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 + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/test-utils@1.7.8 + - @backstage/frontend-app-api@0.11.2 + - @backstage/plugin-app@0.1.9 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## 0.3.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.11.2-next.3 + - @backstage/test-utils@1.7.8-next.2 + - @backstage/config@1.3.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-app@0.1.9-next.3 + ## 0.3.2-next.2 ### Patch Changes diff --git a/packages/frontend-test-utils/catalog-info.yaml b/packages/frontend-test-utils/catalog-info.yaml index e2d2a57897..065a0b64e2 100644 --- a/packages/frontend-test-utils/catalog-info.yaml +++ b/packages/frontend-test-utils/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index b065490006..38b5ff991b 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-next.2", + "version": "0.3.5-next.2", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-test-utils/report.api.md b/packages/frontend-test-utils/report.api.md index b139538cbe..db31c082ea 100644 --- a/packages/frontend-test-utils/report.api.md +++ b/packages/frontend-test-utils/report.api.md @@ -5,7 +5,6 @@ ```ts import { AnalyticsApi } from '@backstage/frontend-plugin-api'; import { AnalyticsEvent } from '@backstage/frontend-plugin-api'; -import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ApiMock } from '@backstage/test-utils'; import { AppNode } from '@backstage/frontend-plugin-api'; import { AppNodeInstance } from '@backstage/frontend-plugin-api'; @@ -13,7 +12,7 @@ import { ErrorWithContext } from '@backstage/test-utils'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionDefinitionParameters } from '@backstage/frontend-plugin-api'; -import { FrontendFeature } from '@backstage/frontend-app-api'; +import { FrontendFeature } from '@backstage/frontend-plugin-api'; import { JsonObject } from '@backstage/types'; import { mockApis } from '@backstage/test-utils'; import { MockConfigApi } from '@backstage/test-utils'; @@ -45,7 +44,7 @@ export function createExtensionTester( export { ErrorWithContext }; // @public (undocumented) -export class ExtensionQuery { +export class ExtensionQuery { constructor(node: AppNode); // (undocumented) get( @@ -62,7 +61,7 @@ export class ExtensionQuery { } // @public (undocumented) -export class ExtensionTester { +export class ExtensionTester { // (undocumented) add( extension: ExtensionDefinition, diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index 37ec1d8fd9..7b321d4413 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -15,7 +15,6 @@ */ import { - AnyExtensionDataRef, AppNode, AppTree, Extension, @@ -40,7 +39,7 @@ import { TestApiRegistry } from '@backstage/test-utils'; import { OpaqueExtensionDefinition } from '@internal/frontend'; /** @public */ -export class ExtensionQuery { +export class ExtensionQuery { #node: AppNode; constructor(node: AppNode) { @@ -75,7 +74,7 @@ export class ExtensionQuery { } /** @public */ -export class ExtensionTester { +export class ExtensionTester { /** @internal */ static forSubject( subject: ExtensionDefinition, diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.test.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.test.tsx index cee6273cb1..8e1b1e1ea0 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.test.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.test.tsx @@ -21,6 +21,7 @@ import { TestApiProvider, } from '@backstage/frontend-test-utils'; import { analyticsApiRef, useAnalytics } from '@backstage/frontend-plugin-api'; +import { Routes, Route } from 'react-router-dom'; import { renderInTestApp } from './renderInTestApp'; describe('renderInTestApp', () => { @@ -65,4 +66,18 @@ describe('renderInTestApp', () => { ]), ); }); + + it('should support setting different locations in the history stack', async () => { + renderInTestApp( + + Index Page} /> + Second Page} /> + , + { + initialRouteEntries: ['/second-page'], + }, + ); + + expect(screen.getByText('Second Page')).toBeInTheDocument(); + }); }); diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index d4010a92ad..80818509da 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -16,10 +16,7 @@ import { Fragment } from 'react'; import { Link, MemoryRouter } from 'react-router-dom'; -import { - createSpecializedApp, - FrontendFeature, -} from '@backstage/frontend-app-api'; +import { createSpecializedApp } from '@backstage/frontend-app-api'; import { RenderResult, render } from '@testing-library/react'; import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; @@ -33,6 +30,7 @@ import { RouterBlueprint, NavItemBlueprint, createFrontendPlugin, + FrontendFeature, } from '@backstage/frontend-plugin-api'; import appPlugin from '@backstage/plugin-app'; @@ -160,7 +158,7 @@ export function renderInTestApp( }), RouterBlueprint.make({ params: { - Component: ({ children }) => ( + component: ({ children }) => ( {children} diff --git a/packages/integration-aws-node/CHANGELOG.md b/packages/integration-aws-node/CHANGELOG.md index 41d4eecb08..297e6330f2 100644 --- a/packages/integration-aws-node/CHANGELOG.md +++ b/packages/integration-aws-node/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/integration-aws-node +## 0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + +## 0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + +## 0.1.16 + +### Patch Changes + +- db4630e: Fixed bug in DefaultAwsCredentialsManager where aws.mainAccount.region has no effect on the STS region used for account ID lookup during credential provider lookup when falling back to the main account, and it does not default to us-east-1 +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + ## 0.1.16-next.0 ### Patch Changes diff --git a/packages/integration-aws-node/catalog-info.yaml b/packages/integration-aws-node/catalog-info.yaml index 4c8264d575..88b86deb84 100644 --- a/packages/integration-aws-node/catalog-info.yaml +++ b/packages/integration-aws-node/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/integration-aws-node/package.json b/packages/integration-aws-node/package.json index 65403727b6..ab0f3583a4 100644 --- a/packages/integration-aws-node/package.json +++ b/packages/integration-aws-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-aws-node", - "version": "0.1.16-next.0", + "version": "0.1.17", "description": "Helpers for fetching AWS account credentials", "backstage": { "role": "node-library" diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index 58fe14bfb1..86a5720839 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/integration-react +## 1.2.9 + +### Patch Changes + +- 998fd15: Separated gitlab `write_repository` and `api` scope to pass checks in `RefreshingAuthSessionManager` +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration@1.17.1 + +## 1.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## 1.2.9-next.0 + +### Patch Changes + +- 998fd15: Separated gitlab `write_repository` and `api` scope to pass checks in `RefreshingAuthSessionManager` +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/config@1.3.2 + - @backstage/core-plugin-api@1.10.8 + +## 1.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.8 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + +## 1.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/config@1.3.2 + +## 1.2.7-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + ## 1.2.7-next.2 ### Patch Changes diff --git a/packages/integration-react/catalog-info.yaml b/packages/integration-react/catalog-info.yaml index 8df5e52c95..1b6876ed99 100644 --- a/packages/integration-react/catalog-info.yaml +++ b/packages/integration-react/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index bf99892817..0c042da567 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-react", - "version": "1.2.7-next.2", + "version": "1.2.9", "description": "Frontend package for managing integrations towards external systems", "backstage": { "role": "web-library" diff --git a/packages/integration-react/src/api/ScmAuth.ts b/packages/integration-react/src/api/ScmAuth.ts index 011422d4cf..823937ac28 100644 --- a/packages/integration-react/src/api/ScmAuth.ts +++ b/packages/integration-react/src/api/ScmAuth.ts @@ -173,7 +173,7 @@ export class ScmAuth implements ScmAuthApi { const host = options?.host ?? 'gitlab.com'; return new ScmAuth('gitlab', gitlabAuthApi, host, { default: ['read_user', 'read_api', 'read_repository'], - repoWrite: ['write_repository api'], + repoWrite: ['write_repository', 'api'], }); } diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index 6a67d797c8..74f899dc77 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,103 @@ # @backstage/integration +## 1.17.1 + +### Patch Changes + +- e0189b8: UrlReader: Fix handling of access tokens for GitLab readURL requests +- d1e4a6d: Fixed bug where the GitLab user token and GitLab integration token were being merged together +- Updated dependencies + - @backstage/config@1.3.3 + +## 1.17.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + +## 1.17.1-next.0 + +### Patch Changes + +- e0189b8: UrlReader: Fix handling of access tokens for GitLab readURL requests +- d1e4a6d: Fixed bug where the GitLab user token and GitLab integration token were being merged together +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 1.17.0 + +### Minor Changes + +- d945206: Added support for federated credentials using managed identities in the Azure DevOps integration. Federated credentials are only available for Azure DevOps organizations that have been configured to use Entra ID for authentication. + + ```diff + integrations: + azure: + - host: dev.azure.com + credentials: + + - clientId: ${APP_REGISTRATION_CLIENT_ID} + + managedIdentityClientId: system-assigned + + tenantId: ${AZURE_TENANT_ID} + ``` + + This also adds support for automatically using the system-assigned managed identity of an Azure resource by specifying `system-assigned` as the client ID of the managed identity. + + ```diff + integrations: + azure: + - host: dev.azure.com + credentials: + - - clientId: ${AZURE_CLIENT_ID} + + - clientId: system-assigned + ``` + +- f134cea: Implement Edit URL feature for Gerrit 3.9+. + + It's possible to disable the edit url by adding the `disableEditUrl: true` config in the Gerrit integration. + +### Patch Changes + +- f3381d3: Added missing `organizations` property to `azure` section in `config.d.ts` file +- acea1d4: update documentation +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 1.17.0-next.3 + +### Minor Changes + +- d945206: Added support for federated credentials using managed identities in the Azure DevOps integration. Federated credentials are only available for Azure DevOps organizations that have been configured to use Entra ID for authentication. + + ```diff + integrations: + azure: + - host: dev.azure.com + credentials: + + - clientId: ${APP_REGISTRATION_CLIENT_ID} + + managedIdentityClientId: system-assigned + + tenantId: ${AZURE_TENANT_ID} + ``` + + This also adds support for automatically using the system-assigned managed identity of an Azure resource by specifying `system-assigned` as the client ID of the managed identity. + + ```diff + integrations: + azure: + - host: dev.azure.com + credentials: + - - clientId: ${AZURE_CLIENT_ID} + + - clientId: system-assigned + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + ## 1.17.0-next.2 ### Minor Changes diff --git a/packages/integration/catalog-info.yaml b/packages/integration/catalog-info.yaml index 7f602c42be..5f9e30681a 100644 --- a/packages/integration/catalog-info.yaml +++ b/packages/integration/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-common-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index 3bfcf42203..4955d0fac3 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -62,6 +62,7 @@ export interface Config { clientSecret?: string; tenantId?: string; personalAccessToken?: string; + managedIdentityClientId?: string; }[]; /** * PGP signing key for signing commits. diff --git a/packages/integration/package.json b/packages/integration/package.json index 053a3e793a..cb78f7b6ad 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "1.17.0-next.2", + "version": "1.17.1", "description": "Helpers for managing integrations towards external systems", "backstage": { "role": "common-library" diff --git a/packages/integration/report.api.md b/packages/integration/report.api.md index 9c1ad3ae82..f07249bc13 100644 --- a/packages/integration/report.api.md +++ b/packages/integration/report.api.md @@ -137,6 +137,7 @@ export interface AzureCredentialsManager { // @public export type AzureDevOpsCredential = | AzureClientSecretCredential + | AzureManagedIdentityClientAssertionCredential | AzureManagedIdentityCredential | PersonalAccessTokenCredential; @@ -144,11 +145,13 @@ export type AzureDevOpsCredential = export type AzureDevOpsCredentialKind = | 'PersonalAccessToken' | 'ClientSecret' - | 'ManagedIdentity'; + | 'ManagedIdentity' + | 'ManagedIdentityClientAssertion'; // @public export type AzureDevOpsCredentialLike = Omit< Partial & + Partial & Partial & Partial, 'kind' @@ -204,10 +207,19 @@ export type AzureIntegrationConfig = { commitSigningKey?: string; }; +// @public +export type AzureManagedIdentityClientAssertionCredential = + AzureCredentialBase & { + kind: 'ManagedIdentityClientAssertion'; + tenantId: string; + clientId: string; + managedIdentityClientId: 'system-assigned' | string; + }; + // @public export type AzureManagedIdentityCredential = AzureCredentialBase & { kind: 'ManagedIdentity'; - clientId: string; + clientId: 'system-assigned' | string; }; // @public @@ -576,6 +588,7 @@ export function getGitilesAuthenticationUrl( export function getGitLabFileFetchUrl( url: string, config: GitLabIntegrationConfig, + token?: string, ): Promise; // @public diff --git a/packages/integration/src/azure/CachedAzureDevOpsCredentialsProvider.test.ts b/packages/integration/src/azure/CachedAzureDevOpsCredentialsProvider.test.ts index c9fbb4c324..e65d127ce8 100644 --- a/packages/integration/src/azure/CachedAzureDevOpsCredentialsProvider.test.ts +++ b/packages/integration/src/azure/CachedAzureDevOpsCredentialsProvider.test.ts @@ -16,6 +16,7 @@ import { CachedAzureDevOpsCredentialsProvider } from './CachedAzureDevOpsCredentialsProvider'; import { AccessToken, + ClientAssertionCredential, ClientSecretCredential, ManagedIdentityCredential, } from '@azure/identity'; @@ -24,6 +25,11 @@ const MockedClientSecretCredential = ClientSecretCredential as jest.MockedClass< typeof ClientSecretCredential >; +const MockedClientAssertionCredential = + ClientAssertionCredential as jest.MockedClass< + typeof ClientAssertionCredential + >; + const MockedManagedIdentityCredential = ManagedIdentityCredential as jest.MockedClass< typeof ManagedIdentityCredential @@ -46,6 +52,13 @@ describe('CachedAzureDevOpsCredentialsProvider', () => { } as AccessToken), ); + MockedClientAssertionCredential.prototype.getToken.mockImplementation(() => + Promise.resolve({ + expiresOnTimestamp: Date.now() + hours(8), + token: 'fake-client-assertion-token', + } as AccessToken), + ); + MockedManagedIdentityCredential.prototype.getToken.mockImplementation(() => Promise.resolve({ expiresOnTimestamp: Date.now() + hours(8), @@ -89,6 +102,25 @@ describe('CachedAzureDevOpsCredentialsProvider', () => { expect(token).toBe('fake-client-secret-token'); }); + it('Should return a bearer credential when a managed identity client assertion is configured', async () => { + const manager = + CachedAzureDevOpsCredentialsProvider.fromAzureDevOpsCredential({ + kind: 'ManagedIdentityClientAssertion', + clientId: 'id', + managedIdentityClientId: 'managedIdentityClientId', + tenantId: 'tenantId', + }); + + const { headers, type, token } = await manager.getCredentials(); + + expect(headers).toStrictEqual({ + Authorization: `Bearer fake-client-assertion-token`, + }); + + expect(type).toBe('bearer'); + expect(token).toBe('fake-client-assertion-token'); + }); + it('Should return a bearer credential when a managed identity is configured', async () => { const manager = CachedAzureDevOpsCredentialsProvider.fromAzureDevOpsCredential({ @@ -124,6 +156,24 @@ describe('CachedAzureDevOpsCredentialsProvider', () => { ).toHaveBeenCalledTimes(1); }); + it('Should not refresh the managed identity client assertion token when it does not expire within 10 minutes', async () => { + const manager = + CachedAzureDevOpsCredentialsProvider.fromAzureDevOpsCredential({ + kind: 'ManagedIdentityClientAssertion', + clientId: 'id', + managedIdentityClientId: 'managedIdentityClientId', + tenantId: 'tenantId', + }); + + await manager.getCredentials(); + await manager.getCredentials(); + await manager.getCredentials(); + + expect( + MockedClientAssertionCredential.prototype.getToken, + ).toHaveBeenCalledTimes(1); + }); + it('Should return the managed identity token when it does not expire within 10 minutes', async () => { const manager = CachedAzureDevOpsCredentialsProvider.fromAzureDevOpsCredential({ @@ -164,6 +214,30 @@ describe('CachedAzureDevOpsCredentialsProvider', () => { ).toHaveBeenCalledTimes(2); }); + it('Should refresh the managed identity client assertion token when it expires within 10 minutes', async () => { + const manager = + CachedAzureDevOpsCredentialsProvider.fromAzureDevOpsCredential({ + kind: 'ManagedIdentityClientAssertion', + clientId: 'id', + managedIdentityClientId: 'managedIdentityClientId', + tenantId: 'tenantId', + }); + + MockedClientAssertionCredential.prototype.getToken.mockImplementation(() => + Promise.resolve({ + expiresOnTimestamp: Date.now() + minutes(9) + seconds(59), + token: 'fake-client-assertion-token', + } as AccessToken), + ); + + await manager.getCredentials(); + await manager.getCredentials(); + + expect( + MockedClientAssertionCredential.prototype.getToken, + ).toHaveBeenCalledTimes(2); + }); + it('Should refresh the managed identity token when it expires within 10 minutes', async () => { const manager = CachedAzureDevOpsCredentialsProvider.fromAzureDevOpsCredential({ diff --git a/packages/integration/src/azure/CachedAzureDevOpsCredentialsProvider.ts b/packages/integration/src/azure/CachedAzureDevOpsCredentialsProvider.ts index aee4cefc41..be721e483e 100644 --- a/packages/integration/src/azure/CachedAzureDevOpsCredentialsProvider.ts +++ b/packages/integration/src/azure/CachedAzureDevOpsCredentialsProvider.ts @@ -15,6 +15,7 @@ */ import { AzureDevOpsCredential, PersonalAccessTokenCredential } from './config'; import { + ClientAssertionCredential, ClientSecretCredential, ManagedIdentityCredential, TokenCredential, @@ -23,6 +24,7 @@ import { AzureDevOpsCredentials, AzureDevOpsCredentialsProvider, } from './types'; +import { ManagedIdentityClientAssertion } from './ManagedIdentityClientAssertion'; type CachedAzureDevOpsCredentials = AzureDevOpsCredentials & { expiresAt?: number; @@ -59,9 +61,26 @@ export class CachedAzureDevOpsCredentialsProvider credential.clientSecret, ), ); + + case 'ManagedIdentityClientAssertion': { + const clientAssertion = new ManagedIdentityClientAssertion({ + clientId: credential.managedIdentityClientId, + }); + + return CachedAzureDevOpsCredentialsProvider.fromTokenCredential( + new ClientAssertionCredential( + credential.tenantId, + credential.clientId, + () => clientAssertion.getSignedAssertion(), + ), + ); + } + case 'ManagedIdentity': return CachedAzureDevOpsCredentialsProvider.fromTokenCredential( - new ManagedIdentityCredential(credential.clientId), + credential.clientId === 'system-assigned' + ? new ManagedIdentityCredential() + : new ManagedIdentityCredential(credential.clientId), ); default: exhaustiveCheck(credential); diff --git a/packages/integration/src/azure/ClientAssertion.ts b/packages/integration/src/azure/ClientAssertion.ts new file mode 100644 index 0000000000..752dcf4dd1 --- /dev/null +++ b/packages/integration/src/azure/ClientAssertion.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type ClientAssertion = { + /** + * The signed assertion token + */ + signedAssertion: string; + /** + * The assertion's expiration timestamp in milliseconds, UNIX epoch time. + */ + expiresOnTimestamp: number; +}; diff --git a/packages/integration/src/azure/ManagedIdentityClientAssertion.test.ts b/packages/integration/src/azure/ManagedIdentityClientAssertion.test.ts new file mode 100644 index 0000000000..102081c698 --- /dev/null +++ b/packages/integration/src/azure/ManagedIdentityClientAssertion.test.ts @@ -0,0 +1,124 @@ +/* + * 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 { ManagedIdentityClientAssertion } from './ManagedIdentityClientAssertion'; +import { ManagedIdentityCredential, AccessToken } from '@azure/identity'; + +const seconds = (s: number) => s * 1000; +const minutes = (m: number) => seconds(60) * m; +const hours = (h: number) => minutes(60) * h; +const MockedManagedIdentityCredential = + ManagedIdentityCredential as jest.MockedClass< + typeof ManagedIdentityCredential + >; + +jest.mock('@azure/identity'); + +describe('ManagedIdentityClientAssertion', () => { + beforeEach(() => { + jest.resetAllMocks(); + + MockedManagedIdentityCredential.prototype.getToken.mockImplementation(() => + Promise.resolve({ + expiresOnTimestamp: Date.now() + hours(8), + token: 'fake-managed-identity-token', + } as AccessToken), + ); + }); + + it('Should return a cached token if it does not expire within 5 minutes', async () => { + const clientAssertion = new ManagedIdentityClientAssertion({ + clientId: 'clientId', + }); + + // First call to getToken to cache the token + await clientAssertion.getSignedAssertion(); + // Second call should return the cached token + const token = await clientAssertion.getSignedAssertion(); + + expect(token).toBe('fake-managed-identity-token'); + expect( + MockedManagedIdentityCredential.prototype.getToken, + ).toHaveBeenCalledTimes(1); + }); + + it('Should obtain a new token if the cached token expires within 5 minutes', async () => { + const clientAssertion = new ManagedIdentityClientAssertion({ + clientId: 'clientId', + }); + + MockedManagedIdentityCredential.prototype.getToken.mockImplementationOnce( + () => + Promise.resolve({ + expiresOnTimestamp: Date.now() + minutes(4), + token: 'expiring-soon-token', + } as AccessToken), + ); + + // First call to getToken to cache the expiring token + await clientAssertion.getSignedAssertion(); + + MockedManagedIdentityCredential.prototype.getToken.mockImplementationOnce( + () => + Promise.resolve({ + expiresOnTimestamp: Date.now() + hours(8), + token: 'new-managed-identity-token', + } as AccessToken), + ); + + // Second call should obtain a new token + const token = await clientAssertion.getSignedAssertion(); + + expect(token).toBe('new-managed-identity-token'); + expect( + MockedManagedIdentityCredential.prototype.getToken, + ).toHaveBeenCalledTimes(2); + }); + + it('Should obtain a new token if no token is cached', async () => { + const clientAssertion = new ManagedIdentityClientAssertion({ + clientId: 'clientId', + }); + + const token = await clientAssertion.getSignedAssertion(); + + expect(token).toBe('fake-managed-identity-token'); + expect( + MockedManagedIdentityCredential.prototype.getToken, + ).toHaveBeenCalledTimes(1); + }); + + it('Should request a token for the correct scope', async () => { + const clientAssertion = new ManagedIdentityClientAssertion({ + clientId: 'clientId', + }); + + await clientAssertion.getSignedAssertion(); + + expect( + MockedManagedIdentityCredential.prototype.getToken, + ).toHaveBeenCalledWith('api://AzureADTokenExchange'); + }); + + it('Should handle system-assigned managed identity', async () => { + const clientAssertion = new ManagedIdentityClientAssertion(); + + await clientAssertion.getSignedAssertion(); + + expect( + MockedManagedIdentityCredential.prototype.getToken, + ).toHaveBeenCalledWith('api://AzureADTokenExchange'); + }); +}); diff --git a/packages/integration/src/azure/ManagedIdentityClientAssertion.ts b/packages/integration/src/azure/ManagedIdentityClientAssertion.ts new file mode 100644 index 0000000000..5fca816634 --- /dev/null +++ b/packages/integration/src/azure/ManagedIdentityClientAssertion.ts @@ -0,0 +1,75 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ManagedIdentityCredential } from '@azure/identity'; +import { ClientAssertion } from './ClientAssertion'; + +export type ManagedIdentityClientAssertionOptions = { + clientId?: string; +}; + +const fiveMinutes = 5 * 60 * 1000; // 5 minutes in milliseconds +const expiresWithinFiveMinutes = (clientAssertion: ClientAssertion) => + clientAssertion.expiresOnTimestamp - Date.now() <= fiveMinutes; + +/** + * Class representing a Managed Identity Client Assertion. + * This class is responsible for obtaining a signed client assertion using Azure Managed Identity. + */ +export class ManagedIdentityClientAssertion { + private credential: ManagedIdentityCredential; + private clientAssertion?: ClientAssertion; + + /** + * Creates an instance of ManagedIdentityClientAssertion. + * @param options - Optional parameters for the ManagedIdentityClientAssertion. + * - clientId: The client ID of the managed identity. If not provided, 'system-assigned' is used. + */ + constructor(options?: ManagedIdentityClientAssertionOptions) { + let { clientId } = options || {}; + clientId ??= 'system-assigned'; + + this.credential = + clientId === 'system-assigned' + ? new ManagedIdentityCredential() + : new ManagedIdentityCredential(clientId); + } + + /** + * Gets a signed client assertion. + * If a valid client assertion is already cached which doesn't expire soon, it returns the cached assertion. + * Otherwise, it obtains a new access token and creates a new client assertion. + * @returns A promise that resolves to the signed client assertion. + */ + public async getSignedAssertion(): Promise { + if ( + this.clientAssertion !== undefined && + !expiresWithinFiveMinutes(this.clientAssertion) + ) { + return this.clientAssertion.signedAssertion; + } + + const accessToken = await this.credential.getToken( + 'api://AzureADTokenExchange', + ); + + this.clientAssertion = { + signedAssertion: accessToken.token, + expiresOnTimestamp: accessToken.expiresOnTimestamp, + }; + + return accessToken.token; + } +} diff --git a/packages/integration/src/azure/config.test.ts b/packages/integration/src/azure/config.test.ts index dc6781c45e..c1ddf1590f 100644 --- a/packages/integration/src/azure/config.test.ts +++ b/packages/integration/src/azure/config.test.ts @@ -167,7 +167,7 @@ describe('readAzureIntegrationConfig', () => { }); }); - it('reads all values when using a managed identity credential', () => { + it('reads all values when using a managed identity client assertion credential', () => { const output = readAzureIntegrationConfig( buildConfig({ host: 'dev.azure.com', @@ -175,6 +175,62 @@ describe('readAzureIntegrationConfig', () => { { organizations: ['org1', 'org2'], clientId: 'id', + managedIdentityClientId: 'system-assigned', + tenantId: 'tenant', + }, + ], + }), + ); + + expect(output).toEqual({ + host: 'dev.azure.com', + credentials: [ + { + kind: 'ManagedIdentityClientAssertion', + organizations: ['org1', 'org2'], + clientId: 'id', + managedIdentityClientId: 'system-assigned', + tenantId: 'tenant', + }, + ], + }); + }); + + it('reads all values when using a managed identity client assertion credential (without organizations)', () => { + const output = readAzureIntegrationConfig( + buildConfig({ + host: 'dev.azure.com', + credentials: [ + { + clientId: 'id', + managedIdentityClientId: 'system-assigned', + tenantId: 'tenant', + }, + ], + }), + ); + + expect(output).toEqual({ + host: 'dev.azure.com', + credentials: [ + { + kind: 'ManagedIdentityClientAssertion', + clientId: 'id', + managedIdentityClientId: 'system-assigned', + tenantId: 'tenant', + }, + ], + }); + }); + + it('reads all values when using a managed identity credential', () => { + const output = readAzureIntegrationConfig( + buildConfig({ + host: 'dev.azure.com', + credentials: [ + { + organizations: ['org1', 'org2'], + clientId: 'system-assigned', }, ], }), @@ -186,7 +242,7 @@ describe('readAzureIntegrationConfig', () => { { kind: 'ManagedIdentity', organizations: ['org1', 'org2'], - clientId: 'id', + clientId: 'system-assigned', }, ], }); diff --git a/packages/integration/src/azure/config.ts b/packages/integration/src/azure/config.ts index 2910387829..09b97755ec 100644 --- a/packages/integration/src/azure/config.ts +++ b/packages/integration/src/azure/config.ts @@ -71,7 +71,8 @@ export type AzureIntegrationConfig = { export type AzureDevOpsCredentialKind = | 'PersonalAccessToken' | 'ClientSecret' - | 'ManagedIdentity'; + | 'ManagedIdentity' + | 'ManagedIdentityClientAssertion'; /** * Common fields for the Azure DevOps credentials. @@ -109,6 +110,31 @@ export type AzureClientSecretCredential = AzureCredentialBase & { clientSecret: string; }; +/** + * A client assertion credential that uses a managed identity to generate a client assertion (JWT). + * @public + */ +export type AzureManagedIdentityClientAssertionCredential = + AzureCredentialBase & { + kind: 'ManagedIdentityClientAssertion'; + /** + * The Entra ID tenant + */ + tenantId: string; + + /** + * The client ID of the app registration you want to authenticate as. + */ + clientId: string; + + /** + * The client ID of the managed identity used to generate a client assertion (JWT). + * Set to "system-assigned" to automatically use the system-assigned managed identity. + * For user-assigned managed identities, specify the client ID of the managed identity you want to use. + */ + managedIdentityClientId: 'system-assigned' | string; + }; + /** * A managed identity credential. * @public @@ -118,7 +144,7 @@ export type AzureManagedIdentityCredential = AzureCredentialBase & { /** * The clientId */ - clientId: string; + clientId: 'system-assigned' | string; }; /** @@ -136,6 +162,7 @@ export type PersonalAccessTokenCredential = AzureCredentialBase & { */ export type AzureDevOpsCredentialLike = Omit< Partial & + Partial & Partial & Partial, 'kind' @@ -147,12 +174,14 @@ export type AzureDevOpsCredentialLike = Omit< */ export type AzureDevOpsCredential = | AzureClientSecretCredential + | AzureManagedIdentityClientAssertionCredential | AzureManagedIdentityCredential | PersonalAccessTokenCredential; const AzureDevOpsCredentialFields = [ 'clientId', 'clientSecret', + 'managedIdentityClientId', 'tenantId', 'personalAccessToken', ] as const; @@ -164,6 +193,10 @@ const AzureDevopsCredentialFieldMap = new Map< >([ ['ClientSecret', ['clientId', 'clientSecret', 'tenantId']], ['ManagedIdentity', ['clientId']], + [ + 'ManagedIdentityClientAssertion', + ['clientId', 'managedIdentityClientId', 'tenantId'], + ], ['PersonalAccessToken', ['personalAccessToken']], ]); @@ -213,9 +246,12 @@ export function readAzureIntegrationConfig( personalAccessToken: credential .getOptionalString('personalAccessToken') ?.trim(), - tenantId: credential.getOptionalString('tenantId'), - clientId: credential.getOptionalString('clientId'), + tenantId: credential.getOptionalString('tenantId')?.trim(), + clientId: credential.getOptionalString('clientId')?.trim(), clientSecret: credential.getOptionalString('clientSecret')?.trim(), + managedIdentityClientId: credential + .getOptionalString('managedIdentityClientId') + ?.trim(), }; return result; diff --git a/packages/integration/src/azure/index.ts b/packages/integration/src/azure/index.ts index b5f5c0a316..a447c8ffd8 100644 --- a/packages/integration/src/azure/index.ts +++ b/packages/integration/src/azure/index.ts @@ -25,6 +25,7 @@ export type { AzureCredentialBase, AzureClientSecretCredential, AzureManagedIdentityCredential, + AzureManagedIdentityClientAssertionCredential, PersonalAccessTokenCredential, AzureDevOpsCredentialLike, AzureDevOpsCredential, diff --git a/packages/integration/src/gitlab/core.test.ts b/packages/integration/src/gitlab/core.test.ts index 81adc29701..d514dba755 100644 --- a/packages/integration/src/gitlab/core.test.ts +++ b/packages/integration/src/gitlab/core.test.ts @@ -188,7 +188,7 @@ describe('gitlab core', () => { }); describe('getGitLabRequestOptions', () => { - it('should return Authorization header when oauthToken is provided', () => { + it('should return Authorization bearer header when a token is provided', () => { const token = '1234567890'; const result = getGitLabRequestOptions( configSelfHosteWithRelativePath, @@ -202,29 +202,12 @@ describe('gitlab core', () => { }); }); - it('should return private-token header when gl-token is provided', () => { - const token = 'glpat-1234566'; - const result = getGitLabRequestOptions( - configSelfHosteWithRelativePath, - token, - ); + it('should return Authorization bearer header using the config token when no token is provided', () => { + const result = getGitLabRequestOptions(configSelfHosteWithRelativePath); expect(result).toEqual({ headers: { - 'PRIVATE-TOKEN': token, - }, - }); - }); - - it('should return private-token header when oauthToken is undefined', () => { - const oauthToken = undefined; - const result = getGitLabRequestOptions( - configSelfHosteWithRelativePath, - oauthToken, - ); - expect(result).toEqual({ - headers: { - 'PRIVATE-TOKEN': configSelfHosteWithRelativePath.token, + Authorization: `Bearer ${configSelfHosteWithRelativePath.token}`, }, }); }); diff --git a/packages/integration/src/gitlab/core.ts b/packages/integration/src/gitlab/core.ts index 4a17b2a15e..65360840b5 100644 --- a/packages/integration/src/gitlab/core.ts +++ b/packages/integration/src/gitlab/core.ts @@ -40,8 +40,9 @@ import { export async function getGitLabFileFetchUrl( url: string, config: GitLabIntegrationConfig, + token?: string, ): Promise { - const projectID = await getProjectId(url, config); + const projectID = await getProjectId(url, config, token); return buildProjectUrl(url, projectID, config).toString(); } @@ -56,20 +57,17 @@ export function getGitLabRequestOptions( config: GitLabIntegrationConfig, token?: string, ): { headers: Record } { - if (token) { - // If token comes from the user and starts with "gl", it's a private token (see https://docs.gitlab.com/ee/security/token_overview.html#token-prefixes) - return { - headers: token.startsWith('gl') - ? { 'PRIVATE-TOKEN': token } - : { Authorization: `Bearer ${token}` }, // Otherwise, it's a bearer token - }; + const headers: Record = {}; + + const accessToken = token || config.token; + if (accessToken) { + // OAuth, Personal, Project, and Group access tokens can all be passed via + // a bearer authorization header + // https://docs.gitlab.com/api/rest/authentication/#personalprojectgroup-access-tokens + headers.Authorization = `Bearer ${accessToken}`; } - // If token not provided, fetch the integration token - const { token: configToken = '' } = config; - return { - headers: { 'PRIVATE-TOKEN': configToken }, - }; + return { headers }; } // Converts @@ -113,6 +111,7 @@ export function buildProjectUrl( export async function getProjectId( target: string, config: GitLabIntegrationConfig, + token?: string, ): Promise { const url = new URL(target); @@ -143,12 +142,18 @@ export async function getProjectId( const response = await fetch( repoIDLookup.toString(), - getGitLabRequestOptions(config), + getGitLabRequestOptions(config, token), ); const data = await response.json(); if (!response.ok) { + if (response.status === 401) { + throw new Error( + 'GitLab Error: 401 - Unauthorized. The access token used is either expired, or does not have permission to read the project', + ); + } + throw new Error( `GitLab Error '${data.error}', ${data.error_description}`, ); diff --git a/packages/opaque-internal/catalog-info.yaml b/packages/opaque-internal/catalog-info.yaml index b1c5d01937..8fd8136d34 100644 --- a/packages/opaque-internal/catalog-info.yaml +++ b/packages/opaque-internal/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-common-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/release-manifests/CHANGELOG.md b/packages/release-manifests/CHANGELOG.md index e88324250f..db9a60d0f4 100644 --- a/packages/release-manifests/CHANGELOG.md +++ b/packages/release-manifests/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/release-manifests +## 0.0.13 + +### Patch Changes + +- 163f3da: This expands the configurability of `release-manifests` to pave the road for more configuration options in the `cli`. + + Specifically it allows the specification of mirrored, proxied, or air-gapped hosts when upgrading across releases when + working in restricted or heavily governed development environments (common in large enterprises and government + entities). + +## 0.0.13-next.0 + +### Patch Changes + +- 163f3da: This expands the configurability of `release-manifests` to pave the road for more configuration options in the `cli`. + + Specifically it allows the specification of mirrored, proxied, or air-gapped hosts when upgrading across releases when + working in restricted or heavily governed development environments (common in large enterprises and government + entities). + ## 0.0.12 ### Patch Changes diff --git a/packages/release-manifests/catalog-info.yaml b/packages/release-manifests/catalog-info.yaml index 4564145a3d..dfef3b2a5f 100644 --- a/packages/release-manifests/catalog-info.yaml +++ b/packages/release-manifests/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-common-library - owner: maintainers + owner: operations-maintainers diff --git a/packages/release-manifests/package.json b/packages/release-manifests/package.json index 0b24a93c6a..7998845125 100644 --- a/packages/release-manifests/package.json +++ b/packages/release-manifests/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/release-manifests", - "version": "0.0.12", + "version": "0.0.13", "description": "Helper library for receiving release manifests", "backstage": { "role": "common-library" diff --git a/packages/release-manifests/report.api.md b/packages/release-manifests/report.api.md index 2d2cc00767..5ef8345ec0 100644 --- a/packages/release-manifests/report.api.md +++ b/packages/release-manifests/report.api.md @@ -11,6 +11,14 @@ export function getManifestByReleaseLine( // @public export type GetManifestByReleaseLineOptions = { releaseLine: string; + fetch?: ( + url: string, + options?: { + signal?: AbortSignal; + }, + ) => Promise>; + gitHubRawBaseUrl?: string; + versionsBaseUrl?: string; }; // @public @@ -27,6 +35,8 @@ export type GetManifestByVersionOptions = { signal?: AbortSignal; }, ) => Promise>; + gitHubRawBaseUrl?: string; + versionsBaseUrl?: string; }; // @public diff --git a/packages/release-manifests/src/manifest.test.ts b/packages/release-manifests/src/manifest.test.ts index ad5bf13496..9cb100376a 100644 --- a/packages/release-manifests/src/manifest.test.ts +++ b/packages/release-manifests/src/manifest.test.ts @@ -86,6 +86,71 @@ describe('Release Manifests', () => { getManifestByVersion({ version: '0.0.0', fetch: mockFetch }), ).rejects.toThrow('No release found for 0.0.0 version'); }); + + it('should allow overriding the versions host', async () => { + const mockFetch = jest.fn().mockImplementation(async url => ({ + status: 200, + url, + json: () => ({ + packages: [{ name: '@backstage/core', version: '2.3.4' }], + }), + })); + + const pkgs = await getManifestByVersion({ + version: '0.0.0', + fetch: mockFetch, + versionsBaseUrl: 'https://versions.some-test-host.com', + }); + + expect(pkgs.packages).toEqual([ + { + name: '@backstage/core', + version: '2.3.4', + }, + ]); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://versions.some-test-host.com/v1/releases/0.0.0/manifest.json', + expect.anything(), + ); + }); + + it('should allow overriding github host', async () => { + const mockFetch = jest.fn().mockImplementation(async url => { + if ( + url === + 'https://versions.some-test-host.com/v1/releases/0.0.0/manifest.json' + ) { + return { + status: 200, + url, + json: () => ({ + packages: [{ name: '@backstage/core', version: '2.3.4' }], + }), + }; + } + + throw new Error('Host not found'); + }); + + const pkgs = await getManifestByVersion({ + version: '0.0.0', + fetch: mockFetch, + gitHubRawBaseUrl: 'https://versions.some-test-host.com', + }); + + expect(pkgs.packages).toEqual([ + { + name: '@backstage/core', + version: '2.3.4', + }, + ]); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://versions.some-test-host.com/v1/releases/0.0.0/manifest.json', + expect.anything(), + ); + }); }); describe('getManifestByReleaseLine', () => { @@ -119,6 +184,121 @@ describe('Release Manifests', () => { getManifestByReleaseLine({ releaseLine: 'foo' }), ).rejects.toThrow("No 'foo' release line found"); }); + + it('should allow overriding the fetch implementation', async () => { + const mockFetch = jest.fn().mockImplementation(async url => ({ + status: 200, + url, + json: () => ({ + packages: [{ name: '@backstage/core', version: '1.2.3' }], + }), + })); + + const pkgs = await getManifestByReleaseLine({ + releaseLine: 'main', + fetch: mockFetch, + }); + + expect(pkgs.packages).toEqual([ + { + name: '@backstage/core', + version: '1.2.3', + }, + ]); + + mockFetch.mockImplementation(async url => ({ + status: 404, + url, + })); + + await expect( + getManifestByReleaseLine({ releaseLine: 'foo', fetch: mockFetch }), + ).rejects.toThrow("No 'foo' release line found"); + }); + + it('should allow overriding the versions host', async () => { + const mockFetch = jest.fn().mockImplementation(async url => ({ + status: 200, + url, + json: () => ({ + packages: [{ name: '@backstage/core', version: '1.2.3' }], + }), + })); + + const pkgs = await getManifestByReleaseLine({ + releaseLine: 'main', + fetch: mockFetch, + versionsBaseUrl: 'https://versions.some-test-host.com', + }); + + expect(pkgs.packages).toEqual([ + { + name: '@backstage/core', + version: '1.2.3', + }, + ]); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://versions.some-test-host.com/v1/tags/main/manifest.json', + expect.anything(), + ); + }); + + it('should allow overriding github host', async () => { + const mockFetch = jest.fn().mockImplementation(async url => { + if ( + url === + 'https://hosted.raw.internal-github.com/backstage/versions/main/v1/tags/main' + ) { + return { + ok: true, + status: 200, + url, + text: () => + 'https://hosted.raw.internal-github.com/backstage/versions/tags/main', + }; + } + + if ( + url === + 'https://hosted.raw.internal-github.com/backstage/versions/tags/main/manifest.json' + ) { + return { + status: 200, + url, + json: () => ({ + packages: [{ name: '@backstage/core', version: '1.2.3' }], + }), + }; + } + + throw new Error('Host Not Found'); + }); + + const pkgs = await getManifestByReleaseLine({ + releaseLine: 'main', + fetch: mockFetch, + gitHubRawBaseUrl: + 'https://hosted.raw.internal-github.com/backstage/versions/main', + }); + + expect(pkgs.packages).toEqual([ + { + name: '@backstage/core', + version: '1.2.3', + }, + ]); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://hosted.raw.internal-github.com/backstage/versions/main/v1/tags/main', + expect.anything(), + ); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://hosted.raw.internal-github.com/backstage/versions/tags/main/manifest.json', + expect.anything(), + ); + }); }); }); diff --git a/packages/release-manifests/src/manifest.ts b/packages/release-manifests/src/manifest.ts index 66a5f62151..8db9d59d39 100644 --- a/packages/release-manifests/src/manifest.ts +++ b/packages/release-manifests/src/manifest.ts @@ -37,6 +37,8 @@ export type GetManifestByVersionOptions = { url: string, options?: { signal?: AbortSignal }, ) => Promise>; + gitHubRawBaseUrl?: string; + versionsBaseUrl?: string; }; // Wait for waitMs, or until signal is aborted. @@ -88,19 +90,18 @@ export async function getManifestByVersion( const versionEnc = encodeURIComponent(options.version); const fetchFn = options.fetch ?? fetch; + const versionsHost = options.versionsBaseUrl ?? VERSIONS_BASE_URL; + const gitHubRawBaseUrl = options.gitHubRawBaseUrl ?? GITHUB_RAW_BASE_URL; const res = await withFallback( signal => - fetchFn(`${VERSIONS_BASE_URL}/v1/releases/${versionEnc}/manifest.json`, { + fetchFn(`${versionsHost}/v1/releases/${versionEnc}/manifest.json`, { signal, }), signal => - fetchFn( - `${GITHUB_RAW_BASE_URL}/v1/releases/${versionEnc}/manifest.json`, - { - signal, - }, - ), + fetchFn(`${gitHubRawBaseUrl}/v1/releases/${versionEnc}/manifest.json`, { + signal, + }), 500, ); if (res.status === 404) { @@ -120,6 +121,12 @@ export async function getManifestByVersion( */ export type GetManifestByReleaseLineOptions = { releaseLine: string; + fetch?: ( + url: string, + options?: { signal?: AbortSignal }, + ) => Promise>; + gitHubRawBaseUrl?: string; + versionsBaseUrl?: string; }; /** @@ -130,20 +137,27 @@ export async function getManifestByReleaseLine( options: GetManifestByReleaseLineOptions, ): Promise { const releaseEnc = encodeURIComponent(options.releaseLine); + + const fetchFn = options.fetch ?? fetch; + const versionsHost = options.versionsBaseUrl ?? VERSIONS_BASE_URL; + const gitHubRawBaseUrl = options.gitHubRawBaseUrl ?? GITHUB_RAW_BASE_URL; + const res = await withFallback( signal => - fetch(`${VERSIONS_BASE_URL}/v1/tags/${releaseEnc}/manifest.json`, { + fetchFn(`${versionsHost}/v1/tags/${releaseEnc}/manifest.json`, { signal, }), async signal => { // The release tags are symlinks, which we need to follow manually when fetching from GitHub. - const baseUrl = `${GITHUB_RAW_BASE_URL}/v1/tags/${releaseEnc}`; - const linkRes = await fetch(baseUrl, { signal }); + const baseUrl = `${gitHubRawBaseUrl}/v1/tags/${releaseEnc}`; + const linkRes = await fetchFn(baseUrl, { signal }); if (!linkRes.ok) { return linkRes; } const link = (await linkRes.text()).trim(); - return fetch(new URL(`${link}/manifest.json`, baseUrl), { signal }); + return fetchFn(new URL(`${link}/manifest.json`, baseUrl).toString(), { + signal, + }); }, 1000, ); diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index 917b364d2e..49ee1bc1dc 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,151 @@ # @backstage/repo-tools +## 0.15.1-next.1 + +### Patch Changes + +- 33060b5: Removed build-in ignore of the `packages/canon` package for knip reports. + +## 0.15.1-next.0 + +### Patch Changes + +- 225d7c2: Added `stringEnums` to `mustache` templates +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.2 + - @backstage/catalog-model@1.7.5 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## 0.15.0 + +### Minor Changes + +- 1e7082e: Clear output directory before running `package-docs` and skip all internal packages. + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + +## 0.15.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + +## 0.15.0-next.0 + +### Minor Changes + +- 1e7082e: Clear output directory before running `package-docs` and skip all internal packages. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## 0.14.0 + +### Minor Changes + +- bf9a173: Add support for caching the per-package output from the `package-docs` command. + +### Patch Changes + +- 4bff5d0: Fixed a bug where linting would fail with the generated clients when defining top-level `enum` schema values. +- 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. +- c83cd8b: Fixed some circular or otherwise unclear imports +- a372bf1: Updated dependency `@electric-sql/pglite` to `^0.3.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @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.2 + +### Patch Changes + +- 4bff5d0: Fixed a bug where linting would fail with the generated clients when defining top-level `enum` schema values. +- 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.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 + +- b229476: Support passing additional properties to OpenAPI server generator +- 659f2ce: Updated dependency `typedoc` to `^0.28.0`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config-loader@1.10.1 + - @backstage/cli-node@0.2.13 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## 0.13.3-next.3 + +### Patch Changes + +- 659f2ce: Updated dependency `typedoc` to `^0.28.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + ## 0.13.3-next.2 ### Patch Changes diff --git a/packages/repo-tools/catalog-info.yaml b/packages/repo-tools/catalog-info.yaml index 66b5ecc64b..379111cbca 100644 --- a/packages/repo-tools/catalog-info.yaml +++ b/packages/repo-tools/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-cli - owner: maintainers + owner: tooling-maintainers diff --git a/packages/repo-tools/knip-report.md b/packages/repo-tools/knip-report.md index 5eec8fdd29..02f92286c1 100644 --- a/packages/repo-tools/knip-report.md +++ b/packages/repo-tools/knip-report.md @@ -1,6 +1,6 @@ # Knip report -## Unused dependencies (5) +## Unused dependencies (4) | Name | Location | Severity | | :---------------------------------- | :----------- | :------- | @@ -8,7 +8,6 @@ | @stoplight/spectral-runtime | package.json | error | | @electric-sql/pglite | package.json | error | | is-glob | package.json | error | -| glob | package.json | error | ## Unused devDependencies (2) diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 1ec1d1a48f..1c38bed59c 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-next.2", + "version": "0.15.1-next.1", "description": "CLI for Backstage repo tooling ", "backstage": { "role": "cli" @@ -49,7 +49,7 @@ "@backstage/cli-node": "workspace:^", "@backstage/config-loader": "workspace:^", "@backstage/errors": "workspace:^", - "@electric-sql/pglite": "^0.2.15", + "@electric-sql/pglite": "^0.3.0", "@manypkg/get-packages": "^1.1.3", "@microsoft/api-documenter": "^7.25.7", "@microsoft/api-extractor": "^7.47.2", @@ -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:^", @@ -90,7 +92,7 @@ "@types/is-glob": "^4.0.2", "@types/node": "^20.16.0", "@types/prettier": "^2.0.0", - "typedoc": "^0.27.6" + "typedoc": "^0.28.0" }, "peerDependencies": { "@microsoft/api-extractor-model": "*", @@ -98,7 +100,7 @@ "@microsoft/tsdoc-config": "*", "@useoptic/optic": "^1.0.0", "prettier": "^2.8.1", - "typedoc": "^0.27.0", + "typedoc": "^0.28.0", "typescript": "> 3.0.0" }, "peerDependenciesMeta": { diff --git a/packages/repo-tools/src/commands/knip-reports/knip-extractor.ts b/packages/repo-tools/src/commands/knip-reports/knip-extractor.ts index 763989f9c7..a97d1ddd8f 100644 --- a/packages/repo-tools/src/commands/knip-reports/knip-extractor.ts +++ b/packages/repo-tools/src/commands/knip-reports/knip-extractor.ts @@ -21,11 +21,6 @@ import fs from 'fs-extra'; import type { KnipConfig } from 'knip'; import { createBinRunner } from '../util'; -// Ignore these -const ignoredPackages = [ - 'packages/canon', // storybook config is different from the rest -]; - interface KnipExtractionOptions { packageDirs: string[]; isLocalBuild: boolean; @@ -104,10 +99,7 @@ async function handlePackage({ isLocalBuild, }: KnipPackageOptions) { console.log(`## Processing ${packageDir}`); - if (ignoredPackages.includes(packageDir)) { - console.log(`Skipping ${packageDir}`); - return; - } + const fullDir = cliPaths.resolveTargetRoot(packageDir); const reportPath = resolvePath(fullDir, 'knip-report.md'); const run = createBinRunner(cliPaths.targetRoot, ''); 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..174a1d949d 100644 --- a/packages/repo-tools/src/commands/package-docs/command.ts +++ b/packages/repo-tools/src/commands/package-docs/command.ts @@ -17,8 +17,12 @@ 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, rm, writeFile } from 'fs/promises'; import pLimit from 'p-limit'; +import { mkdirp } from 'fs-extra'; +import { PackageDocsCache } from './Cache'; +import { Lockfile } from '@backstage/cli-node'; +import { glob } from 'glob'; const limit = pLimit(8); @@ -38,6 +42,7 @@ const EXCLUDE = [ 'packages/techdocs-cli-embedded-app', 'packages/yarn-plugin', 'packages/backend', + 'packages/backend-legacy', ]; const HIGHLIGHT_LANGUAGES = [ @@ -52,6 +57,7 @@ const HIGHLIGHT_LANGUAGES = [ 'diff', 'js', 'json', + 'docker', ]; function getExports(packageJson: any) { @@ -75,60 +81,104 @@ 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) { console.warn('!!! This is an experimental command !!!'); + + const existingDocsJsonPaths = glob.sync( + cliPaths.resolveTargetRoot('dist-types/**/docs.json'), + ); + if (existingDocsJsonPaths.length > 0) { + console.warn( + `!!! Deleting all ${existingDocsJsonPaths.length} existing docs.json files !!!`, + ); + + for (const path of existingDocsJsonPaths) { + await rm(path, { force: true }); + } + } + console.warn('!!! Deleting existing docs output !!!'); + await rm(cliPaths.resolveTargetRoot('type-docs'), { + recursive: true, + force: true, + }); const selectedPackageDirs = await resolvePackagePaths({ paths, include: opts.include, 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 => limit(async () => { - if (EXCLUDE.includes(pkg)) { + const pkgJson = JSON.parse( + await readFile( + cliPaths.resolveTargetRoot(pkg, 'package.json'), + 'utf-8', + ), + ); + if (EXCLUDE.includes(pkg) || pkgJson.name.startsWith('@internal/')) { 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/packages/canon/src/components/DataTable/Pagination/types.ts b/packages/repo-tools/src/commands/package-docs/constants.ts similarity index 84% rename from packages/canon/src/components/DataTable/Pagination/types.ts rename to packages/repo-tools/src/commands/package-docs/constants.ts index b2a895c465..a8372afad8 100644 --- a/packages/canon/src/components/DataTable/Pagination/types.ts +++ b/packages/repo-tools/src/commands/package-docs/constants.ts @@ -14,6 +14,5 @@ * limitations under the License. */ -/** @public */ -export interface DataTablePaginationProps - extends React.HTMLAttributes {} +export const CACHE_FILE = 'cache.json'; +export const CACHE_DIR = '.cache/package-docs'; diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts index bda80d2fb1..822c644c55 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts @@ -95,6 +95,7 @@ export async function command(opts: OptionValues) { try { await sharedCommand(); } catch (err) { + console.error(chalk.red('Error: ', err)); process.exit(1); } } diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts index 1a8dd36cab..c51b63cf54 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts @@ -60,7 +60,7 @@ async function generateSpecFile() { // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** import {createValidatedOpenApiRouterFromGeneratedEndpointMap} from '@backstage/backend-openapi-utils'; -import {EndpointMap} from './'; +import {EndpointMap} from './apis'; export const spec = ${JSON.stringify(yaml, null, 2)} as const; export const createOpenApiRouter = async ( options?: Parameters['1'], diff --git a/packages/repo-tools/templates/typescript-backstage-client/modelEnum.mustache b/packages/repo-tools/templates/typescript-backstage-client/modelEnum.mustache index 3a1b5ce86d..09bc5bdbca 100644 --- a/packages/repo-tools/templates/typescript-backstage-client/modelEnum.mustache +++ b/packages/repo-tools/templates/typescript-backstage-client/modelEnum.mustache @@ -17,15 +17,4 @@ export enum {{classname}} { * @public */ 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..09bc5bdbca 100644 --- a/packages/repo-tools/templates/typescript-backstage-server/modelEnum.mustache +++ b/packages/repo-tools/templates/typescript-backstage-server/modelEnum.mustache @@ -17,15 +17,4 @@ export enum {{classname}} { * @public */ 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 9ba8836fff..2f1a82c665 100644 --- a/packages/scaffolder-internal/CHANGELOG.md +++ b/packages/scaffolder-internal/CHANGELOG.md @@ -1,5 +1,92 @@ # @internal/scaffolder +## 0.0.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.19.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + +## 0.0.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.18.1-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.0.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.18.0 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.0.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.18.0-next.2 + +## 0.0.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.17.1-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.0 + +## 0.0.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.17.0 + - @backstage/frontend-plugin-api@0.10.3 + +## 0.0.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/plugin-scaffolder-react@1.16.1-next.2 + +## 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 + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/plugin-scaffolder-react@1.16.0 + +## 0.0.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.16.0-next.3 + - @backstage/frontend-plugin-api@0.10.2-next.1 + ## 0.0.9-next.2 ### Patch Changes diff --git a/packages/scaffolder-internal/catalog-info.yaml b/packages/scaffolder-internal/catalog-info.yaml index c2c2c013d3..a586883cca 100644 --- a/packages/scaffolder-internal/catalog-info.yaml +++ b/packages/scaffolder-internal/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/scaffolder-internal/package.json b/packages/scaffolder-internal/package.json index 6dda7d0b41..30c8cd4b2a 100644 --- a/packages/scaffolder-internal/package.json +++ b/packages/scaffolder-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/scaffolder", - "version": "0.0.9-next.2", + "version": "0.0.12-next.1", "backstage": { "role": "web-library", "inline": true diff --git a/packages/scaffolder-internal/src/wiring/InternalFormField.ts b/packages/scaffolder-internal/src/wiring/InternalFormField.ts index 5d7bf1ee7d..b711341ea5 100644 --- a/packages/scaffolder-internal/src/wiring/InternalFormField.ts +++ b/packages/scaffolder-internal/src/wiring/InternalFormField.ts @@ -17,12 +17,10 @@ import { OpaqueType } from '@internal/opaque'; import { z } from 'zod'; -import { FormFieldExtensionData } from '@backstage/plugin-scaffolder-react/alpha'; - -/** @alpha */ -export interface FormField { - readonly $$type: '@backstage/scaffolder/FormField'; -} +import { + FormFieldExtensionData, + FormField, +} from '@backstage/plugin-scaffolder-react/alpha'; /** @alpha */ export const OpaqueFormField = OpaqueType.create<{ diff --git a/packages/scaffolder-internal/src/wiring/index.ts b/packages/scaffolder-internal/src/wiring/index.ts index 37c8bd09cb..5195a1fa64 100644 --- a/packages/scaffolder-internal/src/wiring/index.ts +++ b/packages/scaffolder-internal/src/wiring/index.ts @@ -13,5 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { OpaqueFormField, type FormField } from './InternalFormField'; +export { OpaqueFormField } from './InternalFormField'; export { OpaqueFormDecorator } from './InternalFormDecorator'; diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 8afd99d6fb..5990035c6b 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,249 @@ # techdocs-cli-embedded-app +## 0.2.111-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.34.0-next.1 + - @backstage/plugin-techdocs@1.14.0-next.2 + - @backstage/plugin-catalog@1.31.2-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/app-defaults@1.6.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/test-utils@1.7.11-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + +## 0.2.111-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.2-next.1 + - @backstage/plugin-techdocs@1.14.0-next.1 + - @backstage/theme@0.6.8-next.0 + - @backstage/cli@0.33.2-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + - @backstage/app-defaults@1.6.5-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/test-utils@1.7.11-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + +## 0.2.111-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.2-next.0 + - @backstage/plugin-techdocs@1.13.3-next.0 + - @backstage/cli@0.33.1 + - @backstage/integration-react@1.2.9 + +## 0.2.110 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.2 + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/cli@0.33.1 + - @backstage/plugin-catalog@1.31.1 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/core-app-api@1.18.0 + - @backstage/integration-react@1.2.9 + - @backstage/app-defaults@1.6.4 + - @backstage/test-utils@1.7.10 + - @backstage/plugin-techdocs-react@1.3.1 + +## 0.2.110-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.33.1-next.2 + - @backstage/theme@0.6.7-next.1 + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/app-defaults@1.6.4-next.2 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/test-utils@1.7.10-next.2 + - @backstage/plugin-catalog@1.31.1-next.2 + - @backstage/plugin-techdocs@1.13.2-next.1 + +## 0.2.110-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/cli@0.33.1-next.1 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/test-utils@1.7.10-next.1 + - @backstage/plugin-techdocs@1.13.2-next.1 + - @backstage/plugin-techdocs-react@1.3.1-next.1 + - @backstage/plugin-catalog@1.31.1-next.1 + - @backstage/app-defaults@1.6.4-next.1 + +## 0.2.110-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.2-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/theme@0.6.7-next.0 + - @backstage/cli@0.33.1-next.0 + - @backstage/plugin-catalog@1.31.1-next.0 + - @backstage/app-defaults@1.6.4-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/test-utils@1.7.10-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/core-plugin-api@1.10.8 + +## 0.2.109 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/cli@0.33.0 + - @backstage/plugin-techdocs@1.13.0 + - @backstage/plugin-catalog@1.31.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-techdocs-react@1.3.0 + - @backstage/app-defaults@1.6.3 + - @backstage/integration-react@1.2.8 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/test-utils@1.7.9 + - @backstage/theme@0.6.6 + +## 0.2.109-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.33.0-next.2 + - @backstage/app-defaults@1.6.3-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.3-next.0 + - @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-catalog@1.31.0-next.2 + - @backstage/plugin-techdocs@1.13.0-next.2 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + +## 0.2.109-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/cli@0.33.0-next.1 + - @backstage/plugin-catalog@1.31.0-next.2 + - @backstage/plugin-techdocs@1.13.0-next.2 + - @backstage/app-defaults@1.6.3-next.0 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/test-utils@1.7.8 + - @backstage/theme@0.6.6 + +## 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 + +- Updated dependencies + - @backstage/test-utils@1.7.8 + - @backstage/theme@0.6.6 + - @backstage/core-app-api@1.17.0 + - @backstage/cli@0.32.1 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-techdocs@1.12.6 + - @backstage/plugin-catalog@1.30.0 + - @backstage/plugin-techdocs-react@1.2.17 + - @backstage/core-plugin-api@1.10.7 + - @backstage/app-defaults@1.6.2 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7 + +## 0.2.108-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/cli@0.32.1-next.3 + - @backstage/plugin-catalog@1.30.0-next.3 + - @backstage/app-defaults@1.6.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/test-utils@1.7.8-next.2 + - @backstage/plugin-techdocs@1.12.6-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + ## 0.2.108-next.2 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index cd95f9e37e..f487321927 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-next.2", + "version": "0.2.111-next.2", "backstage": { "role": "frontend" }, diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index e9a719087e..16d3db351b 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,110 @@ # @techdocs/cli +## 1.9.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-techdocs-node@1.13.6-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.3 + +## 1.9.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-defaults@0.11.1 + - @backstage/plugin-techdocs-node@1.13.5 + +## 1.9.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/plugin-techdocs-node@1.13.5-next.1 + +## 1.9.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-techdocs-node@1.13.5-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + +## 1.9.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.4 + +## 1.9.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.4-next.1 + +## 1.9.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.4-next.1 + +## 1.9.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-techdocs-node@1.13.4-next.0 + +## 1.9.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-techdocs-node@1.13.3 + - @backstage/config@1.3.2 + - @backstage/cli-common@0.1.15 + +## 1.9.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.3-next.3 + ## 1.9.3-next.2 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index e44b6fb107..9a260cb423 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,6 +1,6 @@ { "name": "@techdocs/cli", - "version": "1.9.3-next.2", + "version": "1.9.6-next.0", "description": "Utility CLI for managing TechDocs sites in Backstage.", "backstage": { "role": "cli" diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 2561b80f57..6bbbe5ea8f 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,103 @@ # @backstage/test-utils +## 1.7.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.8-next.0 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-react@0.4.36 + +## 1.7.10 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/core-app-api@1.18.0 + - @backstage/plugin-permission-react@0.4.36 + +## 1.7.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.1 + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## 1.7.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-permission-react@0.4.36-next.0 + +## 1.7.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-react@0.4.35 + +## 1.7.9 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.8 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-react@0.4.35 + +## 1.7.8 + +### Patch Changes + +- b573341: Added support for interpolating JSX elements with the `MockTranslationApi`. +- Updated dependencies + - @backstage/theme@0.6.6 + - @backstage/core-app-api@1.17.0 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/types@1.2.1 + +## 1.7.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + ## 1.7.8-next.1 ### Patch Changes diff --git a/packages/test-utils/catalog-info.yaml b/packages/test-utils/catalog-info.yaml index 4b894f08b5..534241122d 100644 --- a/packages/test-utils/catalog-info.yaml +++ b/packages/test-utils/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 0839e44b69..5373de3007 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/test-utils", - "version": "1.7.8-next.1", + "version": "1.7.11-next.0", "description": "Utilities to test Backstage plugins and apps.", "backstage": { "role": "web-library" diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md index e7e3b98236..f8181e73fa 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/theme +## 0.6.8-next.0 + +### Patch Changes + +- b731527: We are introducing two new data attributes on the `body` to support Backstage UI (BUI) new theming system. + +## 0.6.7 + +### Patch Changes + +- 373486e: Add a different background colour to focused `MenuItem`s to fix a bug in MUI 4 (which is fixed in MUI 5) +- b68d269: Update uncomfortably bright color in recently visited and top visited home page cards + +## 0.6.7-next.1 + +### Patch Changes + +- 373486e: Add a different background colour to focused `MenuItem`s to fix a bug in MUI 4 (which is fixed in MUI 5) + +## 0.6.7-next.0 + +### Patch Changes + +- b68d269: Update uncomfortably bright color in recently visited and top visited home page cards + +## 0.6.6 + +### Patch Changes + +- 1b14572: Show arrow when MuiTableSortLabel receives focus + ## 0.6.6-next.0 ### Patch Changes diff --git a/packages/theme/catalog-info.yaml b/packages/theme/catalog-info.yaml index 1fee85f58d..f2faf9ed42 100644 --- a/packages/theme/catalog-info.yaml +++ b/packages/theme/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/theme/package.json b/packages/theme/package.json index acf9b891d4..578da9ae2b 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/theme", - "version": "0.6.6-next.0", + "version": "0.6.8-next.0", "description": "material-ui theme for use with Backstage.", "backstage": { "role": "web-library" diff --git a/packages/theme/src/base/pageTheme.ts b/packages/theme/src/base/pageTheme.ts index 12a73b6089..3e1e8641fc 100644 --- a/packages/theme/src/base/pageTheme.ts +++ b/packages/theme/src/base/pageTheme.ts @@ -50,7 +50,7 @@ export const colorVariants: Record = { rubyRed: ['#98002B', '#8D1134'], toastyOrange: ['#BE2200', '#A41D00'], purpleSky: ['#8912CA', '#3E00EA'], - eveningSea: ['#00FFF2', '#035355'], + eveningSea: ['#00CFC5', '#035355'], teal: ['#005B4B'], pinkSea: ['#C8077A', '#C2297D'], greens: ['#4BB8A5', '#187656'], diff --git a/packages/theme/src/unified/UnifiedThemeProvider.tsx b/packages/theme/src/unified/UnifiedThemeProvider.tsx index 32b0fd2708..a80e36cf35 100644 --- a/packages/theme/src/unified/UnifiedThemeProvider.tsx +++ b/packages/theme/src/unified/UnifiedThemeProvider.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ReactNode } from 'react'; +import { ReactNode, useEffect } from 'react'; import CssBaseline from '@material-ui/core/CssBaseline'; import { ThemeProvider, @@ -70,6 +70,18 @@ export function UnifiedThemeProvider( const v4Theme = theme.getTheme('v4') as Mui4Theme; const v5Theme = theme.getTheme('v5') as Mui5Theme; + const themeMode = v4Theme ? v4Theme.palette.type : v5Theme?.palette.mode; + const themeName = 'backstage'; + + useEffect(() => { + document.body.setAttribute('data-theme-mode', themeMode); + document.body.setAttribute('data-theme-name', themeName); + + return () => { + document.body.removeAttribute('data-theme-mode'); + document.body.removeAttribute('data-theme-name'); + }; + }, [themeMode, themeName]); let cssBaseline: JSX.Element | undefined = undefined; if (!noCssBaseline) { diff --git a/packages/theme/src/v5/defaultComponentThemes.ts b/packages/theme/src/v5/defaultComponentThemes.ts index 1d07c318a0..d627ecdcb7 100644 --- a/packages/theme/src/v5/defaultComponentThemes.ts +++ b/packages/theme/src/v5/defaultComponentThemes.ts @@ -269,4 +269,14 @@ export const defaultComponentThemes: ThemeOptions['components'] = { MuiPaper: { styleOverrides: { root: { backgroundImage: 'unset' } }, }, + MuiMenuItem: { + styleOverrides: { + root: ({ theme }) => ({ + // Fixes a bug with MUI 4 that's fixed in MUI 5 + '&:focus': { + backgroundColor: theme.palette.action.focus, + }, + }), + }, + }, }; diff --git a/packages/types/catalog-info.yaml b/packages/types/catalog-info.yaml index 1bd30379cc..cd20c3758e 100644 --- a/packages/types/catalog-info.yaml +++ b/packages/types/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-common-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/canon/.eslintrc.js b/packages/ui/.eslintrc.js similarity index 79% rename from packages/canon/.eslintrc.js rename to packages/ui/.eslintrc.js index f7c0e8cbd1..9638ff6e45 100644 --- a/packages/canon/.eslintrc.js +++ b/packages/ui/.eslintrc.js @@ -3,6 +3,7 @@ module.exports = { extends: ['plugin:storybook/recommended'], rules: { 'react/forbid-elements': 'off', + '@backstage/no-mixed-plugin-imports': 'off' }, }; diff --git a/packages/canon/.gitignore b/packages/ui/.gitignore similarity index 100% rename from packages/canon/.gitignore rename to packages/ui/.gitignore diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md new file mode 100644 index 0000000000..7a2d95f6ae --- /dev/null +++ b/packages/ui/CHANGELOG.md @@ -0,0 +1,325 @@ +# @backstage/ui + +## 0.7.0-next.3 + +### Minor Changes + +- 0615e54: We are moving our DataTable component to React Aria. We removed our DataTable to only use Table as a single and opinionated option for tables. This new structure is made possible by using React Aria under the hood. + +### Patch Changes + +- 230b410: **Breaking change** Move breadcrumb to fit in the `HeaderPage` instead of the `Header` in Backstage UI. +- 8bdc491: Remove stylesheet import from Select component. +- 404b426: Add `startCollapsed` prop on the `SearchField` component in BUI. + +## 0.7.0-next.2 + +### Patch Changes + +- d4e603e: Updated Menu component in Backstage UI to use useId() from React Aria instead of React to support React 17. + +## 0.7.0-next.1 + +### Patch Changes + +- de89a3d: Fixes some styles on the Select component in BUI. +- 75fead9: Fixes a couple of small bugs in BUI including setting H1 and H2 correctly on the Header and HeaderPage. +- 2f9a084: We are motion away from `motion` to use `gsap` instead to make Backstage UI backward compatible with React 17. + +## 0.7.0-next.0 + +### Minor Changes + +- b0e47f3: **Breaking** We are upgrading our `Text` component to support all font sizes making the `Heading` component redundant. The new `Text` component introduces 4 sizes for title and 4 sizes for body text. All of these work in multiple colors and font weights. We improved the `as` prop to include all possible values. The `Link` component has also been updated to match the new `Text` component. + +### Patch Changes + +- e7ff178: Update styling of Tooltip element +- e0e886f: Adds onTabSelectionChange to ui header component. + +## 0.6.0 + +### Minor Changes + +- e92bb9b: Canon has been renamed to Backstage UI. This means that `@backstage/canon` has been deprecated and replaced by `@backstage/ui`. + +## 0.6.0-next.1 + +### Minor Changes + +- 2e30459: We are moving our Tooltip component to use React Aria under the hood. In doing so, the structure of the component and its prop are changing to follow the new underlying structure. + +### Patch Changes + +- 76255b8: Add new Card component to Canon. +- b0a6c8e: Add new Header component to Canon. +- be76576: Improve Button, ButtonIcon and ButtonLink styling in Canon. +- 17beb9b: Update return types for Heading & Text components for React 19. +- eac4a4c: Add new tertiary variant to Button, ButtonIcon and ButtonLink in Canon. +- 8f2e82d: Add new Skeleton component in Canon + +## 0.6.0-next.0 + +### Minor Changes + +- 4c6d891: **BREAKING CHANGES** + + We’re updating our Button component to provide better support for button links. + + - We’re introducing a new `ButtonLink` component, which replaces the previous render prop pattern. + - To maintain naming consistency across components, `IconButton` is being renamed to `ButtonIcon`. + - Additionally, the render prop will be removed from all button-related components. + + These changes aim to simplify usage and improve clarity in our component API. + +### Patch Changes + +- 140f652: We are consolidating all css files into a single styles.css in Canon. +- 8154fb9: Add new SearchField component in Canon +- 6910892: Add new `RadioGroup` + `Radio` component to Canon +- a8a8514: We are transforming how we structure our class names and data attributes definitions for all components. They are now all set in the same place. +- 667b951: Added placeholder prop to TextField component. +- e71333a: adding export for ButtonLink so it's importable + +## 0.5.0 + +### 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. +- 35fd51d: Move TextField component to use react Aria under the hood. Introducing a new FieldLabel component to help build custom fields. +- 78204a2: **Breaking** We are adding a new as prop on the Heading and Text component to make it easier to change the component tag. We are removing the render prop in favour of the as prop. +- c49e335: TextField in Canon now has multiple label sizes as well as the capacity to hide label and description but still make them available for screen readers. +- 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 + +- 44df879: Add min-width: 0; by default on every Flex components in Canon to help support truncated texts inside flex elements. +- ee6ffe6: Fix styling for the title4 prop on the Heading component in Canon. +- f2f814a: Added a render prop to the Button component in Canon to use it as a link. +- 98f02a6: Add new Switch component in Canon. +- c94f8e0: The filter input in menu comboboxes should now always use the full width of the menu it's in. +- 269316d: Remove leftover console.log from Container component. + +## 0.5.0-next.2 + +### Patch Changes + +- 44df879: Add min-width: 0; by default on every Flex components in Canon to help support truncated texts inside flex elements. +- ee6ffe6: Fix styling for the title4 prop on the Heading component in Canon. +- f2f814a: Added a render prop to the Button component in Canon to use it as a link. + +## 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 + +- ea36f74: **Breaking Change** Icons on Button and IconButton now need to be imported and placed like this: + ), }; @@ -72,12 +76,12 @@ export const Sizes: Story = { }, render: () => ( - + ), }; @@ -117,14 +121,17 @@ export const FullWidth: Story = { }; export const Disabled: Story = { - args: { - children: 'Button', - disabled: true, - }, - render: args => ( + render: () => ( - + + ), }; @@ -143,7 +150,8 @@ export const Responsive: Story = { }, }; -const variants: string[] = ['primary', 'secondary']; +const variants = ['primary', 'secondary'] as const; +const sizes = ['small', 'medium'] as const; export const Playground: Story = { args: { @@ -154,25 +162,22 @@ export const Playground: Story = { {variants.map(variant => ( {variant} - {['small', 'medium'].map(size => ( + {sizes.map(size => ( - @@ -180,31 +185,27 @@ export const Playground: Story = { iconStart={} iconEnd={} style={{ width: '200px' }} - variant={variant as ButtonProps['variant']} - size={size as ButtonProps['size']} + variant={variant} + size={size} > Button - diff --git a/packages/ui/src/components/Button/Button.tsx b/packages/ui/src/components/Button/Button.tsx new file mode 100644 index 0000000000..40879e51ed --- /dev/null +++ b/packages/ui/src/components/Button/Button.tsx @@ -0,0 +1,56 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import clsx from 'clsx'; +import { forwardRef, Ref } from 'react'; +import { Button as RAButton } from 'react-aria-components'; +import type { ButtonProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; + +/** @public */ +export const Button = forwardRef( + (props: ButtonProps, ref: Ref) => { + const { + size = 'small', + variant = 'primary', + iconStart, + iconEnd, + children, + className, + ...rest + } = props; + + const { classNames, dataAttributes } = useStyles('Button', { + size, + variant, + }); + + return ( + + {iconStart} + {children} + {iconEnd} + + ); + }, +); + +Button.displayName = 'Button'; diff --git a/canon-docs/src/components/IconLibrary/index.ts b/packages/ui/src/components/Button/index.ts similarity index 92% rename from canon-docs/src/components/IconLibrary/index.ts rename to packages/ui/src/components/Button/index.ts index 071c80db85..b9c497ae1f 100644 --- a/canon-docs/src/components/IconLibrary/index.ts +++ b/packages/ui/src/components/Button/index.ts @@ -13,4 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { IconLibrary } from './IconLibrary'; + +export * from './Button'; +export * from './types'; diff --git a/packages/ui/src/components/Button/styles.css b/packages/ui/src/components/Button/styles.css new file mode 100644 index 0000000000..03138a7c44 --- /dev/null +++ b/packages/ui/src/components/Button/styles.css @@ -0,0 +1,131 @@ +/* + * 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. + */ + +.bui-Button { + border: none; + display: inline-flex; + align-items: center; + justify-content: center; + user-select: none; + font-family: var(--bui-font-regular); + font-weight: var(--bui-font-weight-bold); + padding: 0; + cursor: pointer; + border-radius: var(--bui-radius-2); + gap: var(--bui-space-1_5); + flex-shrink: 0; + + &[data-disabled='true'] { + cursor: not-allowed; + } +} + +.bui-Button[data-variant='primary'] { + background-color: var(--bui-bg-solid); + color: var(--bui-fg-solid); + + &:hover { + background-color: var(--bui-bg-solid-hover); + transition: background-color 150ms ease; + } + + &:active { + background-color: var(--bui-bg-solid-pressed); + } + + &:focus-visible { + outline: 2px solid var(--bui-ring); + outline-offset: 2px; + } + + &[data-disabled='true'] { + background-color: var(--bui-bg-solid-disabled); + color: var(--bui-fg-solid-disabled); + } +} + +.bui-Button[data-variant='secondary'] { + background-color: var(--bui-bg-surface-1); + box-shadow: inset 0 0 0 1px var(--bui-border); + color: var(--bui-fg-primary); + + &:hover { + box-shadow: inset 0 0 0 1px var(--bui-border-hover); + transition: box-shadow 150ms ease; + } + + &:active { + box-shadow: inset 0 0 0 1px var(--bui-border-pressed); + } + + &:focus-visible { + outline: none; + transition: none; + box-shadow: inset 0 0 0 2px var(--bui-ring); + } + + &[data-disabled='true'] { + box-shadow: inset 0 0 0 1px var(--bui-border-disabled); + color: var(--bui-fg-disabled); + } +} + +.bui-Button[data-variant='tertiary'] { + background-color: transparent; + color: var(--bui-fg-primary); + + &:hover { + background-color: var(--bui-bg-surface-1); + transition: background-color 200ms ease; + } + + &:active { + background-color: var(--bui-bg-surface-2); + } + + &:focus-visible { + outline: none; + transition: none; + box-shadow: inset 0 0 0 2px var(--bui-ring); + } + + &[data-disabled='true'] { + background-color: transparent; + color: var(--bui-fg-disabled); + } +} + +.bui-Button[data-size='medium'] { + font-size: var(--bui-font-size-4); + padding: 0 var(--bui-space-3); + height: 2.5rem; +} + +.bui-Button[data-size='small'] { + font-size: var(--bui-font-size-3); + padding: 0 var(--bui-space-2); + height: 2rem; +} + +.bui-Button[data-size='small'] svg { + width: 1rem; + height: 1rem; +} + +.bui-Button[data-size='medium'] svg { + width: 1.25rem; + height: 1.25rem; +} diff --git a/packages/canon/src/components/Button/types.ts b/packages/ui/src/components/Button/types.ts similarity index 53% rename from packages/canon/src/components/Button/types.ts rename to packages/ui/src/components/Button/types.ts index 4ab62d5683..cbe5bd3608 100644 --- a/packages/canon/src/components/Button/types.ts +++ b/packages/ui/src/components/Button/types.ts @@ -14,40 +14,23 @@ * limitations under the License. */ -import type { ButtonOwnProps } from './Button.props'; -import { ReactElement } from 'react'; +import { Breakpoint } from '../..'; +import { ReactElement, ReactNode } from 'react'; +import { ButtonProps as RAButtonProps } from 'react-aria-components'; /** * Properties for {@link Button} * * @public */ -export interface ButtonProps - extends Omit, 'children'> { - /** - * The size of the button - * @defaultValue 'medium' - */ - size?: ButtonOwnProps['size']; - - /** - * The visual variant of the button - * @defaultValue 'primary' - */ - variant?: ButtonOwnProps['variant']; - - /** - * The content of the button - */ - children: React.ReactNode; - - /** - * Optional icon to display at the start of the button - */ +export interface ButtonProps extends RAButtonProps { + size?: 'small' | 'medium' | Partial>; + variant?: + | 'primary' + | 'secondary' + | 'tertiary' + | Partial>; iconStart?: ReactElement; - - /** - * Optional icon to display at the end of the button - */ iconEnd?: ReactElement; + children?: ReactNode; } diff --git a/packages/canon/src/components/IconButton/IconButton.stories.tsx b/packages/ui/src/components/ButtonIcon/ButtonIcon.stories.tsx similarity index 53% rename from packages/canon/src/components/IconButton/IconButton.stories.tsx rename to packages/ui/src/components/ButtonIcon/ButtonIcon.stories.tsx index d16dcd1fcb..10e57fbb45 100644 --- a/packages/canon/src/components/IconButton/IconButton.stories.tsx +++ b/packages/ui/src/components/ButtonIcon/ButtonIcon.stories.tsx @@ -15,15 +15,14 @@ */ import type { Meta, StoryObj } from '@storybook/react'; -import { IconButton } from './IconButton'; +import { ButtonIcon } from './ButtonIcon'; import { Flex } from '../Flex'; import { Text } from '../Text'; -import { IconButtonProps } from './types'; import { Icon } from '../Icon'; const meta = { - title: 'Components/IconButton', - component: IconButton, + title: 'Backstage UI/ButtonIcon', + component: ButtonIcon, argTypes: { size: { control: 'select', @@ -34,66 +33,46 @@ const meta = { options: ['primary', 'secondary'], }, }, - args: { - size: 'medium', - variant: 'primary', - }, -} satisfies Meta; +} satisfies Meta; export default meta; type Story = StoryObj; +export const Default: Story = { + render: () => } />, +}; + export const Variants: Story = { - args: { - icon: , - 'aria-label': 'Cloud icon button', - }, - parameters: { - argTypes: { - variant: { - control: false, - }, - }, - }, - render: args => ( - - - + render: () => ( + + } variant="primary" /> + } variant="secondary" /> + } variant="tertiary" /> ), }; export const Sizes: Story = { - args: { - icon: , - 'aria-label': 'Cloud icon button', - }, - render: args => ( - - - + render: () => ( + + } size="small" /> + } size="medium" /> ), }; export const Disabled: Story = { - args: { - icon: , - disabled: true, - 'aria-label': 'Cloud icon button', - }, - render: args => ( - - - + render: () => ( + + } variant="primary" /> + } variant="secondary" /> + } variant="tertiary" /> ), }; export const Responsive: Story = { args: { - icon: , - 'aria-label': 'Cloud icon button', variant: { initial: 'primary', sm: 'secondary', @@ -103,40 +82,39 @@ export const Responsive: Story = { sm: 'medium', }, }, + render: args => } />, }; -const variants: string[] = ['primary', 'secondary']; +const variants = ['primary', 'secondary'] as const; +const sizes = ['small', 'medium'] as const; export const Playground: Story = { - args: { - icon: , - 'aria-label': 'Cloud icon button', - }, render: args => ( {variants.map(variant => ( {variant} - {['small', 'medium'].map(size => ( + {sizes.map(size => ( - } /> - } aria-label="Chevron right icon button" - variant={variant as IconButtonProps['variant']} - size={size as IconButtonProps['size']} + variant={variant} + size={size} /> - } aria-label="Chevron right icon button" - variant={variant as IconButtonProps['variant']} - size={size as IconButtonProps['size']} + variant={variant} + size={size} /> ))} diff --git a/packages/canon/src/components/IconButton/IconButton.tsx b/packages/ui/src/components/ButtonIcon/ButtonIcon.tsx similarity index 51% rename from packages/canon/src/components/IconButton/IconButton.tsx rename to packages/ui/src/components/ButtonIcon/ButtonIcon.tsx index bca012cac0..072ed20963 100644 --- a/packages/canon/src/components/IconButton/IconButton.tsx +++ b/packages/ui/src/components/ButtonIcon/ButtonIcon.tsx @@ -14,17 +14,17 @@ * limitations under the License. */ -import { forwardRef } from 'react'; import clsx from 'clsx'; -import { useResponsiveValue } from '../../hooks/useResponsiveValue'; - -import type { IconButtonProps } from './types'; +import { forwardRef, Ref } from 'react'; +import { Button as RAButton } from 'react-aria-components'; +import type { ButtonIconProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; /** @public */ -export const IconButton = forwardRef( - (props: IconButtonProps, ref) => { +export const ButtonIcon = forwardRef( + (props: ButtonIconProps, ref: Ref) => { const { - size = 'medium', + size = 'small', variant = 'primary', icon, className, @@ -32,28 +32,24 @@ export const IconButton = forwardRef( ...rest } = props; - const responsiveSize = useResponsiveValue(size); - const responsiveVariant = useResponsiveValue(variant); + const { classNames, dataAttributes } = useStyles('Button', { + size, + variant, + }); + + const { classNames: classNamesButtonIcon } = useStyles('ButtonIcon'); return ( - + {icon} + ); }, ); -export default IconButton; +ButtonIcon.displayName = 'ButtonIcon'; diff --git a/packages/ui/src/components/ButtonIcon/index.tsx b/packages/ui/src/components/ButtonIcon/index.tsx new file mode 100644 index 0000000000..686fd844e8 --- /dev/null +++ b/packages/ui/src/components/ButtonIcon/index.tsx @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './ButtonIcon'; +export * from './types'; diff --git a/packages/ui/src/components/ButtonIcon/styles.css b/packages/ui/src/components/ButtonIcon/styles.css new file mode 100644 index 0000000000..cbd19071a9 --- /dev/null +++ b/packages/ui/src/components/ButtonIcon/styles.css @@ -0,0 +1,30 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.bui-ButtonIcon { + align-items: center; + justify-content: center; +} + +.bui-ButtonIcon[data-size='small'] { + padding: 0; + width: 2rem; +} + +.bui-ButtonIcon[data-size='medium'] { + padding: 0; + width: 2.5rem; +} diff --git a/packages/canon/src/components/IconButton/types.ts b/packages/ui/src/components/ButtonIcon/types.ts similarity index 56% rename from packages/canon/src/components/IconButton/types.ts rename to packages/ui/src/components/ButtonIcon/types.ts index ece1950d1b..bc358b6d7d 100644 --- a/packages/canon/src/components/IconButton/types.ts +++ b/packages/ui/src/components/ButtonIcon/types.ts @@ -14,30 +14,21 @@ * limitations under the License. */ -import type { IconButtonOwnProps } from './IconButton.props'; +import { Breakpoint } from '../..'; import { ReactElement } from 'react'; +import { ButtonProps as RAButtonProps } from 'react-aria-components'; /** - * Properties for {@link IconButton} + * Properties for {@link ButtonIcon} * * @public */ -export interface IconButtonProps - extends Omit, 'children'> { - /** - * The size of the button - * @defaultValue 'medium' - */ - size?: IconButtonOwnProps['size']; - - /** - * The visual variant of the button - * @defaultValue 'primary' - */ - variant?: IconButtonOwnProps['variant']; - - /** - * Icon to display in the button - */ - icon: ReactElement; +export interface ButtonIconProps extends RAButtonProps { + size?: 'small' | 'medium' | Partial>; + variant?: + | 'primary' + | 'secondary' + | 'tertiary' + | Partial>; + icon?: ReactElement; } diff --git a/packages/ui/src/components/ButtonLink/ButtonLink.stories.tsx b/packages/ui/src/components/ButtonLink/ButtonLink.stories.tsx new file mode 100644 index 0000000000..0ded9ef741 --- /dev/null +++ b/packages/ui/src/components/ButtonLink/ButtonLink.stories.tsx @@ -0,0 +1,223 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { ButtonLink } from './ButtonLink'; +import { Flex } from '../Flex'; +import { Text } from '../Text'; +import { Icon } from '../Icon'; + +const meta = { + title: 'Backstage UI/ButtonLink', + component: ButtonLink, + argTypes: { + size: { + control: 'select', + options: ['small', 'medium'], + }, + variant: { + control: 'select', + options: ['primary', 'secondary'], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + children: 'Button', + }, +}; + +export const Variants: Story = { + render: () => ( + + } + variant="primary" + href="https://ui.backstage.io" + target="_blank" + > + Button + + } + variant="secondary" + href="https://ui.backstage.io" + target="_blank" + > + Button + + } + variant="tertiary" + href="https://ui.backstage.io" + target="_blank" + > + Button + + + ), +}; + +export const Sizes: Story = { + args: { + children: 'Button', + }, + render: () => ( + + }> + Small + + }> + Medium + + + ), +}; + +export const WithIcons: Story = { + args: { + children: 'Button', + }, + render: args => ( + + } /> + } /> + } + iconEnd={} + /> + + ), +}; + +export const FullWidth: Story = { + args: { + children: 'Button', + }, + render: args => ( + + } /> + } /> + } + iconEnd={} + /> + + ), +}; + +export const Disabled: Story = { + render: () => ( + + + Primary + + + Secondary + + + Tertiary + + + ), +}; + +export const Responsive: Story = { + args: { + children: 'Button', + variant: { + initial: 'primary', + sm: 'secondary', + }, + size: { + xs: 'small', + sm: 'medium', + }, + }, +}; + +const variants = ['primary', 'secondary'] as const; +const sizes = ['small', 'medium'] as const; + +export const Playground: Story = { + args: { + children: 'Button', + }, + render: () => ( + + {variants.map(variant => ( + + {variant} + {sizes.map(size => ( + + + Button + + } + variant={variant} + size={size} + > + Button + + } + variant={variant} + size={size} + > + Button + + } + iconEnd={} + style={{ width: '200px' }} + variant={variant} + size={size} + > + Button + + + Button + + } + variant={variant} + size={size} + isDisabled + > + Button + + } + variant={variant} + size={size} + isDisabled + > + Button + + + ))} + + ))} + + ), +}; diff --git a/packages/ui/src/components/ButtonLink/ButtonLink.tsx b/packages/ui/src/components/ButtonLink/ButtonLink.tsx new file mode 100644 index 0000000000..153643a530 --- /dev/null +++ b/packages/ui/src/components/ButtonLink/ButtonLink.tsx @@ -0,0 +1,58 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import clsx from 'clsx'; +import { forwardRef, Ref } from 'react'; +import { Link as RALink } from 'react-aria-components'; +import type { ButtonLinkProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; + +/** @public */ +export const ButtonLink = forwardRef( + (props: ButtonLinkProps, ref: Ref) => { + const { + size = 'small', + variant = 'primary', + iconStart, + iconEnd, + children, + className, + ...rest + } = props; + + const { classNames, dataAttributes } = useStyles('Button', { + size, + variant, + }); + + const { classNames: classNamesButtonLink } = useStyles('ButtonLink'); + + return ( + + {iconStart} + {children} + {iconEnd} + + ); + }, +); + +ButtonLink.displayName = 'ButtonLink'; diff --git a/packages/ui/src/components/ButtonLink/index.ts b/packages/ui/src/components/ButtonLink/index.ts new file mode 100644 index 0000000000..bbfc696c67 --- /dev/null +++ b/packages/ui/src/components/ButtonLink/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './ButtonLink'; +export * from './types'; diff --git a/packages/canon/src/components/Link/types.ts b/packages/ui/src/components/ButtonLink/types.ts similarity index 52% rename from packages/canon/src/components/Link/types.ts rename to packages/ui/src/components/ButtonLink/types.ts index 20fa4a2f31..e6b0556349 100644 --- a/packages/canon/src/components/Link/types.ts +++ b/packages/ui/src/components/ButtonLink/types.ts @@ -14,21 +14,23 @@ * limitations under the License. */ -import type { CSSProperties, ReactNode } from 'react'; -import type { Breakpoint } from '../../types'; -import type { useRender } from '@base-ui-components/react/use-render'; +import { Breakpoint } from '../..'; +import { ReactElement, ReactNode } from 'react'; +import { LinkProps as RALinkProps } from 'react-aria-components'; -/** @public */ -export interface LinkProps extends useRender.ComponentProps<'a'> { - children: ReactNode; - to?: string; +/** + * Properties for {@link ButtonLink} + * + * @public + */ +export interface ButtonLinkProps extends RALinkProps { + size?: 'small' | 'medium' | Partial>; variant?: - | 'subtitle' - | 'body' - | 'caption' - | 'label' - | Partial>; - weight?: 'regular' | 'bold' | Partial>; - className?: string; - style?: CSSProperties; + | 'primary' + | 'secondary' + | 'tertiary' + | Partial>; + iconStart?: ReactElement; + iconEnd?: ReactElement; + children?: ReactNode; } diff --git a/packages/ui/src/components/Card/Card.stories.tsx b/packages/ui/src/components/Card/Card.stories.tsx new file mode 100644 index 0000000000..2b8466f145 --- /dev/null +++ b/packages/ui/src/components/Card/Card.stories.tsx @@ -0,0 +1,137 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { Card, CardHeader, CardBody, CardFooter } from './Card'; +import { IconNames, Text } from '../..'; + +export interface ListItem { + id: string; + name: string; + icon?: IconNames; +} + +const meta = { + title: 'Backstage UI/Card', + component: Card, + subcomponents: { CardHeader, CardBody, CardFooter }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: args => ( + + Header + Body + Footer + + ), +}; + +export const CustomSize: Story = { + args: { + style: { + width: '300px', + height: '200px', + }, + }, + render: Default.render, +}; + +export const WithLongBody: Story = { + render: () => ( + + + Header + + + + This is the first paragraph of a long body text that demonstrates how + the Card component handles extensive content. The card should adjust + accordingly to display all the text properly while maintaining its + structure. + + + Here's a second paragraph that adds more content to our card body. + Having multiple paragraphs helps to visualize how spacing works within + the card component. + + + This third paragraph continues to add more text to ensure we have a + proper demonstration of a card with significant content. This makes it + easier to test scrolling behavior and overall layout when content + exceeds the initial view. + + + + Footer + + + ), +}; + +const ListRow = ({ children }: { children: React.ReactNode }) => { + return ( +
    + {children} +
    + ); +}; + +export const WithListRow: Story = { + render: () => ( + + + Header + + + Hello world + Hello world + Hello world + Hello world + Hello world + Hello world + Hello world + Hello world + Hello world + Hello world + Hello world + Hello world + Hello world + Hello world + Hello world + Hello world + + + Footer + + + ), +}; diff --git a/packages/canon/src/components/Table/TableCellLink/TableCellLink.styles.css b/packages/ui/src/components/Card/Card.styles.css similarity index 58% rename from packages/canon/src/components/Table/TableCellLink/TableCellLink.styles.css rename to packages/ui/src/components/Card/Card.styles.css index 3064bbb791..1643ba0b8a 100644 --- a/packages/canon/src/components/Table/TableCellLink/TableCellLink.styles.css +++ b/packages/ui/src/components/Card/Card.styles.css @@ -14,8 +14,30 @@ * limitations under the License. */ -.canon-TableCellLink { +.bui-Card { display: flex; flex-direction: column; - gap: var(--canon-space-0_5); + gap: var(--bui-space-3); + background-color: var(--bui-bg-surface-1); + border-radius: var(--bui-radius-3); + padding-block: var(--bui-space-3); + color: var(--bui-fg-primary); + border: 1px solid var(--bui-border); + overflow: hidden; + min-height: 0; + width: 100%; +} + +.bui-CardBody { + flex: 1; + min-height: 0; + overflow: auto; +} + +.bui-CardHeader { + padding-inline: var(--bui-space-3); +} + +.bui-CardFooter { + padding-inline: var(--bui-space-3); } diff --git a/packages/ui/src/components/Card/Card.tsx b/packages/ui/src/components/Card/Card.tsx new file mode 100644 index 0000000000..7af09d3ec2 --- /dev/null +++ b/packages/ui/src/components/Card/Card.tsx @@ -0,0 +1,102 @@ +/* + * 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 { forwardRef } from 'react'; +import { ScrollArea } from '../ScrollArea'; +import clsx from 'clsx'; +import { useStyles } from '../../hooks/useStyles'; +import type { + CardProps, + CardHeaderProps, + CardBodyProps, + CardFooterProps, +} from './types'; + +/** + * Card component. + * + * @public + */ +export const Card = forwardRef((props, ref) => { + const { className, ...rest } = props; + const { classNames } = useStyles('Card'); + + return ( +
    + ); +}); + +/** + * CardHeader component. + * + * @public + */ +export const CardHeader = forwardRef( + (props, ref) => { + const { className, ...rest } = props; + const { classNames } = useStyles('Card'); + + return ( +
    + ); + }, +); + +/** + * CardBody component. + * + * @public + */ +export const CardBody = forwardRef( + (props, ref) => { + const { children, className, ...rest } = props; + const { classNames } = useStyles('Card'); + + return ( + + + {children} + + + + + + ); + }, +); + +/** + * CardFooter component. + * + * @public + */ +export const CardFooter = forwardRef( + (props, ref) => { + const { className, ...rest } = props; + const { classNames } = useStyles('Card'); + + return ( +
    + ); + }, +); diff --git a/packages/canon/src/components/DataTable/index.ts b/packages/ui/src/components/Card/index.ts similarity index 82% rename from packages/canon/src/components/DataTable/index.ts rename to packages/ui/src/components/Card/index.ts index 9d8c9becbb..27bab991a4 100644 --- a/packages/canon/src/components/DataTable/index.ts +++ b/packages/ui/src/components/Card/index.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -export * from './DataTable'; -export * from './Root/types'; -export * from './Pagination/types'; -export * from './Table/types'; +export { Card } from './Card'; +export type { + CardProps, + CardHeaderProps, + CardBodyProps, + CardFooterProps, +} from './types'; diff --git a/packages/ui/src/components/Card/types.ts b/packages/ui/src/components/Card/types.ts new file mode 100644 index 0000000000..8b3419f6a2 --- /dev/null +++ b/packages/ui/src/components/Card/types.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Props for the Card component. + * + * @public + */ +export interface CardProps extends React.HTMLAttributes { + children?: React.ReactNode; +} + +/** + * Props for the CardHeader component. + * + * @public + */ +export interface CardHeaderProps extends React.HTMLAttributes { + children?: React.ReactNode; +} + +/** + * Props for the CardBody component. + * + * @public + */ +export interface CardBodyProps extends React.HTMLAttributes { + children?: React.ReactNode; +} + +/** + * Props for the CardFooter component. + * + * @public + */ +export interface CardFooterProps extends React.HTMLAttributes { + children?: React.ReactNode; +} diff --git a/packages/canon/src/components/Checkbox/Checkbox.stories.tsx b/packages/ui/src/components/Checkbox/Checkbox.stories.tsx similarity index 97% rename from packages/canon/src/components/Checkbox/Checkbox.stories.tsx rename to packages/ui/src/components/Checkbox/Checkbox.stories.tsx index c5da7083d9..a8aa46fc07 100644 --- a/packages/canon/src/components/Checkbox/Checkbox.stories.tsx +++ b/packages/ui/src/components/Checkbox/Checkbox.stories.tsx @@ -20,7 +20,7 @@ import { Flex } from '../Flex'; import { Text } from '../Text'; const meta = { - title: 'Components/Checkbox', + title: 'Backstage UI/Checkbox', component: Checkbox, } satisfies Meta; diff --git a/packages/canon/src/components/Checkbox/Checkbox.tsx b/packages/ui/src/components/Checkbox/Checkbox.tsx similarity index 84% rename from packages/canon/src/components/Checkbox/Checkbox.tsx rename to packages/ui/src/components/Checkbox/Checkbox.tsx index 5bf6520891..51f1fad4de 100644 --- a/packages/canon/src/components/Checkbox/Checkbox.tsx +++ b/packages/ui/src/components/Checkbox/Checkbox.tsx @@ -16,8 +16,9 @@ import { forwardRef } from 'react'; import { Checkbox as CheckboxPrimitive } from '@base-ui-components/react/checkbox'; -import { Icon } from '@backstage/canon'; +import { Icon } from '../..'; import type { CheckboxProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; import clsx from 'clsx'; /** @public */ @@ -35,10 +36,12 @@ export const Checkbox = forwardRef( style, } = props; + const { classNames } = useStyles('Checkbox'); + const checkboxElement = ( ( value={value} style={style} > - + ); return label ? ( -
    ), }; + +export const WithPaddingY: Story = { + args: { + ...Default.args, + py: '4', + }, +}; + +export const WithPaddingTop: Story = { + args: { + ...Default.args, + pt: '4', + }, +}; + +export const WithPaddingBottom: Story = { + args: { + ...Default.args, + pb: '4', + }, +}; + +export const WithMarginY: Story = { + args: { + ...Default.args, + my: '4', + }, +}; + +export const WithMarginTop: Story = { + args: { + ...Default.args, + mt: '4', + }, +}; + +export const WithMarginBottom: Story = { + args: { + ...Default.args, + mb: '4', + }, +}; diff --git a/packages/canon/src/components/Container/Container.tsx b/packages/ui/src/components/Container/Container.tsx similarity index 68% rename from packages/canon/src/components/Container/Container.tsx rename to packages/ui/src/components/Container/Container.tsx index 46a5356d56..7be4e5fdce 100644 --- a/packages/canon/src/components/Container/Container.tsx +++ b/packages/ui/src/components/Container/Container.tsx @@ -19,20 +19,35 @@ import { ContainerProps } from './types'; import clsx from 'clsx'; import { displayPropDefs } from '../../props/display.props'; import { extractProps } from '../../utils/extractProps'; +import { spacingPropDefs } from '../../props/spacing.props'; +import { useStyles } from '../../hooks/useStyles'; /** @public */ export const Container = forwardRef( (props, ref) => { const { children } = props; + const { classNames } = useStyles('Container'); + + // Create a subset of spacing props that match the interface + const containerSpacingProps = { + my: spacingPropDefs.my, + mt: spacingPropDefs.mt, + mb: spacingPropDefs.mb, + py: spacingPropDefs.py, + pt: spacingPropDefs.pt, + pb: spacingPropDefs.pb, + }; + const propDefs = { ...displayPropDefs, + ...containerSpacingProps, }; const { className, style } = extractProps(props, propDefs); return createElement('div', { ref, - className: clsx('canon-Container', className), + className: clsx(classNames.root, className), style, children, }); diff --git a/packages/canon/src/components/Container/index.tsx b/packages/ui/src/components/Container/index.tsx similarity index 100% rename from packages/canon/src/components/Container/index.tsx rename to packages/ui/src/components/Container/index.tsx diff --git a/packages/ui/src/components/Container/styles.css b/packages/ui/src/components/Container/styles.css new file mode 100644 index 0000000000..a812b7c767 --- /dev/null +++ b/packages/ui/src/components/Container/styles.css @@ -0,0 +1,12 @@ +.bui-Container { + max-width: 120rem; + padding-inline: var(--bui-space-4); + margin-inline: auto; + transition: padding 0.2s ease-in-out; +} + +@media (min-width: 640px) { + .bui-Container { + padding-inline: var(--bui-space-5); + } +} diff --git a/packages/canon/src/components/Container/types.ts b/packages/ui/src/components/Container/types.ts similarity index 100% rename from packages/canon/src/components/Container/types.ts rename to packages/ui/src/components/Container/types.ts diff --git a/packages/ui/src/components/FieldError/FieldError.stories.tsx b/packages/ui/src/components/FieldError/FieldError.stories.tsx new file mode 100644 index 0000000000..2fad9708d8 --- /dev/null +++ b/packages/ui/src/components/FieldError/FieldError.stories.tsx @@ -0,0 +1,87 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { TextField, Input, Form } from 'react-aria-components'; +import { FieldError } from './FieldError'; + +const meta = { + title: 'Backstage UI/FieldError', + component: FieldError, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// Show error with server validation using Form component +export const WithServerValidation: Story = { + render: () => ( +
    + + + + +
    + ), +}; + +// Show error using children +export const WithCustomMessage: Story = { + render: () => ( + + + This is a custom error message. + + ), +}; + +// Show error with render prop function +export const WithRenderProp: Story = { + render: () => ( + 'This field is invalid'} + style={{ + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + }} + > + + + {({ validationErrors }) => + validationErrors.length > 0 ? validationErrors[0] : 'Field is invalid' + } + + + ), +}; diff --git a/packages/ui/src/components/FieldError/FieldError.styles.css b/packages/ui/src/components/FieldError/FieldError.styles.css new file mode 100644 index 0000000000..e69e8637d3 --- /dev/null +++ b/packages/ui/src/components/FieldError/FieldError.styles.css @@ -0,0 +1,7 @@ +.bui-FieldError { + display: inline-block; + color: var(--bui-fg-danger); + font-size: var(--bui-font-size-2); + font-weight: var(--bui-font-weight-regular); + margin-top: var(--bui-space-2); +} diff --git a/packages/ui/src/components/FieldError/FieldError.tsx b/packages/ui/src/components/FieldError/FieldError.tsx new file mode 100644 index 0000000000..f216cd6bb8 --- /dev/null +++ b/packages/ui/src/components/FieldError/FieldError.tsx @@ -0,0 +1,39 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { forwardRef } from 'react'; +import { + FieldError as AriaFieldError, + type FieldErrorProps, +} from 'react-aria-components'; +import clsx from 'clsx'; + +/** @public */ +export const FieldError = forwardRef( + (props: FieldErrorProps, ref) => { + const { className, ...rest } = props; + + return ( + + ); + }, +); + +FieldError.displayName = 'FieldError'; diff --git a/packages/canon/src/components/Tooltip/index.ts b/packages/ui/src/components/FieldError/index.ts similarity index 95% rename from packages/canon/src/components/Tooltip/index.ts rename to packages/ui/src/components/FieldError/index.ts index cf97688854..5b61f83ace 100644 --- a/packages/canon/src/components/Tooltip/index.ts +++ b/packages/ui/src/components/FieldError/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export * from './Tooltip'; +export * from './FieldError'; diff --git a/packages/canon/src/components/Table/TableCellText/TableCellText.stories.tsx b/packages/ui/src/components/FieldLabel/FieldLabel.stories.tsx similarity index 58% rename from packages/canon/src/components/Table/TableCellText/TableCellText.stories.tsx rename to packages/ui/src/components/FieldLabel/FieldLabel.stories.tsx index 3756dd2544..59a0200711 100644 --- a/packages/canon/src/components/Table/TableCellText/TableCellText.stories.tsx +++ b/packages/ui/src/components/FieldLabel/FieldLabel.stories.tsx @@ -15,25 +15,51 @@ */ import type { Meta, StoryObj } from '@storybook/react'; -import { TableCellText } from './TableCellText'; +import { FieldLabel } from './FieldLabel'; const meta = { - title: 'Components/Table/TableCellText', - component: TableCellText, -} satisfies Meta; + title: 'Backstage UI/FieldLabel', + component: FieldLabel, + argTypes: { + label: { + control: 'text', + }, + secondaryLabel: { + control: 'text', + }, + description: { + control: 'text', + }, + }, +} satisfies Meta; export default meta; type Story = StoryObj; export const Default: Story = { args: { - title: 'Hello world', + label: 'Label', + }, +}; + +export const WithSecondaryLabel: Story = { + args: { + ...Default.args, + secondaryLabel: 'Secondary Label', }, }; export const WithDescription: Story = { args: { ...Default.args, - description: 'This is a description', + description: 'Description', + }, +}; + +export const WithAllFields: Story = { + args: { + ...Default.args, + secondaryLabel: 'Secondary Label', + description: 'Description', }, }; diff --git a/packages/ui/src/components/FieldLabel/FieldLabel.styles.css b/packages/ui/src/components/FieldLabel/FieldLabel.styles.css new file mode 100644 index 0000000000..2dfc28a49d --- /dev/null +++ b/packages/ui/src/components/FieldLabel/FieldLabel.styles.css @@ -0,0 +1,27 @@ +.bui-FieldLabelWrapper { + display: flex; + flex-direction: column; + margin-bottom: var(--bui-space-3); + gap: var(--bui-space-1); +} + +.bui-FieldLabel { + color: var(--bui-fg-primary); + margin-right: auto; + cursor: pointer; + font-weight: var(--bui-font-weight-regular); + font-size: var(--bui-font-size-2); +} + +.bui-FieldSecondaryLabel { + color: var(--bui-fg-secondary); + font-weight: var(--bui-font-weight-regular); + margin-left: var(--bui-space-1); +} + +.bui-FieldDescription { + font-weight: var(--bui-font-weight-regular); + font-size: var(--bui-font-size-2); + color: var(--bui-fg-secondary); + margin: 0; +} diff --git a/packages/ui/src/components/FieldLabel/FieldLabel.tsx b/packages/ui/src/components/FieldLabel/FieldLabel.tsx new file mode 100644 index 0000000000..df0735389a --- /dev/null +++ b/packages/ui/src/components/FieldLabel/FieldLabel.tsx @@ -0,0 +1,50 @@ +/* + * 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 { Label } from 'react-aria-components'; +import { forwardRef } from 'react'; +import type { FieldLabelProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; + +/** @public */ +export const FieldLabel = forwardRef( + (props: FieldLabelProps, ref) => { + const { label, secondaryLabel, description, htmlFor, id, ...rest } = props; + + const { classNames } = useStyles('FieldLabel'); + + if (!label) return null; + + return ( +
    + {label && ( + + )} + {description && ( +
    {description}
    + )} +
    + ); + }, +); + +FieldLabel.displayName = 'FieldLabel'; diff --git a/packages/ui/src/components/FieldLabel/index.ts b/packages/ui/src/components/FieldLabel/index.ts new file mode 100644 index 0000000000..b9cebb1bf7 --- /dev/null +++ b/packages/ui/src/components/FieldLabel/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './FieldLabel'; +export * from './types'; diff --git a/packages/ui/src/components/FieldLabel/types.ts b/packages/ui/src/components/FieldLabel/types.ts new file mode 100644 index 0000000000..dc4e444133 --- /dev/null +++ b/packages/ui/src/components/FieldLabel/types.ts @@ -0,0 +1,43 @@ +/* + * 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. + */ + +/** @public */ +export interface FieldLabelProps { + /** + * The label of the text field + */ + label?: string | null; + + /** + * The secondary label of the text field + */ + secondaryLabel?: string | null; + + /** + * The description of the text field + */ + description?: string | null; + + /** + * The HTML for attribute of the text field + */ + htmlFor?: string; + + /** + * The id of the text field + */ + id?: string; +} diff --git a/packages/canon/src/components/Flex/Flex.props.ts b/packages/ui/src/components/Flex/Flex.props.ts similarity index 95% rename from packages/canon/src/components/Flex/Flex.props.ts rename to packages/ui/src/components/Flex/Flex.props.ts index 97a602b42f..b1d9c019d3 100644 --- a/packages/canon/src/components/Flex/Flex.props.ts +++ b/packages/ui/src/components/Flex/Flex.props.ts @@ -29,19 +29,19 @@ const justifyValues = ['start', 'center', 'end', 'between'] as const; const flexPropDefs = { align: { type: 'enum', - className: 'cu-align', + className: 'bui-align', values: alignValues, responsive: true, }, direction: { type: 'enum', - className: 'cu-fd', + className: 'bui-fd', values: directionValues, responsive: true, }, justify: { type: 'enum', - className: 'cu-jc', + className: 'bui-jc', values: justifyValues, responsive: true, }, diff --git a/packages/canon/src/components/Flex/Flex.stories.tsx b/packages/ui/src/components/Flex/Flex.stories.tsx similarity index 78% rename from packages/canon/src/components/Flex/Flex.stories.tsx rename to packages/ui/src/components/Flex/Flex.stories.tsx index bbb5e84692..e557d4700b 100644 --- a/packages/canon/src/components/Flex/Flex.stories.tsx +++ b/packages/ui/src/components/Flex/Flex.stories.tsx @@ -16,9 +16,10 @@ import type { Meta, StoryObj } from '@storybook/react'; import { Flex } from './Flex'; +import { Text } from '../Text'; const meta = { - title: 'Layout/Flex', + title: 'Backstage UI/Flex', component: Flex, argTypes: { align: { @@ -62,7 +63,9 @@ export const Default: Story = { args: { children: ( <> - , , + + + ), }, @@ -70,23 +73,15 @@ export const Default: Story = { export const ColumnDirection: Story = { args: { + ...Default.args, direction: 'column', - children: ( - <> - , , - - ), }, }; export const RowDirection: Story = { args: { + ...Default.args, direction: 'row', - children: ( - <> - , , - - ), }, }; @@ -149,3 +144,24 @@ export const LargeGap: Story = { ), }; + +export const WithTextTruncate: Story = { + render: () => ( + + + + A man looks at a painting in a museum and says, “Brothers and sisters + I have none, but that man's father is my father's son.” Who + is in the painting? + + + + + A man looks at a painting in a museum and says, “Brothers and sisters + I have none, but that man's father is my father's son.” Who + is in the painting? + + + + ), +}; diff --git a/packages/canon/src/components/Flex/Flex.tsx b/packages/ui/src/components/Flex/Flex.tsx similarity index 83% rename from packages/canon/src/components/Flex/Flex.tsx rename to packages/ui/src/components/Flex/Flex.tsx index 7c84ed08f8..ded4fffffa 100644 --- a/packages/canon/src/components/Flex/Flex.tsx +++ b/packages/ui/src/components/Flex/Flex.tsx @@ -20,19 +20,23 @@ import clsx from 'clsx'; import { flexPropDefs } from './Flex.props'; import { extractProps } from '../../utils/extractProps'; import { gapPropDefs } from '../../props/gap-props'; +import { spacingPropDefs } from '../../props/spacing.props'; +import { useStyles } from '../../hooks/useStyles'; /** @public */ export const Flex = forwardRef((props, ref) => { const propDefs = { ...gapPropDefs, ...flexPropDefs, + ...spacingPropDefs, }; + const { classNames } = useStyles('Flex'); const { className, style } = extractProps(props, propDefs); return createElement('div', { ref, - className: clsx('canon-Flex', className), + className: clsx(classNames.root, className), style, children: props.children, }); diff --git a/packages/canon/src/components/Flex/index.ts b/packages/ui/src/components/Flex/index.ts similarity index 100% rename from packages/canon/src/components/Flex/index.ts rename to packages/ui/src/components/Flex/index.ts diff --git a/packages/canon/src/components/Table/TableCellText/TableCellText.styles.css b/packages/ui/src/components/Flex/styles.css similarity index 85% rename from packages/canon/src/components/Table/TableCellText/TableCellText.styles.css rename to packages/ui/src/components/Flex/styles.css index 7a645a1fcb..96c94ab58a 100644 --- a/packages/canon/src/components/Table/TableCellText/TableCellText.styles.css +++ b/packages/ui/src/components/Flex/styles.css @@ -14,8 +14,9 @@ * limitations under the License. */ -.canon-TableCellText { +.bui-Flex { display: flex; - flex-direction: column; - gap: var(--canon-space-0_5); + + /* This helps when using `truncate` on text inside a flex container */ + min-width: 0; } diff --git a/packages/canon/src/components/Flex/types.ts b/packages/ui/src/components/Flex/types.ts similarity index 100% rename from packages/canon/src/components/Flex/types.ts rename to packages/ui/src/components/Flex/types.ts diff --git a/packages/canon/src/components/Grid/Grid.props.ts b/packages/ui/src/components/Grid/Grid.props.ts similarity index 93% rename from packages/canon/src/components/Grid/Grid.props.ts rename to packages/ui/src/components/Grid/Grid.props.ts index a918af6187..b6997cb01b 100644 --- a/packages/canon/src/components/Grid/Grid.props.ts +++ b/packages/ui/src/components/Grid/Grid.props.ts @@ -36,7 +36,7 @@ const columnsValues = [ const gridPropDefs = { columns: { type: 'enum | string', - className: 'cu-columns', + className: 'bui-columns', customProperties: ['--columns'], values: columnsValues, responsive: true, @@ -50,28 +50,28 @@ const gridPropDefs = { const gridItemPropDefs = { colSpan: { type: 'enum | string', - className: 'cu-col-span', + className: 'bui-col-span', customProperties: ['--col-span'], values: columnsValues, responsive: true, }, colEnd: { type: 'enum | string', - className: 'cu-col-end', + className: 'bui-col-end', customProperties: ['--col-end'], values: columnsValues, responsive: true, }, colStart: { type: 'enum | string', - className: 'cu-col-start', + className: 'bui-col-start', customProperties: ['--col-start'], values: columnsValues, responsive: true, }, rowSpan: { type: 'enum | string', - className: 'cu-row-span', + className: 'bui-row-span', customProperties: ['--row-span'], values: columnsValues, responsive: true, diff --git a/packages/canon/src/components/Grid/Grid.stories.tsx b/packages/ui/src/components/Grid/Grid.stories.tsx similarity index 60% rename from packages/canon/src/components/Grid/Grid.stories.tsx rename to packages/ui/src/components/Grid/Grid.stories.tsx index 233d66bae7..26cce0387b 100644 --- a/packages/canon/src/components/Grid/Grid.stories.tsx +++ b/packages/ui/src/components/Grid/Grid.stories.tsx @@ -21,19 +21,8 @@ import { Box } from '../Box/Box'; import { Flex } from '../Flex'; const meta = { - title: 'Layout/Grid', - component: Grid, - argTypes: { - children: { - control: false, - }, - className: { - control: 'text', - }, - }, - args: { - gap: '4', - }, + title: 'Backstage UI/Grid', + component: Grid.Root, } satisfies Meta; export default meta; @@ -53,27 +42,22 @@ const FakeBox = () => ( ); export const Default: Story = { - args: {}, - render: args => ( - - - - - - ), + args: { + children: ( + <> + + + + + ), + }, }; export const LargeGap: Story = { args: { + ...Default.args, gap: '64px', }, - render: args => ( - - - - - - ), }; export const ColumnSizes: Story = { @@ -81,16 +65,16 @@ export const ColumnSizes: Story = { columns: '12', }, render: args => ( - + {Array.from({ length: 11 }, (_, i) => ( - + - + ))} ), @@ -101,27 +85,25 @@ export const RowAndColumns: Story = { columns: '12', }, render: args => ( - - - - - - - - - - - - - + + + + + + + + + + + ), }; diff --git a/packages/canon/src/components/Grid/Grid.tsx b/packages/ui/src/components/Grid/Grid.tsx similarity index 76% rename from packages/canon/src/components/Grid/Grid.tsx rename to packages/ui/src/components/Grid/Grid.tsx index aedce886ed..27257c8972 100644 --- a/packages/canon/src/components/Grid/Grid.tsx +++ b/packages/ui/src/components/Grid/Grid.tsx @@ -20,18 +20,23 @@ import { extractProps } from '../../utils/extractProps'; import { gridItemPropDefs, gridPropDefs } from './Grid.props'; import clsx from 'clsx'; import type { GridItemProps, GridProps } from './types'; +import { spacingPropDefs } from '../../props/spacing.props'; +import { useStyles } from '../../hooks/useStyles'; -const GridBase = forwardRef((props, ref) => { +const GridRoot = forwardRef((props, ref) => { const propDefs = { ...gapPropDefs, ...gridPropDefs, + ...spacingPropDefs, }; + const { classNames } = useStyles('Grid'); + const { className, style } = extractProps(props, propDefs); return createElement('div', { ref, - className: clsx('canon-Grid', className), + className: clsx(classNames.root, className), style, children: props.children, }); @@ -42,15 +47,19 @@ const GridItem = forwardRef((props, ref) => { ...gridItemPropDefs, }; + const { classNames } = useStyles('Grid'); const { className, style } = extractProps(props, propDefs); return createElement('div', { ref, - className: clsx('canon-GridItem', className), + className: clsx(classNames.item, className), style, children: props.children, }); }); /** @public */ -export const Grid = Object.assign(GridBase, { Item: GridItem }); +export const Grid = { + Root: GridRoot, + Item: GridItem, +}; diff --git a/packages/canon/src/components/Grid/index.ts b/packages/ui/src/components/Grid/index.ts similarity index 100% rename from packages/canon/src/components/Grid/index.ts rename to packages/ui/src/components/Grid/index.ts diff --git a/packages/canon/src/components/Grid/styles.css b/packages/ui/src/components/Grid/styles.css similarity index 57% rename from packages/canon/src/components/Grid/styles.css rename to packages/ui/src/components/Grid/styles.css index 0bb9267795..e21191ad47 100644 --- a/packages/canon/src/components/Grid/styles.css +++ b/packages/ui/src/components/Grid/styles.css @@ -1,3 +1,3 @@ -.canon-Grid { +.bui-Grid { display: grid; } diff --git a/packages/canon/src/components/Grid/types.ts b/packages/ui/src/components/Grid/types.ts similarity index 100% rename from packages/canon/src/components/Grid/types.ts rename to packages/ui/src/components/Grid/types.ts diff --git a/packages/ui/src/components/Header/Header.stories.tsx b/packages/ui/src/components/Header/Header.stories.tsx new file mode 100644 index 0000000000..1674e5e0be --- /dev/null +++ b/packages/ui/src/components/Header/Header.stories.tsx @@ -0,0 +1,543 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj, StoryFn } from '@storybook/react'; +import { Header } from './Header'; +import { HeaderMenuItem, HeaderTab } from './types'; +import { Button } from '../Button'; +import { HeaderPage } from '../HeaderPage'; +import { MemoryRouter } from 'react-router-dom'; +import { Container } from '../Container'; +import { Text } from '../Text'; +import { ButtonIcon } from '../ButtonIcon'; +import { + RiHeartLine, + RiEmotionHappyLine, + RiCloudy2Line, +} from '@remixicon/react'; +import { HeaderPageBreadcrumb } from '../HeaderPage/types'; + +const meta = { + title: 'Backstage UI/Header', + component: Header, + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const withRouter = (Story: StoryFn) => ( + + + +); + +const tabs: HeaderTab[] = [ + { + id: 'overview', + label: 'Overview', + }, + { + id: 'checks', + label: 'Checks', + }, + { + id: 'tracks', + label: 'Tracks', + }, + { + id: 'campaigns', + label: 'Campaigns', + href: '/campaigns', + }, + { + id: 'integrations', + label: 'Integrations', + href: '/integrations', + }, +]; + +const tabs2: HeaderTab[] = [ + { + id: 'Banana', + label: 'Banana', + }, + { + id: 'Apple', + label: 'Apple', + }, + { + id: 'Orange', + label: 'Orange', + }, +]; + +const menuItems: HeaderMenuItem[] = [ + { + label: 'Settings', + value: 'settings', + }, + { + label: 'Invite new members', + value: 'invite-new-members', + }, +]; + +const breadcrumbs: HeaderPageBreadcrumb[] = [ + { + label: 'Home', + href: '/', + }, + { + label: 'Dashboard', + href: '/dashboard', + }, + { + label: 'Settings', + href: '/settings', + }, +]; + +// Extract layout decorator as a reusable constant +const layoutDecorator = [ + (Story: StoryFn) => ( + <> +
    +
    + + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, + quos. + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, + quos. + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, + quos. + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, + quos. + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, + quos. + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, + quos. + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, + quos. + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, + quos. + + +
    + + ), + withRouter, +]; + +export const Default: Story = { + args: {}, + decorators: [withRouter], +}; + +export const WithTabs: Story = { + args: { + tabs, + }, + decorators: [withRouter], +}; + +export const WithOptions: Story = { + args: { + menuItems, + }, + decorators: [withRouter], +}; + +export const WithCustomActions: Story = { + args: {}, + decorators: [withRouter], + render: args => ( +
    + } /> + } /> + } /> + + } + /> + ), +}; + +export const WithAllOptions: Story = { + args: { + title: 'My plugin', + titleLink: '/', + menuItems, + }, + decorators: [withRouter], + render: WithCustomActions.render, +}; + +export const WithAllOptionsAndTabs: Story = { + args: { + ...WithAllOptions.args, + tabs, + }, + decorators: [withRouter], + render: WithAllOptions.render, +}; + +export const WithHeaderPage: Story = { + args: { + ...WithAllOptionsAndTabs.args, + }, + decorators: [withRouter], + render: args => ( + <> +
    + } /> + } /> + } /> + + } + /> + Custom action} + breadcrumbs={breadcrumbs} + /> + + ), +}; + +export const WithLayout: Story = { + args: { + menuItems, + }, + decorators: layoutDecorator, + render: args => ( + <> +
    + Custom action} + breadcrumbs={breadcrumbs} + /> + + ), +}; + +export const WithLayoutNoTabs: Story = { + args: { + menuItems, + }, + decorators: layoutDecorator, + render: args => ( + <> +
    + + + ), +}; + +export const WithEverything: Story = { + args: { + menuItems, + tabs, + titleLink: '/', + }, + decorators: layoutDecorator, + render: args => ( + <> +
    + } /> + } /> + } /> + + } + /> + + + + + } + /> + + ), +}; + +export const WithMockedURLCampaigns: Story = { + args: { + tabs, + }, + render: args => ( + +
    + + + Current URL is mocked to be: /campaigns + + + Notice how the "Campaigns" tab is selected (highlighted) because it + matches the current path. + + + + ), +}; + +export const WithMockedURLIntegrations: Story = { + args: { + tabs, + }, + render: args => ( + +
    + + + Current URL is mocked to be: /integrations + + + Notice how the "Integrations" tab is selected (highlighted) because it + matches the current path. + + + + ), +}; + +export const WithMockedURLNoMatch: Story = { + args: { + tabs, + }, + render: args => ( + +
    + + + Current URL is mocked to be: /some-other-page + + + No tab is selected because the current path doesn't match any tab's + href. + + + Tabs without href (like "Overview", "Checks", "Tracks") fall back to + React Aria's internal state. + + + + ), +}; + +export const WithTabsMatchingStrategies: Story = { + args: { + title: 'Route Matching Demo', + tabs: [ + { + id: 'home', + label: 'Home', + href: '/home', + }, + { + id: 'mentorship', + label: 'Mentorship', + href: '/mentorship', + matchStrategy: 'prefix', + }, + { + id: 'catalog', + label: 'Catalog', + href: '/catalog', + matchStrategy: 'prefix', + }, + { + id: 'settings', + label: 'Settings', + href: '/settings', + }, + ], + }, + render: args => ( + +
    + + + Current URL: /mentorship/events + +
    + + Notice how the "Mentorship" tab is active even though we're on a + nested route. This is because it uses{' '} + matchStrategy="prefix". + +
    + + • Home: exact matching (default) - not active + + + • Mentorship: prefix matching - IS active (URL starts + with /mentorship) + + + • Catalog: prefix matching - not active + + + • Settings: exact matching (default) - not active + +
    + + ), +}; + +export const WithTabsExactMatching: Story = { + args: { + title: 'Exact Matching Demo', + tabs: [ + { + id: 'mentorship', + label: 'Mentorship', + href: '/mentorship', + }, + { + id: 'events', + label: 'Events', + href: '/mentorship/events', + }, + { + id: 'mentors', + label: 'Mentors', + href: '/mentorship/mentors', + }, + ], + }, + render: args => ( + +
    + + + Current URL: /mentorship/events + +
    + + With default exact matching, only the "Events" tab is active because + it exactly matches the current URL. The "Mentorship" tab is not active + even though the URL is under /mentorship. + +
    + + ), +}; + +export const WithTabsPrefixMatchingDeep: Story = { + args: { + title: 'Deep Nesting Demo', + tabs: [ + { + id: 'catalog', + label: 'Catalog', + href: '/catalog', + matchStrategy: 'prefix', + }, + { + id: 'users', + label: 'Users', + href: '/catalog/users', + matchStrategy: 'prefix', + }, + { + id: 'components', + label: 'Components', + href: '/catalog/components', + matchStrategy: 'prefix', + }, + ], + }, + render: args => ( + +
    + + + Current URL: /catalog/users/john/details + +
    + Both "Catalog" and "Users" tabs are active because: + + • Catalog: URL starts with /catalog + + + • Users: URL starts with /catalog/users + + + • Components: not active (URL doesn't start with + /catalog/components) + +
    + + This demonstrates how prefix matching works with deeply nested routes. + +
    + + ), +}; diff --git a/packages/ui/src/components/Header/Header.styles.css b/packages/ui/src/components/Header/Header.styles.css new file mode 100644 index 0000000000..d0a9012ecc --- /dev/null +++ b/packages/ui/src/components/Header/Header.styles.css @@ -0,0 +1,94 @@ +/* + * 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. + */ + +.bui-HeaderToolbar { + margin-bottom: var(--bui-space-6); + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0px; + right: 0px; + height: 16px; + background-color: var(--bui-bg); + z-index: 0; + } + + &[data-has-tabs='true'] { + margin-bottom: 0; + } +} + +.bui-HeaderToolbarWrapper { + position: relative; + z-index: 1; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + background-color: var(--bui-bg-surface-1); + padding-inline: var(--bui-space-5); + border-bottom: 1px solid var(--bui-border); + color: var(--bui-fg-primary); + height: 52px; +} + +.bui-HeaderToolbarContent { + display: flex; + flex-direction: row; + align-items: center; + gap: var(--bui-space-2); +} + +.bui-HeaderToolbarName { + display: flex; + flex-direction: row; + align-items: center; + gap: var(--bui-space-2); + font-size: var(--bui-font-size-3); + font-weight: var(--bui-font-weight-regular); + flex-shrink: 0; +} + +.bui-HeaderToolbarIcon { + width: 16px; + height: 16px; + color: var(--bui-fg-primary); + + & svg { + width: 100%; + height: 100%; + } +} + +.bui-HeaderToolbarControls { + position: absolute; + right: var(--bui-space-5); + top: 50%; + transform: translateY(-50%); + display: flex; + flex-direction: row; + align-items: center; + gap: var(--bui-space-2); +} + +.bui-HeaderTabsWrapper { + margin-bottom: var(--bui-space-4); + padding-inline: var(--bui-space-3); + border-bottom: 1px solid var(--bui-border); + background-color: var(--bui-bg-surface-1); +} diff --git a/packages/ui/src/components/Header/Header.tsx b/packages/ui/src/components/Header/Header.tsx new file mode 100644 index 0000000000..01856aa3ce --- /dev/null +++ b/packages/ui/src/components/Header/Header.tsx @@ -0,0 +1,79 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { HeaderProps } from './types'; +import { HeaderToolbar } from './HeaderToolbar'; +import { Tabs, TabList, Tab } from '../Tabs'; +import { useStyles } from '../../hooks/useStyles'; +import { type NavigateOptions } from 'react-router-dom'; + +declare module 'react-aria-components' { + interface RouterConfig { + routerOptions: NavigateOptions; + } +} + +/** + * A component that renders a toolbar. + * + * @public + */ +export const Header = (props: HeaderProps) => { + const { + tabs, + icon, + title, + titleLink, + menuItems, + customActions, + onTabSelectionChange, + } = props; + + const { classNames } = useStyles('Header'); + + const hasTabs = tabs && tabs.length > 0; + + return ( + <> + + {tabs && ( +
    + + + {tabs?.map(tab => ( + + {tab.label} + + ))} + + +
    + )} + + ); +}; diff --git a/packages/ui/src/components/Header/HeaderToolbar.tsx b/packages/ui/src/components/Header/HeaderToolbar.tsx new file mode 100644 index 0000000000..6daa24efa1 --- /dev/null +++ b/packages/ui/src/components/Header/HeaderToolbar.tsx @@ -0,0 +1,99 @@ +/* + * 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 { Link, RouterProvider } from 'react-aria-components'; +import { useStyles } from '../../hooks/useStyles'; +import { useRef } from 'react'; +import { RiMore2Line, RiShapesLine } from '@remixicon/react'; +import type { HeaderToolbarProps } from './types'; +import { ButtonIcon } from '../ButtonIcon'; +import { Menu } from '../Menu'; +import { Text } from '../Text'; +import { useNavigate, useHref } from 'react-router-dom'; + +/** + * A component that renders a toolbar. + * + * @internal + */ +export const HeaderToolbar = (props: HeaderToolbarProps) => { + const { icon, title, titleLink, menuItems, customActions, hasTabs } = props; + const { classNames } = useStyles('Header'); + let navigate = useNavigate(); + + // Refs for collision detection + const toolbarWrapperRef = useRef(null); + const toolbarContentRef = useRef(null); + const toolbarControlsRef = useRef(null); + + const titleContent = ( + <> +
    {icon || }
    + {title || 'Your plugin'} + + ); + + return ( + +
    +
    +
    + + {titleLink ? ( + + {titleContent} + + ) : ( +
    {titleContent}
    + )} +
    +
    +
    + {customActions} + {menuItems && ( + + ( + } + variant="tertiary" + {...props} + /> + )} + /> + + + + {menuItems.map(option => ( + option.onClick?.()} + > + {option.label} + + ))} + + + + + )} +
    +
    +
    +
    + ); +}; diff --git a/packages/canon/src/components/Table/TableCell/TableCell.styles.css b/packages/ui/src/components/Header/index.tsx similarity index 85% rename from packages/canon/src/components/Table/TableCell/TableCell.styles.css rename to packages/ui/src/components/Header/index.tsx index 26d0868b49..ba382fb79d 100644 --- a/packages/canon/src/components/Table/TableCell/TableCell.styles.css +++ b/packages/ui/src/components/Header/index.tsx @@ -14,7 +14,5 @@ * limitations under the License. */ -.canon-TableCell { - padding: var(--canon-space-3); - font-size: var(--canon-font-size-3); -} +export { Header } from './Header'; +export type { HeaderProps, HeaderTab, HeaderMenuItem } from './types'; diff --git a/packages/ui/src/components/Header/types.ts b/packages/ui/src/components/Header/types.ts new file mode 100644 index 0000000000..baf9281cfe --- /dev/null +++ b/packages/ui/src/components/Header/types.ts @@ -0,0 +1,75 @@ +/* + * 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 { TabsProps } from 'react-aria-components'; +import { TabMatchStrategy } from '../Tabs'; + +/** + * Props for the main Header component. + * + * @public + */ +export interface HeaderProps { + icon?: React.ReactNode; + title?: string; + titleLink?: string; + customActions?: React.ReactNode; + menuItems?: HeaderMenuItem[]; + tabs?: HeaderTab[]; + onTabSelectionChange?: TabsProps['onSelectionChange']; +} + +/** + * Represents a tab item in the header navigation. + * + * @public + */ +export interface HeaderTab { + id: string; + label: string; + href?: string; + /** + * Strategy for matching the current route to determine if this tab should be active. + * - 'exact': Tab href must exactly match the current pathname (default) + * - 'prefix': Tab is active if current pathname starts with tab href + */ + matchStrategy?: TabMatchStrategy; +} + +/** + * Represents an option item in the header dropdown menu. + * + * @public + */ +export interface HeaderMenuItem { + label: string; + value: string; + onClick?: () => void; +} + +/** + * Props for the HeaderToolbar component. + * + * @internal + */ +export interface HeaderToolbarProps { + icon?: HeaderProps['icon']; + title?: HeaderProps['title']; + titleLink?: HeaderProps['titleLink']; + customActions?: HeaderProps['customActions']; + menuItems?: HeaderProps['menuItems']; + hasTabs?: boolean; +} diff --git a/packages/ui/src/components/HeaderPage/HeaderPage.stories.tsx b/packages/ui/src/components/HeaderPage/HeaderPage.stories.tsx new file mode 100644 index 0000000000..b470ca3f63 --- /dev/null +++ b/packages/ui/src/components/HeaderPage/HeaderPage.stories.tsx @@ -0,0 +1,331 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj, StoryFn } from '@storybook/react'; +import { HeaderPage } from './HeaderPage'; +import type { HeaderTab, HeaderMenuItem } from '../Header/types'; +import { MemoryRouter } from 'react-router-dom'; +import { Button } from '../Button'; +import { Container } from '../Container'; +import { Text } from '../Text'; + +const meta = { + title: 'Backstage UI/HeaderPage', + component: HeaderPage, + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const tabs: HeaderTab[] = [ + { + id: 'overview', + label: 'Overview', + }, + { + id: 'checks', + label: 'Checks', + }, + { + id: 'tracks', + label: 'Tracks', + }, + { + id: 'campaigns', + label: 'Campaigns', + }, + { + id: 'integrations', + label: 'Integrations', + }, +]; + +const menuItems: HeaderMenuItem[] = [ + { + label: 'Settings', + value: 'settings', + }, + { + label: 'Invite new members', + value: 'invite-new-members', + }, +]; + +const withRouter = (Story: StoryFn) => ( + + + +); + +// Extract layout decorator as a reusable constant +const layoutDecorator = [ + (Story: StoryFn) => ( + <> +
    +
    + + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, + quos. + + +
    + + ), +]; + +export const Default: Story = { + args: { + title: 'Page Title', + }, +}; + +export const WithTabs: Story = { + args: { + ...Default.args, + tabs, + }, + decorators: [withRouter], +}; + +export const WithMenuItems: Story = { + args: { + ...Default.args, + menuItems, + }, +}; + +export const WithCustomActions: Story = { + render: () => ( + Custom action} + /> + ), +}; + +export const WithBreadcrumbs: Story = { + decorators: [withRouter], + args: { + ...Default.args, + breadcrumbs: [{ label: 'Home', href: '/' }], + }, +}; + +export const WithLongBreadcrumbs: Story = { + decorators: [withRouter], + args: { + ...Default.args, + breadcrumbs: [ + { label: 'Home', href: '/' }, + { label: 'Long Breadcrumb Name', href: '/long-breadcrumb' }, + ], + }, +}; + +export const WithEverything: Story = { + decorators: [withRouter], + render: () => ( + Custom action} + breadcrumbs={[{ label: 'Home', href: '/' }]} + /> + ), +}; + +export const WithLayout: Story = { + args: { + ...WithEverything.args, + }, + decorators: [withRouter, ...layoutDecorator], + render: WithEverything.render, +}; + +export const WithTabsMatchingStrategies: Story = { + args: { + title: 'Route Matching Demo', + tabs: [ + { + id: 'home', + label: 'Home', + href: '/home', + }, + { + id: 'mentorship', + label: 'Mentorship', + href: '/mentorship', + matchStrategy: 'prefix', + }, + { + id: 'catalog', + label: 'Catalog', + href: '/catalog', + matchStrategy: 'prefix', + }, + { + id: 'settings', + label: 'Settings', + href: '/settings', + }, + ], + }, + render: args => ( + + + + + Current URL: /mentorship/events + +
    + + Notice how the "Mentorship" tab is active even though we're on a + nested route. This is because it uses{' '} + matchStrategy="prefix". + +
    + + • Home: exact matching (default) - not active + + + • Mentorship: prefix matching - IS active (URL starts + with /mentorship) + + + • Catalog: prefix matching - not active + + + • Settings: exact matching (default) - not active + +
    +
    + ), +}; + +export const WithTabsExactMatching: Story = { + args: { + title: 'Exact Matching Demo', + tabs: [ + { + id: 'mentorship', + label: 'Mentorship', + href: '/mentorship', + }, + { + id: 'events', + label: 'Events', + href: '/mentorship/events', + }, + { + id: 'mentors', + label: 'Mentors', + href: '/mentorship/mentors', + }, + ], + }, + render: args => ( + + + + + Current URL: /mentorship/events + +
    + + With default exact matching, only the "Events" tab is active because + it exactly matches the current URL. The "Mentorship" tab is not active + even though the URL is under /mentorship. + +
    +
    + ), +}; + +export const WithTabsPrefixMatchingDeep: Story = { + args: { + title: 'Deep Nesting Demo', + tabs: [ + { + id: 'catalog', + label: 'Catalog', + href: '/catalog', + matchStrategy: 'prefix', + }, + { + id: 'users', + label: 'Users', + href: '/catalog/users', + matchStrategy: 'prefix', + }, + { + id: 'components', + label: 'Components', + href: '/catalog/components', + matchStrategy: 'prefix', + }, + ], + }, + render: args => ( + + + + + Current URL: /catalog/users/john/details + +
    + Both "Catalog" and "Users" tabs are active because: + + • Catalog: URL starts with /catalog + + + • Users: URL starts with /catalog/users + + + • Components: not active (URL doesn't start with + /catalog/components) + +
    + + This demonstrates how prefix matching works with deeply nested routes. + +
    +
    + ), +}; diff --git a/canon-docs/src/components/Chip/styles.module.css b/packages/ui/src/components/HeaderPage/HeaderPage.styles.css similarity index 55% rename from canon-docs/src/components/Chip/styles.module.css rename to packages/ui/src/components/HeaderPage/HeaderPage.styles.css index 44337afb0a..443f7b4860 100644 --- a/canon-docs/src/components/Chip/styles.module.css +++ b/packages/ui/src/components/HeaderPage/HeaderPage.styles.css @@ -14,30 +14,34 @@ * limitations under the License. */ -.chip { - display: inline-flex; +.bui-HeaderPage { + display: flex; + flex-direction: column; + gap: var(--bui-space-1); + margin-top: var(--bui-space-6); + margin-bottom: var(--bui-space-6); +} + +.bui-HeaderPageContent { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.bui-HeaderPageTabsWrapper { + margin-left: -8px; +} + +.bui-HeaderPageControls { + display: flex; + flex-direction: row; align-items: center; - font-family: monospace; - font-size: 13px; - border-radius: 6px; - padding: 0px 8px; - height: 24px; - margin-right: 4px; - background-color: #f0f0f0; - color: #5d5d5d; - transition: background-color 0.2s ease-in-out; + gap: var(--bui-space-2); } -.head { - background-color: #eaf2fd; - color: #2563eb; -} - -[data-theme='dark'] .chip { - background-color: #2c2c2c; - color: #fff; -} - -[data-theme='dark'] .chip.head { - background-color: #26417b; +.bui-HeaderPageBreadcrumbs { + display: flex; + flex-direction: row; + align-items: center; + gap: var(--bui-space-2); } diff --git a/packages/ui/src/components/HeaderPage/HeaderPage.tsx b/packages/ui/src/components/HeaderPage/HeaderPage.tsx new file mode 100644 index 0000000000..cd3c683e50 --- /dev/null +++ b/packages/ui/src/components/HeaderPage/HeaderPage.tsx @@ -0,0 +1,113 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { HeaderPageProps } from './types'; +import { Menu } from '../Menu'; +import { Text } from '../Text'; +import { ButtonIcon } from '../ButtonIcon'; +import { RiArrowRightSLine, RiMore2Line } from '@remixicon/react'; +import { Tabs, TabList, Tab } from '../Tabs'; +import { useStyles } from '../../hooks/useStyles'; +import { Container } from '../Container'; +import { Link } from '../Link'; +import { Fragment } from 'react/jsx-runtime'; + +/** + * A component that renders a header page. + * + * @public + */ +export const HeaderPage = (props: HeaderPageProps) => { + const { title, menuItems, tabs, customActions, breadcrumbs } = props; + const { classNames } = useStyles('HeaderPage'); + + return ( + +
    +
    + {breadcrumbs && + breadcrumbs.map(breadcrumb => ( + + + {breadcrumb.label} + + + + ))} + + {title} + +
    +
    + {customActions} + {menuItems && ( + + ( + } + variant="tertiary" + /> + )} + /> + + + + {menuItems.map(menuItem => ( + menuItem.onClick?.()} + > + {menuItem.label} + + ))} + + + + + )} +
    +
    + {tabs && ( +
    + + + {tabs.map(tab => ( + + {tab.label} + + ))} + + +
    + )} +
    + ); +}; diff --git a/canon-docs/src/components/Columns/styles.css b/packages/ui/src/components/HeaderPage/index.tsx similarity index 84% rename from canon-docs/src/components/Columns/styles.css rename to packages/ui/src/components/HeaderPage/index.tsx index c34acfc406..fd2c0aaff7 100644 --- a/canon-docs/src/components/Columns/styles.css +++ b/packages/ui/src/components/HeaderPage/index.tsx @@ -14,9 +14,5 @@ * limitations under the License. */ -.columns { - display: grid; - grid-template-columns: repeat(3, 1fr); - row-gap: 20px; - column-gap: 80px; -} +export { HeaderPage } from './HeaderPage'; +export type { HeaderPageProps, HeaderPageBreadcrumb } from './types'; diff --git a/packages/ui/src/components/HeaderPage/types.ts b/packages/ui/src/components/HeaderPage/types.ts new file mode 100644 index 0000000000..38e3872c11 --- /dev/null +++ b/packages/ui/src/components/HeaderPage/types.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { HeaderTab, HeaderMenuItem } from '../Header/types'; + +/** + * Props for the main HeaderPage component. + * + * @public + */ +export interface HeaderPageProps { + title?: string; + customActions?: React.ReactNode; + menuItems?: HeaderMenuItem[]; + tabs?: HeaderTab[]; + breadcrumbs?: HeaderPageBreadcrumb[]; +} + +/** + * Represents a breadcrumb item in the header. + * + * @public + */ +export interface HeaderPageBreadcrumb { + label: string; + href: string; +} diff --git a/packages/canon/src/components/Icon/Icon.stories.tsx b/packages/ui/src/components/Icon/Icon.stories.tsx similarity index 97% rename from packages/canon/src/components/Icon/Icon.stories.tsx rename to packages/ui/src/components/Icon/Icon.stories.tsx index 92f15d8ce2..b4eb01f2ff 100644 --- a/packages/canon/src/components/Icon/Icon.stories.tsx +++ b/packages/ui/src/components/Icon/Icon.stories.tsx @@ -20,7 +20,7 @@ import { IconProvider } from './provider'; import { icons } from './icons'; const meta = { - title: 'Components/Icon', + title: 'Backstage UI/Icon', component: Icon, argTypes: { name: { diff --git a/packages/canon/src/components/Icon/Icon.tsx b/packages/ui/src/components/Icon/Icon.tsx similarity index 81% rename from packages/canon/src/components/Icon/Icon.tsx rename to packages/ui/src/components/Icon/Icon.tsx index 8877420a84..6f1e3d9c72 100644 --- a/packages/canon/src/components/Icon/Icon.tsx +++ b/packages/ui/src/components/Icon/Icon.tsx @@ -18,22 +18,25 @@ import { ComponentType } from 'react'; import { useIcons } from './context'; import type { IconProps } from './types'; import clsx from 'clsx'; +import { useStyles } from '../../hooks/useStyles'; /** @public */ export const Icon = (props: IconProps) => { const { name, size, className, style, ...restProps } = props; const { icons } = useIcons(); - const CanonIcon = icons[name] as ComponentType>; + const BckstageIcon = icons[name] as ComponentType>; - if (!CanonIcon) { + if (!BckstageIcon) { console.error(`Icon "${name}" not found or is not a valid component.`); return null; } + const { classNames } = useStyles('Icon'); + return ( - ( + + + + ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + href: '/', + children: 'Sign up for Backstage', + }, +}; + +export const ExternalLink: Story = { + args: { + href: 'https://backstage.io', + children: 'Sign up for Backstage', + target: '_blank', + }, +}; + +export const AllVariants: Story = { + args: { + ...Default.args, + }, + render: args => ( + + + + + + + + + + + ), +}; + +export const AllColors: Story = { + render: () => ( + + + + + + + + ), +}; + +export const AllWeights: Story = { + render: () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ), +}; + +export const Truncate: Story = { + args: { + children: + "A man looks at a painting in a museum and says, “Brothers and sisters I have none, but that man's father is my father's son.” Who is in the painting?", + href: '/', + truncate: true, + style: { width: '480px' }, + }, +}; + +export const Responsive: Story = { + args: { + ...Default.args, + variant: { + xs: 'title-x-small', + md: 'body-x-small', + }, + }, +}; + +export const Playground: Story = { + args: { + ...Default.args, + }, + render: args => ( + + Title X Small + + Body X Small + + Body Small + + Body Medium + + Body Large + + Title Small + + Title Medium + + + ), +}; diff --git a/packages/ui/src/components/Link/Link.tsx b/packages/ui/src/components/Link/Link.tsx new file mode 100644 index 0000000000..cf2686ba81 --- /dev/null +++ b/packages/ui/src/components/Link/Link.tsx @@ -0,0 +1,77 @@ +/* + * 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 { forwardRef } from 'react'; +import { Link as AriaLink, RouterProvider } from 'react-aria-components'; +import clsx from 'clsx'; +import { useStyles } from '../../hooks/useStyles'; +import type { LinkProps } from './types'; +import { useNavigate, useHref } from 'react-router-dom'; +import { isExternalLink } from '../../utils/isExternalLink'; + +/** @public */ +export const Link = forwardRef((props, ref) => { + const navigate = useNavigate(); + const { + className, + variant = 'body', + weight = 'regular', + color = 'primary', + truncate, + href, + ...restProps + } = props; + + const { classNames: linkClassNames } = useStyles('Link'); + const { classNames: textClassNames, dataAttributes: textDataAttributes } = + useStyles('Text', { + variant, + weight, + color, + }); + + const isExternal = isExternalLink(href); + + // If it's an external link, render AriaLink without RouterProvider + if (isExternal) { + return ( + + ); + } + + // For internal links, use RouterProvider + return ( + + + + ); +}); + +Link.displayName = 'Link'; diff --git a/packages/canon/src/components/Link/index.ts b/packages/ui/src/components/Link/index.ts similarity index 100% rename from packages/canon/src/components/Link/index.ts rename to packages/ui/src/components/Link/index.ts diff --git a/packages/ui/src/components/Link/styles.css b/packages/ui/src/components/Link/styles.css new file mode 100644 index 0000000000..76a56e2d7a --- /dev/null +++ b/packages/ui/src/components/Link/styles.css @@ -0,0 +1,32 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.bui-Link { + font-family: var(--bui-font-regular); + padding: 0; + margin: 0; + cursor: pointer; + text-decoration-line: none; + display: inline-block; + + &:hover { + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-thickness: min(2px, max(1px, 0.05em)); + text-underline-offset: calc(0.025em + 2px); + text-decoration-color: color-mix(in srgb, currentColor 30%, transparent); + } +} diff --git a/packages/canon/src/components/Heading/types.ts b/packages/ui/src/components/Link/types.ts similarity index 54% rename from packages/canon/src/components/Heading/types.ts rename to packages/ui/src/components/Link/types.ts index 6250e8d2eb..33c33b26c0 100644 --- a/packages/canon/src/components/Heading/types.ts +++ b/packages/ui/src/components/Link/types.ts @@ -14,25 +14,22 @@ * limitations under the License. */ -import { Breakpoint } from '../../types'; +import type { + Breakpoint, + TextColors, + TextColorStatus, + TextVariants, + TextWeights, +} from '../../types'; +import type { LinkProps as AriaLinkProps } from 'react-aria-components'; /** @public */ -export interface HeadingProps { - children: React.ReactNode; - variant?: - | 'display' - | 'title1' - | 'title2' - | 'title3' - | 'title4' - | 'title5' - | Partial< - Record< - Breakpoint, - 'display' | 'title1' | 'title2' | 'title3' | 'title4' | 'title5' - > - >; - as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; - className?: string; - style?: React.CSSProperties; +export interface LinkProps extends AriaLinkProps { + variant?: TextVariants | Partial>; + weight?: TextWeights | Partial>; + color?: + | TextColors + | TextColorStatus + | Partial>; + truncate?: boolean; } diff --git a/packages/ui/src/components/Menu/Combobox.tsx b/packages/ui/src/components/Menu/Combobox.tsx new file mode 100644 index 0000000000..796b0921ad --- /dev/null +++ b/packages/ui/src/components/Menu/Combobox.tsx @@ -0,0 +1,243 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + forwardRef, + useState, + useMemo, + useCallback, + ChangeEvent, + KeyboardEvent, + useRef, + useEffect, +} from 'react'; +import clsx from 'clsx'; +import { MenuComboboxOption, MenuComboboxProps } from './types'; +import { Icon } from '../..'; +import { useId } from 'react-aria'; + +const getListboxItemId = (listboxId: string, optionValue: string): string => + `${listboxId}-option-${optionValue}`; + +// Internal component for rendering individual items +function ComboboxItem({ + option, + optionIndex, + value, + activeOptionIndex, + onItemActive, + onItemSelect, + listboxId, +}: { + option: MenuComboboxOption; + optionIndex: number; + value?: string[]; + activeOptionIndex: number; + onItemActive: (index: number) => void; + onItemSelect: (value: string) => void; + listboxId: string; +}) { + const isSelected = value?.includes(option.value) ?? false; + const isHighlighted = optionIndex === activeOptionIndex; + const itemId = getListboxItemId(listboxId, option.value); + + const itemRef = useRef(null); + + // Scroll the item into view when it becomes highlighted + useEffect(() => { + if (isHighlighted && itemRef.current) { + itemRef.current.scrollIntoView({ block: 'nearest' }); + } + }, [isHighlighted]); + + return ( +
    !option.disabled && onItemActive(optionIndex)} + onClick={() => !option.disabled && onItemSelect(option.value)} + > +
    + {isSelected &&
    +
    {option.label}
    +
    + ); +} + +/** @public */ +export const Combobox = forwardRef( + (props, ref) => { + const { + options, + value, + onValueChange, + multiselect = false, + className, + ...rest + } = props; + + const triggerId = useId(); + const listboxId = `${triggerId}-listbox`; + + // State management + const [filterString, setFilterString] = useState(''); + const [activeOptionIndex, setActiveOptionIndex] = useState(0); + + // Filter options based on input + const filteredOptions = useMemo(() => { + if (!filterString) return options; + const lowerFilterString = filterString.toLocaleLowerCase('en-US'); + return options.filter(option => + option.label.toLocaleLowerCase('en-US').includes(lowerFilterString), + ); + }, [filterString, options]); + + // Get the active descendant ID for accessibility + const activeDescendantId = + activeOptionIndex >= 0 && filteredOptions.length > 0 + ? getListboxItemId(listboxId, filteredOptions[activeOptionIndex].value) + : undefined; + + const handleValueChange = useCallback( + (toggledValue: string) => { + let newValue: string[]; + if (multiselect) { + newValue = value?.includes(toggledValue) + ? value.filter(v => v !== toggledValue) + : [...(value ?? []), toggledValue]; + } else { + newValue = value?.includes(toggledValue) ? [] : [toggledValue]; + } + + onValueChange?.(newValue); + }, + [multiselect, onValueChange, value], + ); + + const handleSearchChange = useCallback( + (e: ChangeEvent) => { + setFilterString(e.target.value); + setActiveOptionIndex(0); + e.preventDefault(); + }, + [], + ); + + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + let wasEscapeKey = false; + switch (e.key) { + case 'ArrowDown': + e.preventDefault(); + setActiveOptionIndex(prev => + Math.min(prev + 1, filteredOptions.length - 1), + ); + break; + case 'ArrowUp': + e.preventDefault(); + setActiveOptionIndex(prev => Math.max(prev - 1, 0)); + break; + case 'Home': + e.preventDefault(); + setActiveOptionIndex(0); + break; + case 'End': + e.preventDefault(); + setActiveOptionIndex(Math.max(filteredOptions.length - 1, 0)); + break; + case 'Enter': + e.preventDefault(); + if ( + activeOptionIndex >= 0 && + !filteredOptions[activeOptionIndex].disabled + ) { + handleValueChange(filteredOptions[activeOptionIndex].value); + } + break; + case 'Escape': + // The Menu component should handle this + wasEscapeKey = true; + break; + default: + break; + } + + if (!wasEscapeKey) { + // Stop propagation so Menu components don't prevent the input from updating + e.stopPropagation(); + } + }, + [filteredOptions, activeOptionIndex, handleValueChange], + ); + + return ( +
    + +
    + {filteredOptions.length === 0 ? ( +
    No results found
    + ) : ( + filteredOptions.map((option, index) => ( + + )) + )} +
    +
    + ); + }, +); +Combobox.displayName = 'Combobox'; diff --git a/packages/ui/src/components/Menu/Menu.stories.tsx b/packages/ui/src/components/Menu/Menu.stories.tsx new file mode 100644 index 0000000000..7f491452a2 --- /dev/null +++ b/packages/ui/src/components/Menu/Menu.stories.tsx @@ -0,0 +1,231 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { Menu } from './Menu'; +import { Text, Icon, Button, Flex } from '../../index'; +import { useState } from 'react'; + +const meta = { + title: 'Backstage UI/Menu', + component: Menu.Root, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const options = [ + { label: 'Apple', value: 'apple' }, + { label: 'Banana', value: 'banana' }, + { label: 'Blueberry', value: 'blueberry' }, + { label: 'Cherry', value: 'cherry' }, + { label: 'Durian', value: 'durian' }, + { label: 'Elderberry', value: 'elderberry' }, + { label: 'Fig', value: 'fig' }, + { label: 'Grape', value: 'grape' }, + { label: 'Honeydew', value: 'honeydew' }, +]; + +export const Default: Story = { + args: { children: undefined }, + render: args => ( + + ( + + )} + /> + + + + Settings + Invite new members + Download app + Log out + + + + + ), +}; + +export const Open: Story = { + args: { + ...Default.args, + open: true, + }, + render: Default.render, +}; + +export const OpenOnHover: Story = { + args: { + ...Default.args, + openOnHover: true, + }, + render: Default.render, +}; + +export const Submenu: Story = { + args: { children: undefined }, + render: args => ( + + ( + + )} + /> + + + + Settings + Invite new members + Download app + Log out + + Submenu + + + + Submenu Item 1 + Submenu Item 2 + Submenu Item 3 + + + + + + + + + ), +}; + +export const SubmenuCombobox = () => { + const [selectedValues, setSelectedValues] = useState([]); + + return ( + + + {selectedValues.length === 0 + ? 'Which is your favorite fruit?' + : `Yum, ${selectedValues[0]} is delicious!`} + + + ( + + )} + /> + + + + Regular Item + + Fruits + + + + + + + + + Another Item + + + + + + ); +}; + +export const SubmenuComboboxMultiselect = () => { + const [selectedValues, setSelectedValues] = useState([]); + + return ( + + + {selectedValues.length === 0 + ? 'Tell us what fruits you like.' + : `${selectedValues.join( + ', ', + )} would make for a great, healthy smoothy!`} + + + ( + + )} + /> + + + + Regular Item + + Fruits + + + + + + + + + Another Item + + + + + + ); +}; diff --git a/packages/ui/src/components/Menu/Menu.styles.css b/packages/ui/src/components/Menu/Menu.styles.css new file mode 100644 index 0000000000..fe1275cb56 --- /dev/null +++ b/packages/ui/src/components/Menu/Menu.styles.css @@ -0,0 +1,182 @@ +.bui-MenuPositioner { + outline: 0; + z-index: 100; +} + +.bui-MenuPopup { + display: flex; + flex-direction: column; + border-radius: 0.375rem; + background-color: var(--bui-bg-surface-1); + border: 1px solid var(--bui-border); + color: var(--bui-fg-primary); + outline: none; + overflow: auto; + transform-origin: var(--transform-origin); + max-width: min(var(--available-width), 340px); + max-height: min(var(--available-height), 500px); + transition: transform 150ms, opacity 150ms; + position: relative; + padding-bottom: var(--bui-space-1); + + &[data-starting-style], + &[data-ending-style] { + opacity: 0; + transform: scale(0.9); + } +} + +.bui-MenuItem { + outline: 0; + user-select: none; + display: flex; + height: 32px; + align-items: center; + gap: var(--bui-space-2); + color: var(--bui-fg-primary); + text-decoration: none; + border-radius: var(--bui-radius-2); + margin-inline: var(--bui-space-1); + padding-inline: var(--bui-space-2); + font-size: var(--bui-font-size-3); + cursor: pointer; + flex-shrink: 0; + + &:first-child { + margin-top: var(--bui-space-1); + } + + &[data-highlighted] { + background-color: var(--bui-gray-3); + } +} + +.bui-MenuSubmenuTrigger { + outline: 0; + user-select: none; + display: flex; + height: 32px; + align-items: center; + justify-content: space-between; + gap: var(--bui-space-2); + color: var(--bui-fg-primary); + text-decoration: none; + border-radius: var(--bui-radius-2); + margin-inline: var(--bui-space-1); + padding-inline: var(--bui-space-2); + font-size: var(--bui-font-size-3); + cursor: pointer; + flex-shrink: 0; + + & .bui-Icon { + color: var(--bui-fg-secondary); + } + + &:first-child { + margin-top: var(--bui-space-1); + } + + &[data-popup-open], + &[data-highlighted] { + background-color: var(--bui-gray-3); + + .bui-Icon { + color: var(--bui-fg-primary); + } + } +} + +.bui-MenuSeparator { + margin: 0.375rem 1rem; + height: 1px; + background-color: var(--color-gray-200); +} + +.bui-SubmenuComboboxSearch { + padding-inline: var(--bui-space-3); + width: 100%; + height: 32px; + border: none; + border-bottom: 1px solid var(--bui-border); + background-color: var(--bui-bg-surface-1); + color: var(--bui-fg-primary); + line-height: 140%; + outline: none; + font-size: var(--bui-font-size-3); + position: sticky; + top: 0; + z-index: 1; + + &::placeholder { + color: var(--bui-fg-secondary); + } + + &:disabled { + opacity: 0.6; + cursor: not-allowed; + } +} + +.bui-SubmenuComboboxItems { + overflow-y: auto; + display: flex; + flex-direction: column; + padding-top: var(--bui-space-2); + outline: none; +} + +.bui-SubmenuComboboxNoResults { + padding-inline: var(--bui-space-3); + padding-top: var(--bui-space-2); + padding-bottom: var(--bui-space-4); + color: var(--bui-fg-secondary); + font-size: var(--bui-font-size-3); +} + +.bui-SubmenuComboboxItem { + outline: 0; + user-select: none; + display: flex; + height: 32px; + align-items: center; + justify-content: space-between; + gap: var(--bui-space-2); + color: var(--bui-fg-primary); + text-decoration: none; + border-radius: var(--bui-radius-2); + margin-inline: var(--bui-space-1); + padding-inline: var(--bui-space-2); + font-size: var(--bui-font-size-3); + cursor: pointer; + user-select: none; + flex-shrink: 0; + + &[data-highlighted] { + background-color: var(--bui-gray-3); + } + + &[data-disabled] { + opacity: 0.5; + cursor: not-allowed; + } +} + +.bui-SubmenuComboboxItemCheckbox { + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + color: var(--bui-fg-primary); + flex-shrink: 0; + border-radius: var(--bui-radius-2); + border: 1px solid var(--bui-border); + background: var(--bui-bg-surface-1); +} + +.bui-SubmenuComboboxItemLabel { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/packages/canon/src/components/Menu/Menu.tsx b/packages/ui/src/components/Menu/Menu.tsx similarity index 55% rename from packages/canon/src/components/Menu/Menu.tsx rename to packages/ui/src/components/Menu/Menu.tsx index 6b93d14865..9c475fdf5f 100644 --- a/packages/canon/src/components/Menu/Menu.tsx +++ b/packages/ui/src/components/Menu/Menu.tsx @@ -18,187 +18,257 @@ import { forwardRef } from 'react'; import { Menu as MenuPrimitive } from '@base-ui-components/react/menu'; import clsx from 'clsx'; import { MenuComponent } from './types'; +import { Combobox } from './Combobox'; +import { Icon } from '../Icon'; +import { useStyles } from '../../hooks/useStyles'; const MenuTrigger = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('Menu'); + + return ( + + ); +}); MenuTrigger.displayName = MenuPrimitive.Trigger.displayName; const MenuBackdrop = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('Menu'); + + return ( + + ); +}); MenuBackdrop.displayName = MenuPrimitive.Backdrop.displayName; const MenuPositioner = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('Menu'); + + return ( + + ); +}); MenuPositioner.displayName = MenuPrimitive.Positioner.displayName; const MenuPopup = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('Menu'); + + return ( + + ); +}); MenuPopup.displayName = MenuPrimitive.Popup.displayName; const MenuArrow = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('Menu'); + + return ( + + ); +}); MenuArrow.displayName = MenuPrimitive.Arrow.displayName; const MenuItem = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('Menu'); + + return ( + + ); +}); MenuItem.displayName = MenuPrimitive.Item.displayName; const MenuGroup = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('Menu'); + + return ( + + ); +}); MenuGroup.displayName = MenuPrimitive.Group.displayName; const MenuGroupLabel = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('Menu'); + + return ( + + ); +}); MenuGroupLabel.displayName = MenuPrimitive.GroupLabel.displayName; const MenuRadioGroup = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('Menu'); + + return ( + + ); +}); MenuRadioGroup.displayName = MenuPrimitive.RadioGroup.displayName; const MenuRadioItem = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('Menu'); + + return ( + + ); +}); MenuRadioItem.displayName = MenuPrimitive.RadioItem.displayName; const MenuRadioItemIndicator = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('Menu'); + + return ( + + ); +}); MenuRadioItemIndicator.displayName = MenuPrimitive.RadioItemIndicator.displayName; const MenuCheckboxItem = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('Menu'); + + return ( + + ); +}); MenuCheckboxItem.displayName = MenuPrimitive.CheckboxItem.displayName; const MenuCheckboxItemIndicator = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('Menu'); + + return ( + + ); +}); MenuCheckboxItemIndicator.displayName = MenuPrimitive.CheckboxItemIndicator.displayName; const MenuSubmenuTrigger = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, children, ...props }, ref) => { + const { classNames } = useStyles('Menu'); + + return ( + +
    {children}
    + +
    + ); +}); MenuSubmenuTrigger.displayName = MenuPrimitive.SubmenuTrigger.displayName; const MenuSeparator = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('Menu'); + + return ( + + ); +}); MenuSeparator.displayName = MenuPrimitive.Separator.displayName; /** @public */ @@ -220,4 +290,5 @@ export const Menu: MenuComponent = { CheckboxItemIndicator: MenuCheckboxItemIndicator, SubmenuTrigger: MenuSubmenuTrigger, Separator: MenuSeparator, + Combobox, }; diff --git a/packages/canon/src/components/Menu/index.ts b/packages/ui/src/components/Menu/index.ts similarity index 100% rename from packages/canon/src/components/Menu/index.ts rename to packages/ui/src/components/Menu/index.ts diff --git a/packages/canon/src/components/Menu/types.ts b/packages/ui/src/components/Menu/types.ts similarity index 73% rename from packages/canon/src/components/Menu/types.ts rename to packages/ui/src/components/Menu/types.ts index 63f8069f61..9071f9a921 100644 --- a/packages/canon/src/components/Menu/types.ts +++ b/packages/ui/src/components/Menu/types.ts @@ -13,7 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { Menu as MenuPrimitive } from '@base-ui-components/react/menu'; +import { + ForwardRefExoticComponent, + RefAttributes, + ComponentProps, +} from 'react'; + +/** @public */ +export type MenuComboboxOption = { + label: string; + value: string; + disabled?: boolean; +}; + +/** @public */ +export interface MenuComboboxProps extends ComponentProps<'div'> { + options: MenuComboboxOption[]; + value?: string[]; + onValueChange?: (value: string[]) => void; + multiselect?: boolean; + closeParentOnEsc?: boolean; +} /** @public */ export type MenuComponent = { @@ -34,4 +56,7 @@ export type MenuComponent = { CheckboxItemIndicator: typeof MenuPrimitive.CheckboxItemIndicator; SubmenuTrigger: typeof MenuPrimitive.SubmenuTrigger; Separator: typeof MenuPrimitive.Separator; + Combobox: ForwardRefExoticComponent< + MenuComboboxProps & RefAttributes + >; }; diff --git a/packages/ui/src/components/Popover/Popover.styles.css b/packages/ui/src/components/Popover/Popover.styles.css new file mode 100644 index 0000000000..23ae5c1019 --- /dev/null +++ b/packages/ui/src/components/Popover/Popover.styles.css @@ -0,0 +1,25 @@ +/* + * 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. + */ + +.bui-Popover { + margin-right: 12px; + overflow: scroll; + background-color: var(--bui-bg-surface-1); + border: 1px solid var(--bui-border); + border-radius: var(--bui-radius-3); + padding-block: var(--bui-space-1); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} diff --git a/packages/ui/src/components/RadioGroup/RadioGroup.stories.tsx b/packages/ui/src/components/RadioGroup/RadioGroup.stories.tsx new file mode 100644 index 0000000000..b9c4c26f08 --- /dev/null +++ b/packages/ui/src/components/RadioGroup/RadioGroup.stories.tsx @@ -0,0 +1,147 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { RadioGroup, Radio } from './RadioGroup'; + +const meta = { + title: 'Backstage UI/RadioGroup', + component: RadioGroup, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + label: 'What is your favorite pokemon?', + }, + render: args => ( + + Bulbasaur + Charmander + Squirtle + + ), +}; + +export const Horizontal: Story = { + args: { + ...Default.args, + orientation: 'horizontal', + }, + render: args => ( + + Bulbasaur + Charmander + Squirtle + + ), +}; + +export const Disabled: Story = { + args: { + ...Default.args, + isDisabled: true, + }, + render: args => ( + + Bulbasaur + Charmander + Squirtle + + ), +}; + +export const DisabledSingle: Story = { + args: { + ...Default.args, + }, + render: args => ( + + Bulbasaur + + Charmander + + Squirtle + + ), +}; + +export const DisabledAndSelected: Story = { + args: { + ...Default.args, + value: 'charmander', + }, + render: args => ( + + Bulbasaur + + Charmander + + Squirtle + + ), +}; + +export const Invalid: Story = { + args: { + ...Default.args, + name: 'pokemon', + isInvalid: true, + }, + render: args => ( + + Bulbasaur + + Charmander + + Squirtle + + ), +}; + +export const Validation: Story = { + args: { + ...Default.args, + name: 'pokemon', + defaultValue: 'charmander', + validationBehavior: 'aria', + validate: value => (value === 'charmander' ? 'Nice try!' : null), + }, + render: args => ( + + Bulbasaur + Charmander + Squirtle + + ), +}; + +export const ReadOnly: Story = { + args: { + ...Default.args, + isReadOnly: true, + defaultValue: 'charmander', + }, + render: args => ( + + Bulbasaur + Charmander + Squirtle + + ), +}; diff --git a/packages/ui/src/components/RadioGroup/RadioGroup.styles.css b/packages/ui/src/components/RadioGroup/RadioGroup.styles.css new file mode 100644 index 0000000000..f12d5571e0 --- /dev/null +++ b/packages/ui/src/components/RadioGroup/RadioGroup.styles.css @@ -0,0 +1,95 @@ +.bui-RadioGroup { + display: flex; + flex-direction: column; + color: var(--bui-fg-primary); +} + +.bui-RadioGroup[data-orientation='horizontal'] .bui-RadioGroupContent { + flex-direction: row; + gap: var(--bui-space-4); +} + +.bui-RadioGroupContent { + display: flex; + flex-direction: column; + gap: var(--bui-space-2); +} + +.bui-Radio { + display: flex; + /* This is needed so the HiddenInput is positioned correctly */ + position: relative; + align-items: center; + gap: var(--bui-space-2); + font-size: var(--bui-font-size-2); + color: var(--bui-fg-primary); + forced-color-adjust: none; + + &:before { + content: ''; + display: block; + width: 1rem; + height: 1rem; + box-sizing: border-box; + border: 0.125rem solid var(--bui-border); + background: var(--bui-gray-1); + border-radius: var(--bui-radius-full); + transition: all 200ms; + } + + &[data-pressed]:before { + border-color: var(--bui-border); + } + + &[data-selected] { + &:before { + border-color: var(--bui-bg-solid); + border-width: 0.25rem; + } + + &[data-pressed]:before { + border-color: var(--bui-bg-solid); + } + } + + &[data-focus-visible]:before { + outline: 2px solid var(--bui-ring); + outline-offset: 2px; + } + + &[data-disabled] { + cursor: not-allowed; + color: var(--bui-fg-disabled); + + &:before { + border-color: var(--bui-border-disabled); + background: var(--bui-bg-disabled); + } + + &[data-selected]:before { + border-color: var(--bui-border-disabled); + } + } + + &[data-invalid]:before { + border-color: var(--bui-border-danger); + } + + &[data-invalid][data-selected]:before { + border-color: var(--bui-border-danger); + } + + /* Ensure disabled state prevails over invalid state */ + &[data-disabled][data-invalid] { + color: var(--bui-fg-disabled); + + &:before { + border-color: var(--bui-border-disabled); + background: var(--bui-bg-disabled); + } + + &[data-selected]:before { + border-color: var(--bui-border-disabled); + } + } +} diff --git a/packages/ui/src/components/RadioGroup/RadioGroup.tsx b/packages/ui/src/components/RadioGroup/RadioGroup.tsx new file mode 100644 index 0000000000..4363477c1a --- /dev/null +++ b/packages/ui/src/components/RadioGroup/RadioGroup.tsx @@ -0,0 +1,95 @@ +/* + * 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 { forwardRef, useEffect } from 'react'; +import { + RadioGroup as AriaRadioGroup, + Radio as AriaRadio, +} from 'react-aria-components'; +import clsx from 'clsx'; +import { FieldLabel } from '../FieldLabel'; +import { FieldError } from '../FieldError'; +import { useStyles } from '../../hooks/useStyles'; + +import type { RadioGroupProps, RadioProps } from './types'; + +/** @public */ +export const RadioGroup = forwardRef( + (props, ref) => { + const { + className, + label, + secondaryLabel, + description, + isRequired, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledBy, + children, + ...rest + } = props; + + const { classNames } = useStyles('RadioGroup'); + + useEffect(() => { + if (!label && !ariaLabel && !ariaLabelledBy) { + console.warn( + 'RadioGroup requires either a visible label, aria-label, or aria-labelledby for accessibility', + ); + } + }, [label, ariaLabel, ariaLabelledBy]); + + // If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required. + const secondaryLabelText = + secondaryLabel || (isRequired ? 'Required' : null); + + return ( + + +
    {children}
    + +
    + ); + }, +); + +RadioGroup.displayName = 'RadioGroup'; + +/** @public */ +export const Radio = forwardRef((props, ref) => { + const { className, ...rest } = props; + + const { classNames } = useStyles('RadioGroup'); + + return ( + + ); +}); + +RadioGroup.displayName = 'RadioGroup'; diff --git a/packages/ui/src/components/RadioGroup/index.ts b/packages/ui/src/components/RadioGroup/index.ts new file mode 100644 index 0000000000..fd9572f667 --- /dev/null +++ b/packages/ui/src/components/RadioGroup/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './RadioGroup'; +export * from './types'; diff --git a/packages/ui/src/components/RadioGroup/types.ts b/packages/ui/src/components/RadioGroup/types.ts new file mode 100644 index 0000000000..029ef4baf9 --- /dev/null +++ b/packages/ui/src/components/RadioGroup/types.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { + RadioGroupProps as AriaRadioGroupProps, + RadioProps as AriaRadioProps, +} from 'react-aria-components'; +import type { FieldLabelProps } from '../FieldLabel/types'; +import { ReactNode } from 'react'; + +/** @public */ +export interface RadioGroupProps + extends Omit, + Omit { + children?: ReactNode; +} + +/** @public */ +export interface RadioProps extends AriaRadioProps {} diff --git a/packages/canon/src/components/ScrollArea/ScrollArea.stories.tsx b/packages/ui/src/components/ScrollArea/ScrollArea.stories.tsx similarity index 96% rename from packages/canon/src/components/ScrollArea/ScrollArea.stories.tsx rename to packages/ui/src/components/ScrollArea/ScrollArea.stories.tsx index 25a3d5f3d8..95a7fc3329 100644 --- a/packages/canon/src/components/ScrollArea/ScrollArea.stories.tsx +++ b/packages/ui/src/components/ScrollArea/ScrollArea.stories.tsx @@ -19,7 +19,7 @@ import { ScrollArea } from './ScrollArea'; import { Text } from '../Text/Text'; const meta = { - title: 'Components/ScrollArea', + title: 'Backstage UI/ScrollArea', component: ScrollArea.Root, } satisfies Meta; @@ -30,7 +30,7 @@ export const Default: Story = { render: () => (
    diff --git a/packages/canon/src/components/ScrollArea/ScrollArea.styles.css b/packages/ui/src/components/ScrollArea/ScrollArea.styles.css similarity index 80% rename from packages/canon/src/components/ScrollArea/ScrollArea.styles.css rename to packages/ui/src/components/ScrollArea/ScrollArea.styles.css index 1d1c25e9d0..209168c329 100644 --- a/packages/canon/src/components/ScrollArea/ScrollArea.styles.css +++ b/packages/ui/src/components/ScrollArea/ScrollArea.styles.css @@ -14,19 +14,17 @@ * limitations under the License. */ -.canon-ScrollAreaRoot { +.bui-ScrollAreaRoot { box-sizing: border-box; - width: 24rem; - height: 8.5rem; - max-width: calc(100vw - 8rem); + width: 100%; } -.canon-ScrollAreaViewport { +.bui-ScrollAreaViewport { height: 100%; overscroll-behavior: contain; } -.canon-ScrollAreaContent { +.bui-ScrollAreaContent { display: flex; flex-direction: column; gap: 1rem; @@ -35,10 +33,10 @@ padding-right: 1.5rem; } -.canon-ScrollAreaScrollbar { +.bui-ScrollAreaScrollbar { display: flex; justify-content: center; - background-color: var(--canon-scrollbar); + background-color: var(--bui-scrollbar); width: 0.25rem; border-radius: 0.375rem; margin: 0.5rem; @@ -60,8 +58,8 @@ } } -.canon-ScrollAreaThumb { +.bui-ScrollAreaThumb { width: 100%; border-radius: inherit; - background-color: var(--canon-scrollbar-thumb); + background-color: var(--bui-scrollbar-thumb); } diff --git a/packages/canon/src/components/ScrollArea/ScrollArea.tsx b/packages/ui/src/components/ScrollArea/ScrollArea.tsx similarity index 65% rename from packages/canon/src/components/ScrollArea/ScrollArea.tsx rename to packages/ui/src/components/ScrollArea/ScrollArea.tsx index e96e76c29b..44722fbd12 100644 --- a/packages/canon/src/components/ScrollArea/ScrollArea.tsx +++ b/packages/ui/src/components/ScrollArea/ScrollArea.tsx @@ -17,53 +17,70 @@ import { forwardRef } from 'react'; import { ScrollArea as ScrollAreaPrimitive } from '@base-ui-components/react/scroll-area'; import clsx from 'clsx'; +import { useStyles } from '../../hooks/useStyles'; const ScrollAreaRoot = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('ScrollArea'); + + return ( + + ); +}); ScrollAreaRoot.displayName = ScrollAreaPrimitive.Root.displayName; const ScrollAreaViewport = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('ScrollArea'); + + return ( + + ); +}); ScrollAreaViewport.displayName = ScrollAreaPrimitive.Viewport.displayName; const ScrollAreaScrollbar = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('ScrollArea'); + + return ( + + ); +}); ScrollAreaScrollbar.displayName = ScrollAreaPrimitive.Scrollbar.displayName; const ScrollAreaThumb = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => { + const { classNames } = useStyles('ScrollArea'); + + return ( + + ); +}); ScrollAreaThumb.displayName = ScrollAreaPrimitive.Thumb.displayName; /** @public */ diff --git a/packages/canon/src/components/ScrollArea/index.ts b/packages/ui/src/components/ScrollArea/index.ts similarity index 100% rename from packages/canon/src/components/ScrollArea/index.ts rename to packages/ui/src/components/ScrollArea/index.ts diff --git a/packages/ui/src/components/SearchField/SearchField.stories.tsx b/packages/ui/src/components/SearchField/SearchField.stories.tsx new file mode 100644 index 0000000000..c8867215a1 --- /dev/null +++ b/packages/ui/src/components/SearchField/SearchField.stories.tsx @@ -0,0 +1,191 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { SearchField } from './SearchField'; +import { Form } from 'react-aria-components'; +import { Icon } from '../Icon'; +import { Flex } from '../Flex'; +import { FieldLabel } from '../FieldLabel'; +import { ButtonIcon } from '../ButtonIcon'; +import { RiCactusLine } from '@remixicon/react'; +import { Button } from '../Button'; + +const meta = { + title: 'Backstage UI/SearchField', + component: SearchField, + argTypes: { + isRequired: { + control: 'boolean', + }, + icon: { + control: 'object', + }, + placeholder: { + control: 'text', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + name: 'url', + style: { + maxWidth: '300px', + }, + }, +}; + +export const Sizes: Story = { + args: { + ...Default.args, + }, + render: args => ( + + + + + ), +}; + +export const DefaultValue: Story = { + args: { + ...Default.args, + defaultValue: 'https://example.com', + }, +}; + +export const WithLabel: Story = { + args: { + ...Default.args, + label: 'Label', + }, +}; + +export const WithDescription: Story = { + args: { + ...WithLabel.args, + description: 'Description', + }, +}; + +export const Required: Story = { + args: { + ...WithLabel.args, + isRequired: true, + }, +}; + +export const Disabled: Story = { + args: { + ...Default.args, + isDisabled: true, + }, +}; + +export const WithIcon: Story = { + args: { + ...Default.args, + }, + render: args => ( + } + /> + ), +}; + +export const DisabledWithIcon: Story = { + args: { + ...WithIcon.args, + isDisabled: true, + }, +}; + +export const ShowError: Story = { + args: { + ...WithLabel.args, + }, + render: args => ( +
    + + + ), +}; + +export const Validation: Story = { + args: { + ...WithLabel.args, + validate: value => (value === 'admin' ? 'Nice try!' : null), + }, +}; + +export const CustomField: Story = { + render: () => ( + <> + + + + ), +}; + +export const StartCollapsed: Story = { + args: { + ...Default.args, + startCollapsed: true, + }, + + render: args => ( + + + + + ), +}; + +export const StartCollapsedWithButtons: Story = { + args: { + ...StartCollapsed.args, + }, + render: args => ( + + + } size="small" variant="secondary" /> + + + } size="medium" variant="secondary" /> + + + ), +}; diff --git a/packages/ui/src/components/SearchField/SearchField.styles.css b/packages/ui/src/components/SearchField/SearchField.styles.css new file mode 100644 index 0000000000..062757f38f --- /dev/null +++ b/packages/ui/src/components/SearchField/SearchField.styles.css @@ -0,0 +1,102 @@ +/* + * 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. + */ + +.bui-SearchField { + &[data-empty] { + .bui-InputClear { + display: none; + } + } + + &[data-start-collapsed='true'] { + transition: width 0.3s ease-in-out; + padding: 0; + + &[data-collapsed='false'] { + cursor: pointer; + + &[data-size='medium'] { + width: 2.5rem; + height: 2.5rem; + } + + &[data-size='small'] { + width: 2rem; + height: 2rem; + } + + &[data-size='medium'] .bui-Input { + padding-left: 0; + + &::placeholder { + opacity: 0; + } + } + + &[data-size='small'] .bui-Input { + padding-left: 0; + + &::placeholder { + opacity: 0; + } + } + + &[data-size='small'] .bui-InputIcon { + left: var(--bui-space-2); + } + + &[data-size='medium'] .bui-InputIcon { + left: 10px; + } + } + } +} + +.bui-InputClear { + position: absolute; + right: 0; + top: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + background-color: transparent; + border: none; + padding: 0; + margin: 0; + cursor: pointer; + color: var(--bui-fg-secondary); + transition: color 0.2s ease-in-out; +} + +.bui-InputClear:hover { + color: var(--bui-fg-primary); +} + +.bui-InputClear[data-size='small'] { + width: 2rem; + height: 2rem; +} + +.bui-InputClear[data-size='medium'] { + width: 2.5rem; + height: 2.5rem; +} + +.bui-InputClear svg { + width: 1rem; + height: 1rem; +} diff --git a/packages/ui/src/components/SearchField/SearchField.tsx b/packages/ui/src/components/SearchField/SearchField.tsx new file mode 100644 index 0000000000..e497cecd49 --- /dev/null +++ b/packages/ui/src/components/SearchField/SearchField.tsx @@ -0,0 +1,146 @@ +/* + * 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 { forwardRef, useEffect, useState } from 'react'; +import { + Input, + SearchField as AriaSearchField, + Button, +} from 'react-aria-components'; +import clsx from 'clsx'; +import { FieldLabel } from '../FieldLabel'; +import { FieldError } from '../FieldError'; +import { RiSearch2Line, RiCloseCircleLine } from '@remixicon/react'; +import { useStyles } from '../../hooks/useStyles'; + +import type { SearchFieldProps } from './types'; + +/** @public */ +export const SearchField = forwardRef( + (props, ref) => { + const { + className, + icon, + size = 'small', + label, + secondaryLabel, + description, + isRequired, + placeholder = 'Search', + startCollapsed = false, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledBy, + ...rest + } = props; + + const [isCollapsed, setIsCollapsed] = useState(false); + const [shouldCollapse, setShouldCollapse] = useState(true); + + useEffect(() => { + if (!label && !ariaLabel && !ariaLabelledBy) { + console.warn( + 'SearchField requires either a visible label, aria-label, or aria-labelledby for accessibility', + ); + } + }, [label, ariaLabel, ariaLabelledBy]); + + const { classNames: textFieldClassNames, dataAttributes } = useStyles( + 'TextField', + { + size, + }, + ); + + const { classNames: searchFieldClassNames } = useStyles('SearchField', {}); + + // If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required. + const secondaryLabelText = + secondaryLabel || (isRequired ? 'Required' : null); + + const handleClick = (isFocused: boolean) => { + props.onFocusChange?.(isFocused); + if (shouldCollapse) { + if (isFocused) { + setIsCollapsed(true); + } else { + setIsCollapsed(false); + } + } + }; + + const handleChange = (value: string) => { + props.onChange?.(value); + if (value.length > 0) { + setShouldCollapse(false); + } else { + setShouldCollapse(true); + } + }; + + return ( + + +
    + {icon !== false && ( + + )} + + +
    + +
    + ); + }, +); + +SearchField.displayName = 'searchField'; diff --git a/packages/ui/src/components/SearchField/index.ts b/packages/ui/src/components/SearchField/index.ts new file mode 100644 index 0000000000..288c3fe44a --- /dev/null +++ b/packages/ui/src/components/SearchField/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './SearchField'; +export * from './types'; diff --git a/packages/ui/src/components/SearchField/types.ts b/packages/ui/src/components/SearchField/types.ts new file mode 100644 index 0000000000..5fb6552c2e --- /dev/null +++ b/packages/ui/src/components/SearchField/types.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 type { SearchFieldProps as AriaSearchFieldProps } from 'react-aria-components'; +import { ReactNode } from 'react'; +import type { Breakpoint } from '../../types'; +import type { FieldLabelProps } from '../FieldLabel/types'; + +/** @public */ +export interface SearchFieldProps + extends AriaSearchFieldProps, + Omit { + /** + * An icon to render before the input + */ + icon?: ReactNode | false; + + /** + * The size of the text field + * @defaultValue 'medium' + */ + size?: 'small' | 'medium' | Partial>; + + /** + * The placeholder text for the input + */ + placeholder?: string; + + /** + * Controls whether the SearchField starts in a collapsed state. + */ + startCollapsed?: boolean; +} diff --git a/packages/canon/src/components/Select/Select.stories.tsx b/packages/ui/src/components/Select/Select.stories.tsx similarity index 81% rename from packages/canon/src/components/Select/Select.stories.tsx rename to packages/ui/src/components/Select/Select.stories.tsx index 000ba180dc..5ca84d10f6 100644 --- a/packages/canon/src/components/Select/Select.stories.tsx +++ b/packages/ui/src/components/Select/Select.stories.tsx @@ -16,9 +16,11 @@ import type { Meta, StoryObj } from '@storybook/react'; import { Select } from './Select'; import { Flex } from '../Flex'; +import { Form } from 'react-aria-components'; +import { RiCloudLine } from '@remixicon/react'; const meta = { - title: 'Components/Select', + title: 'Backstage UI/Select', component: Select, } satisfies Meta; @@ -49,21 +51,43 @@ export const Preview: Story = { }, }; -export const WithDescription: Story = { +export const WithLabel: Story = { args: { - ...Preview.args, + ...Default.args, + label: 'Font Family', + }, +}; + +export const WithFullWidth: Story = { + args: { + ...Default.args, + label: 'Font Family', + style: { width: '100%' }, + }, +}; + +export const WithLabelAndDescription: Story = { + args: { + ...WithLabel.args, description: 'Choose a font family for your document', }, }; +export const WithIcon: Story = { + args: { + ...WithLabel.args, + }, + render: args => - } /> + + + ), }; -export const WithLongOptionNames: Story = { +export const WithLongNames: Story = { args: { label: 'Document Template', options: [ @@ -286,6 +312,19 @@ export const WithLongOptionNames: Story = { placeholder: 'Select a document template', name: 'template', style: { maxWidth: 400 }, - value: 'annual-report-2024', + defaultSelectedKey: 'annual-report-2024', }, }; + +export const WithLongNamesAndPadding: Story = { + args: { + ...WithLongNames.args, + }, + decorators: [ + (Story, { args }) => ( +
    + +
    + ), + ], +}; diff --git a/packages/ui/src/components/Select/Select.styles.css b/packages/ui/src/components/Select/Select.styles.css new file mode 100644 index 0000000000..2cbadfda32 --- /dev/null +++ b/packages/ui/src/components/Select/Select.styles.css @@ -0,0 +1,176 @@ +/* + * 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. + */ + +.bui-Select[data-invalid] { + & .bui-SelectTrigger { + border-color: var(--bui-fg-danger); + } +} + +.bui-SelectTrigger { + box-sizing: border-box; + border-radius: var(--bui-radius-3); + border: 1px solid var(--bui-border); + background-color: var(--bui-bg-surface-1); + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + gap: var(--bui-space-2); + width: 100%; + + & svg { + flex-shrink: 0; + color: var(--bui-fg-secondary); + } + + &[data-size='small'] { + height: 2rem; + padding-inline: var(--bui-space-3); + } + + &[data-size='medium'] { + height: 3rem; + padding-inline: var(--bui-space-4); + } + + &[data-size='small'] svg { + width: 1rem; + height: 1rem; + } + + &[data-size='medium'] svg { + width: 1.25rem; + height: 1.25rem; + } + + &::placeholder { + color: var(--bui-fg-secondary); + } + + &:hover { + transition: border-color 0.2s ease-in-out, outline-color 0.2s ease-in-out; + border-color: var(--bui-border-hover); + } + + &:focus-visible { + border-color: var(--bui-border-pressed); + outline: 0; + } + + &[data-invalid] { + border-color: var(--bui-fg-danger); + } + &[data-invalid]:hover { + border-width: 2px; + } + + &[data-invalid]:focus-visible { + border-width: 2px; + } + + &[disabled] { + cursor: not-allowed; + border-color: var(--bui-border-disabled); + color: var(--bui-fg-disabled); + } + + &[disabled] .bui-SelectValue { + color: var(--bui-fg-disabled); + } + + &[data-popup-open] .bui-SelectIcon { + transform: rotate(180deg); + } +} + +.bui-SelectValue { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; + font-size: var(--bui-font-size-3); + font-family: var(--bui-font-regular); + font-weight: var(--bui-font-weight-regular); + color: var(--bui-fg-primary); + text-align: left; + + & .bui-SelectItemIndicator { + display: none; + } + + &[disabled] { + color: var(--bui-fg-disabled); + } +} + +.bui-SelectItem { + position: relative; + width: var(--anchor-width); + display: grid; + grid-template-areas: 'icon text'; + grid-template-columns: 1rem 1fr; + align-items: center; + padding-block: var(--bui-space-2); + padding-left: var(--bui-space-3); + padding-right: var(--bui-space-4); + color: var(--bui-fg-primary); + border-radius: var(--bui-radius-3); + cursor: pointer; + user-select: none; + font-size: var(--bui-font-size-3); + gap: var(--bui-space-1); + outline: none; + + &[data-focused] { + z-index: 0; + position: relative; + color: var(--bui-fg-primary); + } + + &[data-focused]::before { + content: ''; + z-index: -1; + position: absolute; + inset-block: 0; + inset-inline: 0.25rem; + border-radius: 0.25rem; + background-color: var(--bui-bg-tint-hover); + } + + &[data-disabled] { + cursor: not-allowed; + color: var(--bui-fg-disabled); + } + + &[data-selected] .bui-SelectItemIndicator { + opacity: 1; + } +} + +.bui-SelectItemIndicator { + grid-area: icon; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: opacity 0.2s ease-in-out; +} + +.bui-SelectItemLabel { + flex: 1; + grid-area: text; +} diff --git a/packages/ui/src/components/Select/Select.tsx b/packages/ui/src/components/Select/Select.tsx new file mode 100644 index 0000000000..c63e8964ab --- /dev/null +++ b/packages/ui/src/components/Select/Select.tsx @@ -0,0 +1,111 @@ +/* + * 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 { forwardRef, useEffect } from 'react'; +import { + Select as AriaSelect, + SelectValue, + Button, + Popover, + ListBox, + ListBoxItem, + Text, +} from 'react-aria-components'; +import clsx from 'clsx'; +import { SelectProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; +import { FieldLabel } from '../FieldLabel'; +import { Icon } from '../Icon'; +import { FieldError } from '../FieldError'; + +/** @public */ +export const Select = forwardRef((props, ref) => { + const { + className, + label, + description, + options, + placeholder = 'Select an option', + size = 'small', + icon, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledBy, + isRequired, + secondaryLabel, + style, + ...rest + } = props; + + const { classNames: popoverClassNames } = useStyles('Popover'); + const { classNames, dataAttributes } = useStyles('Select', { + size, + }); + + useEffect(() => { + if (!label && !ariaLabel && !ariaLabelledBy) { + console.warn( + 'Select requires either a visible label, aria-label, or aria-labelledby for accessibility', + ); + } + }, [label, ariaLabel, ariaLabelledBy]); + + // If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required. + const secondaryLabelText = secondaryLabel || (isRequired ? 'Required' : null); + + return ( + + + + + + + {options?.map(option => ( + +
    + +
    + + {option.label} + +
    + ))} +
    +
    +
    + ); +}); + +Select.displayName = 'Select'; diff --git a/packages/canon/src/components/Select/index.ts b/packages/ui/src/components/Select/index.ts similarity index 100% rename from packages/canon/src/components/Select/index.ts rename to packages/ui/src/components/Select/index.ts diff --git a/packages/ui/src/components/Select/types.ts b/packages/ui/src/components/Select/types.ts new file mode 100644 index 0000000000..6aea3452cc --- /dev/null +++ b/packages/ui/src/components/Select/types.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Breakpoint } from '../..'; +import { ReactNode } from 'react'; +import type { SelectProps as AriaSelectProps } from 'react-aria-components'; +import type { FieldLabelProps } from '../FieldLabel/types'; + +/** @public */ +export interface SelectProps + extends AriaSelectProps<{ + name: string; + value: string; + }>, + Omit { + /** + * An icon to render before the input + */ + icon?: ReactNode; + + /** + * The size of the select field + * @defaultValue 'medium' + */ + size?: 'small' | 'medium' | Partial>; + + /** + * The options of the select field + */ + options?: Array<{ value: string; label: string; disabled?: boolean }>; +} diff --git a/packages/ui/src/components/Skeleton/Skeleton.stories.tsx b/packages/ui/src/components/Skeleton/Skeleton.stories.tsx new file mode 100644 index 0000000000..95f3e57547 --- /dev/null +++ b/packages/ui/src/components/Skeleton/Skeleton.stories.tsx @@ -0,0 +1,82 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { Skeleton } from './Skeleton'; +import { Flex } from '../Flex'; + +const meta = { + title: 'Backstage UI/Skeleton', + component: Skeleton, + argTypes: { + rounded: { + control: 'boolean', + }, + width: { + control: 'number', + }, + height: { + control: 'number', + }, + }, + args: { + width: 80, + height: 24, + rounded: false, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: {}, +}; + +export const Rounded: Story = { + args: { + rounded: true, + width: 48, + height: 48, + }, +}; + +export const Demo1: Story = { + render: () => ( + + + + + + + + + + + + + ), +}; + +export const Demo2: Story = { + render: () => ( + + + + + + ), +}; diff --git a/packages/ui/src/components/Skeleton/Skeleton.styles.css b/packages/ui/src/components/Skeleton/Skeleton.styles.css new file mode 100644 index 0000000000..bbbb6deaeb --- /dev/null +++ b/packages/ui/src/components/Skeleton/Skeleton.styles.css @@ -0,0 +1,25 @@ +/* + * 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. + */ + +.bui-Skeleton { + animation: var(--bui-animate-pulse); + background-color: var(--bui-bg-surface-2); + border-radius: var(--bui-radius-2); +} + +.bui-Skeleton[data-rounded='true'] { + border-radius: var(--bui-radius-full); +} diff --git a/packages/canon/src/components/Table/TableCell/TableCell.tsx b/packages/ui/src/components/Skeleton/Skeleton.tsx similarity index 59% rename from packages/canon/src/components/Table/TableCell/TableCell.tsx rename to packages/ui/src/components/Skeleton/Skeleton.tsx index a8a87ac038..772a69bc28 100644 --- a/packages/canon/src/components/Table/TableCell/TableCell.tsx +++ b/packages/ui/src/components/Skeleton/Skeleton.tsx @@ -14,16 +14,23 @@ * limitations under the License. */ -import { forwardRef } from 'react'; -import clsx from 'clsx'; +import { useStyles } from '../../hooks/useStyles'; +import { SkeletonProps } from './types'; /** @public */ -const TableCell = forwardRef< - HTMLTableCellElement, - React.TdHTMLAttributes ->(({ className, ...props }, ref) => ( - -)); -TableCell.displayName = 'TableCell'; +export const Skeleton = (props: SkeletonProps) => { + const { width = 80, height = 24, rounded = false, ...rest } = props; + const { classNames } = useStyles('Skeleton'); -export { TableCell }; + return ( +
    + ); +}; diff --git a/packages/canon/src/components/Heading/index.ts b/packages/ui/src/components/Skeleton/index.tsx similarity index 87% rename from packages/canon/src/components/Heading/index.ts rename to packages/ui/src/components/Skeleton/index.tsx index 69b7c3c1a0..f1e76b1851 100644 --- a/packages/canon/src/components/Heading/index.ts +++ b/packages/ui/src/components/Skeleton/index.tsx @@ -14,5 +14,5 @@ * limitations under the License. */ -export { Heading } from './Heading'; -export type { HeadingProps } from './types'; +export { Skeleton } from './Skeleton'; +export type { SkeletonProps } from './types'; diff --git a/packages/canon/src/components/Table/TableCellText/types.ts b/packages/ui/src/components/Skeleton/types.ts similarity index 77% rename from packages/canon/src/components/Table/TableCellText/types.ts rename to packages/ui/src/components/Skeleton/types.ts index 99a27ad248..136873ed27 100644 --- a/packages/canon/src/components/Table/TableCellText/types.ts +++ b/packages/ui/src/components/Skeleton/types.ts @@ -14,9 +14,11 @@ * limitations under the License. */ +import { ComponentProps } from 'react'; + /** @public */ -export interface TableCellTextProps - extends React.HTMLAttributes { - title: string; - description?: string; +export interface SkeletonProps extends ComponentProps<'div'> { + width?: number | string; + height?: number | string; + rounded?: boolean; } diff --git a/packages/canon/src/components/Table/TableCell/TableCell.stories.tsx b/packages/ui/src/components/Switch/Switch.stories.tsx similarity index 72% rename from packages/canon/src/components/Table/TableCell/TableCell.stories.tsx rename to packages/ui/src/components/Switch/Switch.stories.tsx index 123dca6e39..a8d12bccca 100644 --- a/packages/canon/src/components/Table/TableCell/TableCell.stories.tsx +++ b/packages/ui/src/components/Switch/Switch.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,18 +15,25 @@ */ import type { Meta, StoryObj } from '@storybook/react'; -import { TableCell } from './TableCell'; +import { Switch } from './Switch'; const meta = { - title: 'Components/Table/TableCell', - component: TableCell, -} satisfies Meta; + title: 'Backstage UI/Switch', + component: Switch, +} satisfies Meta; export default meta; type Story = StoryObj; export const Default: Story = { args: { - children: 'Hello world', + label: 'Switch', + }, +}; + +export const Disabled: Story = { + args: { + ...Default.args, + isDisabled: true, }, }; diff --git a/packages/ui/src/components/Switch/Switch.styles.css b/packages/ui/src/components/Switch/Switch.styles.css new file mode 100644 index 0000000000..ee2a24d013 --- /dev/null +++ b/packages/ui/src/components/Switch/Switch.styles.css @@ -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. + */ + +.bui-Switch { + display: flex; + /* This is needed so the HiddenInput is positioned correctly */ + position: relative; + align-items: center; + gap: var(--bui-space-3); + font-size: var(--bui-font-size-3); + color: var(--bui-fg-primary); + cursor: pointer; + + &[data-pressed] .bui-SwitchIndicator { + &:before { + background: var(--bui-fg-solid); + } + } + + &[data-selected] { + .bui-SwitchIndicator { + background: var(--bui-bg-solid); + + &:before { + background: var(--bui-fg-solid); + transform: translateX(100%); + } + } + + &[data-pressed] { + .indicator { + background: var(--bui-gray-3); + } + } + } + + &[data-focus-visible] .bui-SwitchIndicator { + outline: 2px solid; + outline-offset: 2px; + } +} + +.bui-SwitchIndicator { + width: 2rem; + height: 1.143rem; + border: 2px; + background: var(--bui-gray-3); + border-radius: 1.143rem; + transition: all 200ms; + + &:before { + content: ''; + display: block; + margin: 0.143rem; + width: 0.857rem; + height: 0.857rem; + background: var(--bui-fg-solid); + border-radius: 16px; + transition: all 200ms; + } +} diff --git a/packages/ui/src/components/Switch/Switch.tsx b/packages/ui/src/components/Switch/Switch.tsx new file mode 100644 index 0000000000..70890ca3a0 --- /dev/null +++ b/packages/ui/src/components/Switch/Switch.tsx @@ -0,0 +1,36 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { forwardRef } from 'react'; +import { Switch as AriaSwitch } from 'react-aria-components'; +import type { SwitchProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; + +/** @public */ +export const Switch = forwardRef( + ({ label, ...props }, ref) => { + const { classNames } = useStyles('Switch'); + + return ( + +
    + {label} + + ); + }, +); + +Switch.displayName = 'Switch'; diff --git a/packages/ui/src/components/Switch/index.tsx b/packages/ui/src/components/Switch/index.tsx new file mode 100644 index 0000000000..171969809d --- /dev/null +++ b/packages/ui/src/components/Switch/index.tsx @@ -0,0 +1,18 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './Switch'; +export * from './types'; diff --git a/packages/canon/src/components/DataTable/Root/types.ts b/packages/ui/src/components/Switch/types.ts similarity index 77% rename from packages/canon/src/components/DataTable/Root/types.ts rename to packages/ui/src/components/Switch/types.ts index 0a14f81fb3..725671edd5 100644 --- a/packages/canon/src/components/DataTable/Root/types.ts +++ b/packages/ui/src/components/Switch/types.ts @@ -14,13 +14,12 @@ * limitations under the License. */ -import { Table } from '@tanstack/react-table'; +import type { SwitchProps as AriaSwitchProps } from 'react-aria-components'; /** @public */ -export interface DataTableRootProps - extends React.HTMLAttributes { +export interface SwitchProps extends AriaSwitchProps { /** - * The table instance. + * The label of the switch */ - table: Table; + label?: string; } diff --git a/packages/ui/src/components/Table/Table.stories.tsx b/packages/ui/src/components/Table/Table.stories.tsx new file mode 100644 index 0000000000..903caba25d --- /dev/null +++ b/packages/ui/src/components/Table/Table.stories.tsx @@ -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 { useState } from 'react'; +import type { Meta, StoryFn, StoryObj } from '@storybook/react'; +import { + Table, + TableHeader, + Column, + TableBody, + Row, + Cell, + CellProfile as CellProfileBUI, + useTable, +} from '.'; +import { MemoryRouter } from 'react-router-dom'; +import { data as data1Raw } from './mocked-data1'; +import { data as data2 } from './mocked-data2'; +import { data as data3 } from './mocked-data3'; +import { data as data4 } from './mocked-data4'; +import { RiCactusLine } from '@remixicon/react'; +import { TablePagination } from '../TablePagination'; + +const meta = { + title: 'Backstage UI/Table', + decorators: [ + (Story: StoryFn) => ( + + + + ), + ], +} satisfies Meta; + +// Added this fix to fix Chromatic timeout error. This bug is due to rerendering the table with too many rows. +// Work in progress to fix it here - https://github.com/backstage/backstage/pull/30687 +const data1 = data1Raw.slice(0, 10); + +export default meta; +type Story = StoryObj; + +export const TableOnly: Story = { + render: () => { + return ( + + + Name + Owner + Type + Lifecycle + + + {data1.map(item => ( + + } + description={item.description} + /> + + + + + ))} + +
    + ); + }, +}; + +export const WithPaginationUncontrolled: Story = { + render: () => { + const { data, paginationProps } = useTable({ data: data1 }); + + return ( + <> + + + Name + Owner + Type + Lifecycle + + + {data?.map(item => ( + + } + description={item.description} + /> + + + + + ))} + +
    + + + ); + }, +}; + +export const WithPaginationControlled: Story = { + render: () => { + const [offset, setOffset] = useState(0); + const [pageSize, setPageSize] = useState(5); + + const { data, paginationProps } = useTable({ + data: data4, + pagination: { + offset, + pageSize, + onOffsetChange: setOffset, + onPageSizeChange: setPageSize, + onNextPage: () => console.log('Next page analytics'), + onPreviousPage: () => console.log('Previous page analytics'), + }, + }); + + return ( + <> + + + Band name + Genre + Year formed + Albums + + + {data?.map(item => ( + + + + + + + ))} + +
    + +
    + Current state: offset={offset}, pageSize={pageSize} +
    + + ); + }, +}; + +export const TableRockBand: Story = { + render: () => { + const { data, paginationProps } = useTable({ + data: data4, + pagination: { + defaultPageSize: 5, + }, + }); + + return ( + <> + + + Band name + Genre + Year formed + Albums + + + {data?.map(item => ( + + + + + + + ))} + +
    + + + ); + }, +}; + +export const RowClick: Story = { + render: () => { + const { data, paginationProps } = useTable({ + data: data4, + pagination: { + defaultPageSize: 5, + }, + }); + + return ( + <> + + + Band name + Genre + Year formed + Albums + + + {data?.map(item => ( + alert('Row clicked')}> + + + + + + ))} + +
    + + + ); + }, +}; + +export const RowLink: Story = { + render: () => { + const { data, paginationProps } = useTable({ + data: data4, + pagination: { + defaultPageSize: 5, + }, + }); + + return ( + <> + + + Band name + Genre + Year formed + Albums + + + {data?.map(item => ( + + + + + + + ))} + +
    + + + ); + }, +}; + +export const CellText: Story = { + render: () => { + return ( + + + Name + + + {data2.map(item => ( + + + + ))} + +
    + ); + }, +}; + +export const CellProfile: Story = { + render: () => { + return ( + + + Name + + + {data3.map(item => ( + + + + ))} + +
    + ); + }, +}; diff --git a/packages/ui/src/components/Table/Table.styles.css b/packages/ui/src/components/Table/Table.styles.css new file mode 100644 index 0000000000..84d417604a --- /dev/null +++ b/packages/ui/src/components/Table/Table.styles.css @@ -0,0 +1,149 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.bui-Table { + width: 100%; + caption-side: bottom; + border-collapse: collapse; +} + +.bui-TableHeader { + border-bottom: 1px solid var(--bui-border); + transition: color 0.2s ease-in-out; +} + +.bui-TableHead { + text-align: left; + padding: var(--bui-space-3); + font-size: var(--bui-font-size-3); + color: var(--bui-fg-primary); +} + +.bui-TableHeadSortButton { + cursor: pointer; + user-select: none; + display: inline-flex; + align-items: center; + gap: var(--bui-space-1); + + &:hover svg { + opacity: 0.5; + } + + & svg { + opacity: 0; + transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out; + } + + &[data-sort-order='asc'] svg { + opacity: 1; + transform: rotate(0); + } + + &[data-sort-order='desc'] svg { + opacity: 1; + transform: rotate(180deg); + } +} + +.bui-TableBody { + color: var(--bui-fg-primary); +} + +.bui-TableRow { + border-bottom: 1px solid var(--bui-border); + transition: color 0.2s ease-in-out; + + &[data-react-aria-pressable='true'] { + cursor: pointer; + } +} + +.bui-TableBody .bui-TableRow:hover { + background-color: var(--bui-gray-2); +} + +.bui-TableCell { + padding: var(--bui-space-3); + font-size: var(--bui-font-size-3); +} + +.bui-TableCell { + padding: var(--bui-space-3); + font-size: var(--bui-font-size-3); +} + +.bui-TableCellContentWrapper { + display: inline-flex; + flex-direction: row; + align-items: center; + gap: var(--bui-space-2); +} + +.bui-TableCellIcon, +.bui-TableCellIcon svg { + display: inline-flex; + align-items: center; + color: var(--bui-fg-primary); +} + +.bui-TableCellContent { + display: flex; + flex-direction: column; + gap: var(--bui-space-0_5); +} + +.bui-TableCellProfile { + display: flex; + flex-direction: row; + gap: var(--bui-space-2); + align-items: center; +} + +.bui-TableCellProfileAvatar { + display: inline-flex; + justify-content: center; + align-items: center; + vertical-align: middle; + border-radius: 100%; + user-select: none; + font-weight: 500; + color: var(--bui-fg-primary); + background-color: var(--bui-bg-surface-2); + font-size: 1rem; + line-height: 1; + overflow: hidden; + height: 1.25rem; + width: 1.25rem; +} + +.bui-TableCellProfileAvatarImage { + object-fit: cover; + height: 100%; + width: 100%; +} + +.bui-TableCellProfileAvatarFallback { + align-items: center; + display: flex; + justify-content: center; + height: 100%; + width: 100%; + font-size: var(--bui-font-size-2); + font-weight: var(--bui-font-weight-regular); + box-shadow: inset 0 0 0 1px var(--bui-border); + border-radius: var(--bui-radius-full); +} diff --git a/packages/ui/src/components/Table/components/Cell.tsx b/packages/ui/src/components/Table/components/Cell.tsx new file mode 100644 index 0000000000..d511ed21cb --- /dev/null +++ b/packages/ui/src/components/Table/components/Cell.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import clsx from 'clsx'; +import { Text } from '../../Text'; +import { Link } from '../../Link'; +import { Cell as ReactAriaCell } from 'react-aria-components'; +import type { CellProps } from '../types'; +import { useStyles } from '../../../hooks/useStyles'; + +/** @public */ +const Cell = (props: CellProps) => { + const { + className, + title, + description, + color = 'primary', + leadingIcon, + href, + ...rest + } = props; + + const { classNames } = useStyles('Table'); + + return ( + +
    + {leadingIcon && ( +
    {leadingIcon}
    + )} +
    + {href ? ( + + {title} + + ) : ( + + {title} + + )} + {description && ( + + {description} + + )} +
    +
    +
    + ); +}; + +Cell.displayName = 'Cell'; + +export { Cell }; diff --git a/packages/ui/src/components/Table/components/CellProfile.tsx b/packages/ui/src/components/Table/components/CellProfile.tsx new file mode 100644 index 0000000000..4e4906046d --- /dev/null +++ b/packages/ui/src/components/Table/components/CellProfile.tsx @@ -0,0 +1,78 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import clsx from 'clsx'; +import { CellProfileProps } from '../types'; +import { Text } from '../../Text/Text'; +import { Link } from '../../Link/Link'; +import { Avatar } from '@base-ui-components/react/avatar'; +import { useStyles } from '../../../hooks/useStyles'; +import { Cell as ReactAriaCell } from 'react-aria-components'; + +/** @public */ +export const CellProfile = (props: CellProfileProps) => { + const { + className, + src, + name, + href, + description, + color = 'primary', + ...rest + } = props; + const { classNames } = useStyles('Table'); + + return ( + +
    +
    + {src && ( + + + + {(name || '') + .split(' ') + .map(word => word[0]) + .join('') + .toLocaleUpperCase('en-US') + .slice(0, 1)} + + + )} +
    +
    + {name && href ? ( + {name} + ) : ( + + {name} + + )} + {description && ( + + {description} + + )} +
    +
    +
    + ); +}; diff --git a/packages/ui/src/components/Table/components/Column.tsx b/packages/ui/src/components/Table/components/Column.tsx new file mode 100644 index 0000000000..1df279b92f --- /dev/null +++ b/packages/ui/src/components/Table/components/Column.tsx @@ -0,0 +1,48 @@ +/* + * 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 { + Column as ReactAriaColumn, + type ColumnProps, +} from 'react-aria-components'; +import { Icon } from '../../Icon'; +import { useStyles } from '../../../hooks/useStyles'; + +/** @public */ +export const Column = ( + props: Omit & { children?: React.ReactNode }, +) => { + const { classNames } = useStyles('Table'); + + return ( + + {({ allowsSorting, sortDirection }) => ( + <> + {props.children} + {allowsSorting && ( + + )} + + )} + + ); +}; diff --git a/packages/ui/src/components/Table/components/Row.tsx b/packages/ui/src/components/Table/components/Row.tsx new file mode 100644 index 0000000000..e0d5bcc34d --- /dev/null +++ b/packages/ui/src/components/Table/components/Row.tsx @@ -0,0 +1,76 @@ +/* + * 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 { + Row as ReactAriaRow, + RowProps, + useTableOptions, + Cell, + Collection, + Checkbox, + RouterProvider, +} from 'react-aria-components'; +import { useStyles } from '../../../hooks/useStyles'; +import { useNavigate } from 'react-router-dom'; +import { useHref } from 'react-router-dom'; +import { isExternalLink } from '../../../utils/isExternalLink'; + +/** @public */ +export function Row({ + id, + columns, + children, + href, + ...otherProps +}: RowProps) { + const { classNames } = useStyles('Table'); + const navigate = useNavigate(); + const isExternal = isExternalLink(href); + + let { selectionBehavior } = useTableOptions(); + + const content = ( + <> + {selectionBehavior === 'toggle' && ( + + + + )} + {children} + + ); + + if (isExternal) { + return ( + + {content} + + ); + } + + return ( + + + {content} + + + ); +} diff --git a/packages/ui/src/components/Table/components/Table.tsx b/packages/ui/src/components/Table/components/Table.tsx new file mode 100644 index 0000000000..0ad22a85b8 --- /dev/null +++ b/packages/ui/src/components/Table/components/Table.tsx @@ -0,0 +1,34 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useStyles } from '../../../hooks/useStyles'; +import { + Table as ReactAriaTable, + type TableProps, +} from 'react-aria-components'; + +/** @public */ +export const Table = (props: TableProps) => { + const { classNames } = useStyles('Table'); + + return ( + + ); +}; diff --git a/packages/ui/src/components/Table/components/TableBody.tsx b/packages/ui/src/components/Table/components/TableBody.tsx new file mode 100644 index 0000000000..64e960167b --- /dev/null +++ b/packages/ui/src/components/Table/components/TableBody.tsx @@ -0,0 +1,28 @@ +/* + * 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 { + TableBody as ReactAriaTableBody, + type TableBodyProps, +} from 'react-aria-components'; +import { useStyles } from '../../../hooks/useStyles'; + +/** @public */ +export const TableBody = (props: TableBodyProps) => { + const { classNames } = useStyles('Table'); + + return ; +}; diff --git a/packages/ui/src/components/Table/components/TableHeader.tsx b/packages/ui/src/components/Table/components/TableHeader.tsx new file mode 100644 index 0000000000..7802cd0327 --- /dev/null +++ b/packages/ui/src/components/Table/components/TableHeader.tsx @@ -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 { + TableHeader as ReactAriaTableHeader, + type TableHeaderProps, + Checkbox, +} from 'react-aria-components'; +import { Collection, useTableOptions } from 'react-aria-components'; +import { Column } from './Column'; +import { useStyles } from '../../../hooks/useStyles'; + +/** @public */ +export const TableHeader = ({ + columns, + children, +}: TableHeaderProps) => { + let { selectionBehavior, selectionMode, allowsDragging } = useTableOptions(); + + const { classNames } = useStyles('Table'); + + return ( + + {/* Add extra columns for drag and drop and selection. */} + {allowsDragging && } + {selectionBehavior === 'toggle' && ( + + {selectionMode === 'multiple' && } + + )} + {children} + + ); +}; diff --git a/packages/ui/src/components/Table/hooks/types.ts b/packages/ui/src/components/Table/hooks/types.ts new file mode 100644 index 0000000000..8c93d678fc --- /dev/null +++ b/packages/ui/src/components/Table/hooks/types.ts @@ -0,0 +1,87 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { TablePaginationProps } from '../../TablePagination/types'; + +/** @public */ +export interface UseTablePaginationConfig { + /** Total number of rows in the dataset - only needed when data is not provided at the top level */ + rowCount?: number; + + // Controlled pagination with offset/pageSize (Backstage style) + /** Current offset. When provided, pagination is controlled */ + offset?: number; + /** Current page size. When provided, pagination is controlled */ + pageSize?: number; + /** Callback when offset changes */ + onOffsetChange?: (offset: number) => void; + /** Callback when page size changes */ + onPageSizeChange?: (pageSize: number) => void; + + // Uncontrolled pagination defaults + /** Default page size for uncontrolled mode */ + defaultPageSize?: number; + /** Default offset for uncontrolled mode */ + defaultOffset?: number; + + // Analytics callbacks + /** Callback when next page is clicked */ + onNextPage?: () => void; + /** Callback when previous page is clicked */ + onPreviousPage?: () => void; + + // UI options + /** Whether to show page size options */ + showPageSizeOptions?: boolean; +} + +/** @public */ +export interface UseTablePagination { + /** Props to pass to TablePagination component */ + paginationProps: TablePaginationProps; + /** Current offset */ + offset: number; + /** Current page size */ + pageSize: number; + /** Sliced data for current page - only available when data is provided to useTable */ + data?: T[]; + /** Go to next page */ + nextPage: () => void; + /** Go to previous page */ + previousPage: () => void; + /** Set specific offset */ + setOffset: (offset: number) => void; + /** Set page size */ + setPageSize: (pageSize: number) => void; +} + +/** @public */ +export interface UseTableConfig { + /** Full dataset - when provided, rowCount is calculated automatically and sliced data is returned */ + data?: T[]; + /** Pagination configuration */ + pagination?: UseTablePaginationConfig; +} + +/** @public */ +export interface UseTableResult { + /** Sliced data for current page */ + data?: T[]; + /** Props to pass to TablePagination component */ + paginationProps: TablePaginationProps; + /** Pagination utilities */ + pagination: UseTablePagination; +} diff --git a/packages/ui/src/components/Table/hooks/useTable.ts b/packages/ui/src/components/Table/hooks/useTable.ts new file mode 100644 index 0000000000..2759fcde41 --- /dev/null +++ b/packages/ui/src/components/Table/hooks/useTable.ts @@ -0,0 +1,166 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useState, useMemo, useCallback } from 'react'; +import type { TablePaginationProps } from '../../TablePagination/types'; +import type { + UseTableConfig, + UseTableResult, + UseTablePagination, +} from './types'; + +/** + * Hook for managing table state including pagination and future features like sorting. + * Supports both controlled and uncontrolled modes using offset/pageSize pattern (Backstage style). + * + * @public + */ +export function useTable( + config: UseTableConfig = {}, +): UseTableResult { + const { data, pagination: paginationConfig = {} } = config; + + const { + rowCount: providedRowCount, + offset: controlledOffset, + pageSize: controlledPageSize, + onOffsetChange, + onPageSizeChange, + defaultPageSize = 10, + defaultOffset = 0, + onNextPage, + onPreviousPage, + showPageSizeOptions = true, + } = paginationConfig; + + // Determine if we're in controlled mode + const isControlled = + controlledOffset !== undefined || controlledPageSize !== undefined; + + // Calculate row count from data or use provided value + const rowCount = data?.length ?? providedRowCount ?? 0; + + // Internal state for uncontrolled mode + const [internalOffset, setInternalOffset] = useState(defaultOffset); + const [internalPageSize, setInternalPageSize] = useState(defaultPageSize); + + // Calculate current values + const currentOffset = controlledOffset ?? internalOffset; + const currentPageSize = controlledPageSize ?? internalPageSize; + + // Calculate sliced data if data array is provided + const currentData = useMemo(() => { + if (!data) return undefined; + return data.slice(currentOffset, currentOffset + currentPageSize); + }, [data, currentOffset, currentPageSize]); + + // Update functions + const setOffset = useCallback( + (newOffset: number) => { + if (isControlled) { + onOffsetChange?.(newOffset); + } else { + setInternalOffset(newOffset); + } + }, + [isControlled, onOffsetChange], + ); + + const setPageSize = useCallback( + (newPageSize: number) => { + // When changing page size, reset to first page to avoid showing empty results + const newOffset = 0; + + if (isControlled) { + onPageSizeChange?.(newPageSize); + onOffsetChange?.(newOffset); + } else { + setInternalPageSize(newPageSize); + setInternalOffset(newOffset); + } + }, + [isControlled, onPageSizeChange, onOffsetChange], + ); + + const nextPage = useCallback(() => { + const nextOffset = currentOffset + currentPageSize; + if (nextOffset < rowCount) { + onNextPage?.(); + setOffset(nextOffset); + } + }, [currentOffset, currentPageSize, rowCount, onNextPage, setOffset]); + + const previousPage = useCallback(() => { + if (currentOffset > 0) { + onPreviousPage?.(); + const prevOffset = Math.max(0, currentOffset - currentPageSize); + setOffset(prevOffset); + } + }, [currentOffset, currentPageSize, onPreviousPage, setOffset]); + + // Pagination props for TablePagination component + const paginationProps: TablePaginationProps = useMemo( + () => ({ + offset: currentOffset, + pageSize: currentPageSize, + rowCount, + setOffset, + setPageSize, + onNextPage, + onPreviousPage, + showPageSizeOptions, + }), + [ + currentOffset, + currentPageSize, + rowCount, + setOffset, + setPageSize, + onNextPage, + onPreviousPage, + showPageSizeOptions, + ], + ); + + const pagination: UseTablePagination = useMemo( + () => ({ + paginationProps, + offset: currentOffset, + pageSize: currentPageSize, + data: currentData, + nextPage, + previousPage, + setOffset, + setPageSize, + }), + [ + paginationProps, + currentOffset, + currentPageSize, + currentData, + nextPage, + previousPage, + setOffset, + setPageSize, + ], + ); + + return { + data: currentData, + paginationProps, + pagination, + }; +} diff --git a/packages/ui/src/components/Table/index.ts b/packages/ui/src/components/Table/index.ts new file mode 100644 index 0000000000..945621b26c --- /dev/null +++ b/packages/ui/src/components/Table/index.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { Table } from './components/Table'; +export { TableHeader } from './components/TableHeader'; +export { TableBody } from './components/TableBody'; +export { Column } from './components/Column'; +export { Row } from './components/Row'; +export { Cell } from './components/Cell'; +export { CellProfile } from './components/CellProfile'; +export { useTable } from './hooks/useTable'; + +export type { CellProps, CellProfileProps } from './types'; +export type { + UseTableConfig, + UseTableResult, + UseTablePagination, + UseTablePaginationConfig, +} from './hooks/types'; diff --git a/packages/canon/src/components/DataTable/mocked-components.ts b/packages/ui/src/components/Table/mocked-data1.ts similarity index 92% rename from packages/canon/src/components/DataTable/mocked-components.ts rename to packages/ui/src/components/Table/mocked-data1.ts index 40d790de78..ea5a9f2db8 100644 --- a/packages/canon/src/components/DataTable/mocked-components.ts +++ b/packages/ui/src/components/Table/mocked-data1.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export interface DataProps { name: string; owner: { @@ -23,6 +24,7 @@ export interface DataProps { type: 'documentation' | 'library' | 'service' | 'website' | 'other'; description?: string; tags?: string[]; + lifecycle: 'experimental' | 'production'; } export const data: DataProps[] = [ @@ -37,6 +39,7 @@ export const data: DataProps[] = [ description: 'A comprehensive service handling user authentication and role-based access control across all applications.', tags: ['security', 'authentication', 'authorization'], + lifecycle: 'production', }, { name: 'user-interface-dashboard-and-analytics-platform', @@ -49,6 +52,7 @@ export const data: DataProps[] = [ description: 'Interactive dashboard providing real-time analytics and data visualization for business metrics.', tags: ['analytics', 'visualization', 'dashboard'], + lifecycle: 'production', }, { name: 'payment-gateway', @@ -61,6 +65,7 @@ export const data: DataProps[] = [ description: 'Secure payment processing system supporting multiple payment methods and currencies.', tags: ['payments', 'security', 'finance'], + lifecycle: 'production', }, { name: 'real-time-analytics-processing-and-visualization-engine', @@ -73,6 +78,7 @@ export const data: DataProps[] = [ description: 'High-performance engine for processing and visualizing streaming data analytics.', tags: ['analytics', 'real-time', 'data-processing'], + lifecycle: 'experimental', }, { name: 'notification-center', @@ -85,6 +91,7 @@ export const data: DataProps[] = [ description: 'Centralized system for managing and delivering notifications across multiple channels.', tags: ['notifications', 'messaging'], + lifecycle: 'production', }, { name: 'administrative-control-panel-and-user-management-interface', @@ -97,6 +104,7 @@ export const data: DataProps[] = [ description: 'Admin interface for managing users, permissions, and system configurations.', tags: ['admin', 'user-management', 'configuration'], + lifecycle: 'production', }, { name: 'search-indexer', @@ -109,6 +117,7 @@ export const data: DataProps[] = [ description: 'Service responsible for indexing and updating searchable content across the platform.', tags: ['search', 'indexing'], + lifecycle: 'production', }, { name: 'cross-platform-mobile-application-framework', @@ -121,6 +130,7 @@ export const data: DataProps[] = [ description: 'Framework enabling development of cross-platform mobile applications with shared codebase.', tags: ['mobile', 'framework', 'cross-platform'], + lifecycle: 'experimental', }, { name: 'database-migration', @@ -133,6 +143,7 @@ export const data: DataProps[] = [ description: 'Tools and scripts for managing database schema migrations and data transformations.', tags: ['database', 'migration', 'devops'], + lifecycle: 'production', }, { name: 'api-gateway', @@ -145,6 +156,7 @@ export const data: DataProps[] = [ description: 'Central entry point for all API requests, handling routing, authentication, and rate limiting.', tags: ['api', 'gateway', 'security', 'routing'], + lifecycle: 'production', }, { name: 'content-management', @@ -157,6 +169,7 @@ export const data: DataProps[] = [ description: 'System for managing and delivering digital content across multiple channels.', tags: ['content', 'management', 'delivery'], + lifecycle: 'production', }, { name: 'enterprise-reporting-and-analytics-dashboard', @@ -169,6 +182,7 @@ export const data: DataProps[] = [ description: 'Comprehensive business intelligence platform for enterprise-wide reporting and analytics.', tags: ['analytics', 'reporting', 'business-intelligence'], + lifecycle: 'production', }, { name: 'image-processing-and-optimization-service', @@ -181,6 +195,7 @@ export const data: DataProps[] = [ description: 'Service for processing, optimizing, and delivering images across different devices and networks.', tags: ['media', 'optimization', 'processing'], + lifecycle: 'production', }, { name: 'customer-portal', @@ -193,6 +208,7 @@ export const data: DataProps[] = [ description: 'Self-service portal for customers to manage their accounts and access services.', tags: ['customer', 'self-service'], + lifecycle: 'production', }, { name: 'log-aggregator', @@ -205,6 +221,7 @@ export const data: DataProps[] = [ description: 'Centralized logging system for collecting, processing, and analyzing application logs.', tags: ['logging', 'monitoring', 'devops'], + lifecycle: 'production', }, { name: 'identity-provider', @@ -217,6 +234,7 @@ export const data: DataProps[] = [ description: 'Service managing user identities and authentication across the organization.', tags: ['identity', 'security', 'authentication'], + lifecycle: 'production', }, { name: 'document-storage', @@ -229,6 +247,7 @@ export const data: DataProps[] = [ description: 'Secure and scalable document storage system with version control and access management.', tags: ['storage', 'documents', 'version-control'], + lifecycle: 'production', }, { name: 'workflow-engine', @@ -241,6 +260,7 @@ export const data: DataProps[] = [ description: 'Engine for defining and executing business processes and workflows.', tags: ['workflow', 'automation'], + lifecycle: 'experimental', }, { name: 'mobile-backend', @@ -253,6 +273,7 @@ export const data: DataProps[] = [ description: 'Backend services supporting mobile applications with optimized APIs and data synchronization.', tags: ['mobile', 'backend', 'api'], + lifecycle: 'production', }, { name: 'system-monitoring-and-alerting-dashboard', @@ -265,6 +286,7 @@ export const data: DataProps[] = [ description: 'Real-time monitoring and alerting system for infrastructure and application health.', tags: ['monitoring', 'alerting', 'devops', 'infrastructure'], + lifecycle: 'production', }, { name: 'email-service', @@ -277,6 +299,7 @@ export const data: DataProps[] = [ description: 'Reliable email delivery service with templates and tracking capabilities.', tags: ['email', 'communication'], + lifecycle: 'production', }, { name: 'data-pipeline', @@ -289,6 +312,7 @@ export const data: DataProps[] = [ description: 'ETL pipeline for processing and transforming large volumes of data.', tags: ['data', 'etl', 'pipeline'], + lifecycle: 'production', }, { name: 'configuration-manager', @@ -301,6 +325,7 @@ export const data: DataProps[] = [ description: 'Centralized system for managing application configurations across environments.', tags: ['configuration', 'management'], + lifecycle: 'production', }, { name: 'testing-framework', @@ -313,6 +338,7 @@ export const data: DataProps[] = [ description: 'Comprehensive testing framework supporting various types of automated tests.', tags: ['testing', 'automation', 'qa'], + lifecycle: 'production', }, { name: 'cache-service', @@ -325,6 +351,7 @@ export const data: DataProps[] = [ description: 'Distributed caching service for improving application performance.', tags: ['caching', 'performance'], + lifecycle: 'production', }, { name: 'billing-system', @@ -337,6 +364,7 @@ export const data: DataProps[] = [ description: 'System for managing customer billing, invoicing, and payment processing.', tags: ['billing', 'finance', 'payments'], + lifecycle: 'production', }, { name: 'comprehensive-product-documentation-and-api-reference', @@ -349,6 +377,7 @@ export const data: DataProps[] = [ description: 'Complete documentation covering product features, APIs, and integration guides.', tags: ['documentation', 'api', 'reference'], + lifecycle: 'production', }, { name: 'queue-manager', @@ -361,6 +390,7 @@ export const data: DataProps[] = [ description: 'Message queue system for asynchronous processing and event handling.', tags: ['queue', 'messaging', 'async'], + lifecycle: 'production', }, { name: 'security-scanner', @@ -373,6 +403,7 @@ export const data: DataProps[] = [ description: 'Automated security scanning tool for identifying vulnerabilities in code and infrastructure.', tags: ['security', 'scanning', 'vulnerability'], + lifecycle: 'experimental', }, { name: 'user-profile', @@ -385,6 +416,7 @@ export const data: DataProps[] = [ description: 'User profile management interface with personalization features.', tags: ['user', 'profile', 'personalization'], + lifecycle: 'production', }, { name: 'data-warehouse', @@ -397,6 +429,7 @@ export const data: DataProps[] = [ description: 'Centralized data repository for business intelligence and analytics.', tags: ['data', 'warehouse', 'analytics'], + lifecycle: 'production', }, { name: 'deployment-automation', @@ -409,6 +442,7 @@ export const data: DataProps[] = [ description: 'Automated deployment pipeline for continuous integration and delivery.', tags: ['deployment', 'automation', 'ci-cd', 'devops'], + lifecycle: 'production', }, { name: 'chat-service', @@ -421,6 +455,7 @@ export const data: DataProps[] = [ description: 'Real-time chat service supporting text, file sharing, and group conversations.', tags: ['chat', 'communication', 'real-time'], + lifecycle: 'experimental', }, { name: 'analytics-dashboard', @@ -433,6 +468,7 @@ export const data: DataProps[] = [ description: 'Interactive dashboard for visualizing and analyzing business metrics.', tags: ['analytics', 'dashboard', 'visualization'], + lifecycle: 'production', }, { name: 'file-uploader', @@ -445,6 +481,7 @@ export const data: DataProps[] = [ description: 'Service for handling secure file uploads with progress tracking and validation.', tags: ['storage', 'upload', 'files'], + lifecycle: 'production', }, { name: 'search-service', @@ -457,6 +494,7 @@ export const data: DataProps[] = [ description: 'Full-text search service with advanced filtering and ranking capabilities.', tags: ['search', 'full-text'], + lifecycle: 'production', }, { name: 'mobile-sdk', @@ -469,6 +507,7 @@ export const data: DataProps[] = [ description: 'Software development kit for building mobile applications with native features.', tags: ['mobile', 'sdk', 'development'], + lifecycle: 'production', }, { name: 'performance-monitor', @@ -481,6 +520,7 @@ export const data: DataProps[] = [ description: 'System for monitoring and analyzing application performance metrics.', tags: ['performance', 'monitoring', 'metrics'], + lifecycle: 'production', }, { name: 'content-delivery', @@ -493,6 +533,7 @@ export const data: DataProps[] = [ description: 'CDN service for optimized content delivery across global networks.', tags: ['cdn', 'content', 'delivery'], + lifecycle: 'production', }, { name: 'user-authentication', @@ -505,6 +546,7 @@ export const data: DataProps[] = [ description: 'Service handling user login, session management, and authentication flows.', tags: ['authentication', 'security', 'user'], + lifecycle: 'production', }, { name: 'data-export', @@ -517,6 +559,7 @@ export const data: DataProps[] = [ description: 'Service for exporting data in various formats with scheduling capabilities.', tags: ['data', 'export', 'scheduling'], + lifecycle: 'production', }, { name: 'admin-api', @@ -529,6 +572,7 @@ export const data: DataProps[] = [ description: 'API endpoints for administrative functions and system management.', tags: ['api', 'admin', 'management'], + lifecycle: 'production', }, { name: 'testing-dashboard', @@ -540,6 +584,7 @@ export const data: DataProps[] = [ type: 'website', description: 'Dashboard for monitoring test results and quality metrics.', tags: ['testing', 'dashboard', 'qa'], + lifecycle: 'production', }, { name: 'message-broker', @@ -552,6 +597,7 @@ export const data: DataProps[] = [ description: 'Message broker service for reliable event-driven communication between services.', tags: ['messaging', 'broker', 'event-driven'], + lifecycle: 'production', }, { name: 'payment-processor', @@ -564,6 +610,7 @@ export const data: DataProps[] = [ description: 'Service for processing financial transactions and payment methods.', tags: ['payments', 'finance', 'processing'], + lifecycle: 'production', }, { name: 'document-viewer', @@ -575,6 +622,7 @@ export const data: DataProps[] = [ type: 'website', description: 'Web-based document viewer supporting multiple file formats.', tags: ['documents', 'viewer'], + lifecycle: 'production', }, { name: 'load-balancer', @@ -587,6 +635,7 @@ export const data: DataProps[] = [ description: 'Service for distributing network traffic across multiple servers.', tags: ['load-balancing', 'networking', 'infrastructure'], + lifecycle: 'production', }, { name: 'security-audit', @@ -599,6 +648,7 @@ export const data: DataProps[] = [ description: 'Tools and processes for conducting security audits and compliance checks.', tags: ['security', 'audit', 'compliance'], + lifecycle: 'production', }, { name: 'user-settings', @@ -611,6 +661,7 @@ export const data: DataProps[] = [ description: 'Interface for users to manage their preferences and account settings.', tags: ['user', 'settings', 'preferences'], + lifecycle: 'production', }, { name: 'data-import', @@ -623,6 +674,7 @@ export const data: DataProps[] = [ description: 'Service for importing and validating data from external sources.', tags: ['data', 'import', 'validation'], + lifecycle: 'production', }, { name: 'infrastructure-monitor', @@ -635,6 +687,7 @@ export const data: DataProps[] = [ description: 'Monitoring system for infrastructure components and resources.', tags: ['monitoring', 'infrastructure', 'devops'], + lifecycle: 'production', }, { name: 'notification-manager', @@ -647,6 +700,7 @@ export const data: DataProps[] = [ description: 'Service for managing and delivering notifications across multiple channels.', tags: ['notifications', 'management'], + lifecycle: 'production', }, { name: 'analytics-processor', @@ -659,6 +713,7 @@ export const data: DataProps[] = [ description: 'Service for processing and analyzing business data and metrics.', tags: ['analytics', 'processing', 'metrics'], + lifecycle: 'production', }, { name: 'file-manager', @@ -670,6 +725,7 @@ export const data: DataProps[] = [ type: 'website', description: 'Web interface for managing files and storage resources.', tags: ['files', 'storage', 'management'], + lifecycle: 'production', }, { name: 'search-index', @@ -681,6 +737,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for maintaining and updating search indices.', tags: ['search', 'indexing'], + lifecycle: 'production', }, { name: 'mobile-authentication', @@ -693,6 +750,7 @@ export const data: DataProps[] = [ description: 'Authentication service specifically designed for mobile applications.', tags: ['mobile', 'authentication', 'security'], + lifecycle: 'experimental', }, { name: 'system-monitor', @@ -705,6 +763,7 @@ export const data: DataProps[] = [ description: 'Monitoring service for system health and performance metrics.', tags: ['monitoring', 'system', 'metrics'], + lifecycle: 'production', }, { name: 'media-processor', @@ -716,6 +775,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for processing and optimizing media files.', tags: ['media', 'processing', 'optimization'], + lifecycle: 'production', }, { name: 'user-management', @@ -727,6 +787,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for managing user accounts and permissions.', tags: ['user', 'management', 'security'], + lifecycle: 'production', }, { name: 'data-transformer', @@ -739,6 +800,7 @@ export const data: DataProps[] = [ description: 'Service for transforming data between different formats and structures.', tags: ['data', 'transformation'], + lifecycle: 'production', }, { name: 'admin-dashboard', @@ -751,6 +813,7 @@ export const data: DataProps[] = [ description: 'Administrative interface for system management and monitoring.', tags: ['admin', 'dashboard', 'management'], + lifecycle: 'production', }, { name: 'test-automation', @@ -762,6 +825,7 @@ export const data: DataProps[] = [ type: 'other', description: 'Tools and frameworks for automating testing processes.', tags: ['testing', 'automation', 'qa'], + lifecycle: 'production', }, { name: 'event-bus', @@ -773,6 +837,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Event-driven communication system between services.', tags: ['events', 'messaging', 'communication'], + lifecycle: 'production', }, { name: 'invoice-generator', @@ -784,6 +849,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for generating and managing invoices.', tags: ['invoices', 'finance'], + lifecycle: 'production', }, { name: 'document-editor', @@ -795,6 +861,7 @@ export const data: DataProps[] = [ type: 'website', description: 'Web-based document editing interface.', tags: ['documents', 'editor'], + lifecycle: 'experimental', }, { name: 'service-discovery', @@ -806,6 +873,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for discovering and registering available services.', tags: ['discovery', 'services', 'devops'], + lifecycle: 'production', }, { name: 'security-monitor', @@ -817,6 +885,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for monitoring security events and threats.', tags: ['security', 'monitoring', 'threats'], + lifecycle: 'production', }, { name: 'user-preferences', @@ -828,6 +897,7 @@ export const data: DataProps[] = [ type: 'website', description: 'Interface for managing user preferences and settings.', tags: ['user', 'preferences'], + lifecycle: 'production', }, { name: 'data-validator', @@ -839,6 +909,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for validating data integrity and format.', tags: ['data', 'validation'], + lifecycle: 'production', }, { name: 'infrastructure-automation', @@ -851,6 +922,7 @@ export const data: DataProps[] = [ description: 'Tools for automating infrastructure provisioning and management.', tags: ['infrastructure', 'automation', 'devops'], + lifecycle: 'production', }, { name: 'notification-dispatcher', @@ -863,6 +935,7 @@ export const data: DataProps[] = [ description: 'Service for dispatching notifications to appropriate channels.', tags: ['notifications', 'dispatch'], + lifecycle: 'production', }, { name: 'analytics-collector', @@ -874,6 +947,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for collecting and aggregating analytics data.', tags: ['analytics', 'collection', 'aggregation'], + lifecycle: 'production', }, { name: 'file-processor', @@ -885,6 +959,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for processing and managing files.', tags: ['files', 'processing'], + lifecycle: 'production', }, { name: 'search-analyzer', @@ -896,6 +971,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for analyzing search queries and results.', tags: ['search', 'analysis'], + lifecycle: 'experimental', }, { name: 'mobile-notifications', @@ -907,6 +983,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for sending notifications to mobile devices.', tags: ['mobile', 'notifications'], + lifecycle: 'experimental', }, { name: 'system-alerts', @@ -918,6 +995,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for managing and dispatching system alerts.', tags: ['alerts', 'system', 'monitoring'], + lifecycle: 'production', }, { name: 'media-encoder', @@ -929,6 +1007,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for encoding and processing media files.', tags: ['media', 'encoding'], + lifecycle: 'production', }, { name: 'user-authorization', @@ -940,6 +1019,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for managing user permissions and access control.', tags: ['authorization', 'security', 'user'], + lifecycle: 'production', }, { name: 'data-aggregator', @@ -951,6 +1031,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for aggregating data from multiple sources.', tags: ['data', 'aggregation'], + lifecycle: 'production', }, { name: 'admin-authentication', @@ -962,6 +1043,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Authentication service for administrative access.', tags: ['admin', 'authentication', 'security'], + lifecycle: 'production', }, { name: 'test-coverage', @@ -973,6 +1055,7 @@ export const data: DataProps[] = [ type: 'other', description: 'Tools for measuring and reporting test coverage.', tags: ['testing', 'coverage', 'qa'], + lifecycle: 'production', }, { name: 'event-processor', @@ -984,6 +1067,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for processing and handling events.', tags: ['events', 'processing'], + lifecycle: 'production', }, { name: 'payment-validator', @@ -995,6 +1079,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for validating payment transactions.', tags: ['payments', 'validation', 'finance'], + lifecycle: 'production', }, { name: 'document-converter', @@ -1006,6 +1091,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for converting documents between different formats.', tags: ['documents', 'conversion'], + lifecycle: 'experimental', }, { name: 'service-health', @@ -1017,6 +1103,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for monitoring and reporting service health status.', tags: ['health', 'monitoring', 'services'], + lifecycle: 'production', }, { name: 'security-logger', @@ -1028,6 +1115,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for logging security-related events and activities.', tags: ['security', 'logging'], + lifecycle: 'production', }, { name: 'user-analytics', @@ -1040,6 +1128,7 @@ export const data: DataProps[] = [ description: 'Analytics dashboard for user behavior and engagement metrics.', tags: ['analytics', 'user', 'metrics'], + lifecycle: 'experimental', }, { name: 'data-cleaner', @@ -1051,6 +1140,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for cleaning and standardizing data.', tags: ['data', 'cleaning'], + lifecycle: 'production', }, { name: 'infrastructure-deployer', @@ -1062,6 +1152,7 @@ export const data: DataProps[] = [ type: 'other', description: 'Tools for deploying and managing infrastructure resources.', tags: ['infrastructure', 'deployment', 'devops'], + lifecycle: 'production', }, { name: 'notification-queue', @@ -1073,6 +1164,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Queue system for managing notification delivery.', tags: ['notifications', 'queue'], + lifecycle: 'production', }, { name: 'analytics-exporter', @@ -1084,6 +1176,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for exporting analytics data in various formats.', tags: ['analytics', 'export'], + lifecycle: 'production', }, { name: 'file-validator', @@ -1095,6 +1188,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for validating file integrity and format.', tags: ['files', 'validation'], + lifecycle: 'production', }, { name: 'search-optimizer', @@ -1106,6 +1200,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for optimizing search performance and relevance.', tags: ['search', 'optimization'], + lifecycle: 'experimental', }, { name: 'mobile-analytics', @@ -1117,6 +1212,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Analytics service specifically for mobile applications.', tags: ['mobile', 'analytics'], + lifecycle: 'experimental', }, { name: 'system-logger', @@ -1128,6 +1224,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for logging system events and activities.', tags: ['logging', 'system'], + lifecycle: 'production', }, { name: 'media-validator', @@ -1139,6 +1236,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for validating media files and formats.', tags: ['media', 'validation'], + lifecycle: 'production', }, { name: 'user-audit', @@ -1150,6 +1248,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for auditing user activities and access.', tags: ['audit', 'user', 'security'], + lifecycle: 'production', }, { name: 'data-normalizer', @@ -1161,6 +1260,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Service for normalizing data formats and structures.', tags: ['data', 'normalization'], + lifecycle: 'production', }, { name: 'admin-authorization', @@ -1172,6 +1272,7 @@ export const data: DataProps[] = [ type: 'service', description: 'Authorization service for administrative functions.', tags: ['admin', 'authorization', 'security'], + lifecycle: 'production', }, { name: 'test-reporting', @@ -1183,60 +1284,6 @@ export const data: DataProps[] = [ type: 'other', description: 'Tools for generating and managing test reports.', tags: ['testing', 'reporting', 'qa'], - }, - { - name: 'event-aggregator', - owner: { - name: 'platform-team', - profilePicture: 'https://github.com/platform-team.png', - link: 'https://github.com/orgs/company/teams/platform-team', - }, - type: 'service', - description: 'Service for aggregating and processing events.', - tags: ['events', 'aggregation'], - }, - { - name: 'payment-reconciler', - owner: { - name: 'finance-team', - profilePicture: 'https://github.com/finance-team.png', - link: 'https://github.com/orgs/company/teams/finance-team', - }, - type: 'service', - description: 'Service for reconciling payment transactions.', - tags: ['payments', 'reconciliation', 'finance'], - }, - { - name: 'document-validator', - owner: { - name: 'frontend-team', - profilePicture: 'https://github.com/frontend-team.png', - link: 'https://github.com/orgs/company/teams/frontend-team', - }, - type: 'service', - description: 'Service for validating document formats and content.', - tags: ['documents', 'validation'], - }, - { - name: 'service-monitor', - owner: { - name: 'devops-team', - profilePicture: 'https://github.com/devops-team.png', - link: 'https://github.com/orgs/company/teams/devops-team', - }, - type: 'service', - description: 'Service for monitoring service health and performance.', - tags: ['monitoring', 'services', 'health'], - }, - { - name: 'security-validator', - owner: { - name: 'security-team', - profilePicture: 'https://github.com/security-team.png', - link: 'https://github.com/orgs/company/teams/security-team', - }, - type: 'service', - description: 'Service for validating security configurations and policies.', - tags: ['security', 'validation'], + lifecycle: 'production', }, ]; diff --git a/packages/ui/src/components/Table/mocked-data2.ts b/packages/ui/src/components/Table/mocked-data2.ts new file mode 100644 index 0000000000..30ca36a689 --- /dev/null +++ b/packages/ui/src/components/Table/mocked-data2.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 { createElement } from 'react'; +import { RiCactusLine } from '@remixicon/react'; + +export interface DataProps { + name: string; + description?: string; + icon?: React.ReactNode; +} + +export const data: DataProps[] = [ + { + name: 'Cell with title only', + }, + { + name: 'Cell with title and description', + description: + 'A comprehensive service handling user authentication and role-based access control across all applications.', + }, + { + name: 'Cell with title and icon', + icon: createElement(RiCactusLine), + }, + { + name: 'Cell with title, description and icon', + + description: + 'A comprehensive service handling user authentication and role-based access control across all applications.', + icon: createElement(RiCactusLine), + }, +]; diff --git a/packages/ui/src/components/Table/mocked-data3.ts b/packages/ui/src/components/Table/mocked-data3.ts new file mode 100644 index 0000000000..d209a0b5b4 --- /dev/null +++ b/packages/ui/src/components/Table/mocked-data3.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. + */ + +export interface DataProps { + name: string; + profilePicture?: string; + link?: string; + description?: string; +} + +export const data: DataProps[] = [ + { + name: 'John Lennon', + profilePicture: + 'https://upload.wikimedia.org/wikipedia/commons/8/85/John_Lennon_1969_%28cropped%29.jpg', + }, + { + name: 'Paul McCartney', + profilePicture: + 'https://d2kdkfqxnvpuu9.cloudfront.net/images/giant/51895.jpg?1341834484', + }, + { + name: 'Paul McCartney (Broken image link - fallback instead)', + profilePicture: + 'https://d2kdkfqxnvpuu9.clont.net/images/giant/51895.jpg?1341834484', + }, + { + name: 'George Harrison (with link)', + profilePicture: + 'https://www.who2.com/wp-content/uploads/2015/10/georgeharrison-6-scaled.jpg', + link: 'https://en.wikipedia.org/wiki/George_Harrison', + }, + { + name: 'Ringo Starr (with description)', + profilePicture: + 'https://ntvb.tmsimg.com/assets/assets/1686_v9_bb.jpg?w=360&h=480', + description: 'Ringo Starr is a drummer and singer.', + }, + { + name: 'Ringo Starr (with everything)', + profilePicture: + 'https://ntvb.tmsimg.com/assets/assets/1686_v9_bb.jpg?w=360&h=480', + description: 'Ringo Starr is a drummer and singer.', + link: 'https://en.wikipedia.org/wiki/George_Harrison', + }, +]; diff --git a/packages/ui/src/components/Table/mocked-data4.ts b/packages/ui/src/components/Table/mocked-data4.ts new file mode 100644 index 0000000000..b86d4c903f --- /dev/null +++ b/packages/ui/src/components/Table/mocked-data4.ts @@ -0,0 +1,223 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface RockBandDataProps { + name: string; + image: string; + description: string; + genre: string; + yearFormed: number; + country: string; + members: string[]; + albums: number; + awards: string[]; + influence: string; + activeYears: string; + website?: string; +} + +export const data: RockBandDataProps[] = [ + { + name: 'The Beatles', + image: + 'https://upload.wikimedia.org/wikipedia/en/thumb/4/42/Beatles_-_Abbey_Road.jpg/250px-Beatles_-_Abbey_Road.jpg', + description: + 'The most influential band in rock history, revolutionizing popular music with their innovative songwriting, studio techniques, and cultural impact.', + genre: 'Rock, Pop, Psychedelic Rock', + yearFormed: 1960, + country: 'United Kingdom', + members: [ + 'John Lennon', + 'Paul McCartney', + 'George Harrison', + 'Ringo Starr', + ], + albums: 13, + awards: [ + 'Grammy Lifetime Achievement Award', + 'Rock and Roll Hall of Fame', + 'Multiple Grammy Awards', + ], + influence: + 'Revolutionized popular music, influenced countless artists across all genres', + activeYears: '1960-1970', + website: 'https://www.thebeatles.com', + }, + { + name: 'Led Zeppelin', + image: + 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxMTEhUSExMWFRUXGBcYFxgYGBcXGBgXGBUYFhgYFhgYHSggGBolHRcXITEhJSkrLi4uGB8zODMtNygtLysBCgoKBQUFDgUFDisZExkrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrK//AABEIALwBDAMBIgACEQEDEQH/xAAcAAABBQEBAQAAAAAAAAAAAAAGAQIEBQcAAwj/xABCEAACAQIEBAQDBQYFBAEFAQABAhEAAwQSITEFBkFREyJhcTKBkQdCobHwFCNScsHRM2KC4fEkg5KiQxdTc6OyFf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDZzS0gpaBK6urqDq6urqDq6krqBa6krqDjSVxrqDprqSuoFmuFJSigWaWkFdQdS0grqBa411JQcaQmuNV/FMVlUgHUg/Kg8+Kcat2d5J7Dt3J6CgXiX2nFLoVbQKGAGLFNZgk5htU7ia3C9uyozggEkgEmQRm1I2jodMwq24fyxYyL41pHYDqJGpkmDufU0HYTnFAiteAAZgilD4gk6DNG1FStQVxjhdq0gVhlsh1dGk/unVsyySdLZOnpMbHS14HxwXGyaHyzI1HyNARUtMFOoOpaSloENIwp1IaBa6urqDjSV1dQdSV1NJoFmummzXTQOmups0s0C00murzuXANaB80mas/5u+0uxhly2Ct+6egMqv8AOR19KAb/ANquPZgQ1tB/CEBB9y0mg37NSg1mvLP2rWLxS3iVNm4YGfQ2idt5lJ9RA71ottwdZoJE11eeanTQPpKQGloFrqSaaTQI7UL8UZ7twqNEUEluuYEZQo7DUyav8ZdgGgbGY4XRdt+IbfSREn/bSJ9aCnt8cvJde5cVFbNaQKznKLbZir5gCczHy6CdYANGvBOLXWA8ZbYVhKFPEB9mW4oI+cH0FZ7wJ7jYgC4ZHiIkn7xV7t4R1O2Yeh13FaPjQ0L4YUkEEyxUR1JIU9KCvxXBXvNcLvnVwR+8NwhQWMqltWVAMvWJmJmq7lu0bd5VyeGgQrl3lywOYHqIHyJb3orw+IUyuZSd8oIJH4z86EP2/NiblvT93d9hHhp17h80j1HpIaHhrkivcVU8KxIIg79R2q0BoHUtJS0HUhNdXRQLXV1JQcaSuNJNB01E4jjUs22uOYVRJqSxrNPtI4mbpOHRiAozN2LfdX8Z+QoKHmT7QMRec28OSqzpl/239++3QlmBw3GLdv8AaPGugAzkZ2JIjcqdI9Pwq0+zfhVhE8a5ke6zsqgssrl8p8p3Myd9BEAdT3H4lVGp37KWj1IUGB6nSgqeR+cRjEyuAt5PiXoY0zL/AG6UVrdFfN9jibYTGi6mkPJH+Q7r66Vu3D+IB1V1MhhI9qC7a7WZfbJx69ZtW7Vpiq3S6uwMGAF8oO+snUfwx1oz4zjclm4wmcpC5d8x0UCeskV8+80cZxN5hYxDqxsMy+UAeYHKSdPT060FITVry/y/fxj5LKgxGZmMKs9+p9gDUPhPD2xF1bKQGadTsIE6kdK2HlfhRwFpxoSVYkobgBI+9lZ2Ab1AG3yoM85v5QuYEW2JLo2jMBoH3AjoCJj2o1+yfm27cP7JdYMESbbHeAYyk9YG3tULnzh998O7sXyIM+t5rgYSCcyPOQjUgqemwnTPeFYtrbhlYqRsRvPSg+pUea9laqPgGJL2LRZgWKISRtmKifxq2VqCQDTpryBp4NA4mvN2pSa8bjUFNzFfXIVZsoI1PaszFyzbwl5XuFyZJZN7h+6oIGg9Onzo15o4rZC3EuifKTHcCAfzrL8JxbC2UvhJAurGQnNqCCGXSFKwdTQQsFxh7dxbuY5sNcLNaJMOpyWXZT/EoCAg9NRs1azwfjWExoUAqxWDkY6g9MyHSR3rEOJ4sXGzARmMvMeZtQDp6GfcmtK5G4Vh71pWa3bdlGudA2nY5pmgM+N8wYfBWWZmWVHltrGZmOygD5a9BrWT8Hxd/wAR7l7zLdJuFQ0f4p302AD9egoo+0kWksC2iKh3VUXKJJCzCiOtBXBsViLcMqgquYCQJYspABPWPK3+kUG38HxS5c/whjOu8DQfgKIsPckVlvK9u81xrjHLaMMqGSdAAzDspaSAe4o54NxIMPhI1iOo96C/paYhp1AtLSUtAlJTqaaBDTTSk01jQeOJuQpPpWScxXApu3mbLmMDX7ijOzR10P4VovM3EVs2WdjA0/E/2k/KsB5y4hcuuB/8YUZOxDksD81y/IUG2YLFWLGFth3ChUtlpkwzgHUDqSfxqEDbxGIuqtycqqDkdkZT7qQw9CPX51+C5lwYwqXFJ8RUgbBx8OYHMIIOVTqCDAPrVBzBxixaU4y04OIYgJoiEZRqGVAM0yJMaiKAU5ow+W9clizJedGY7nyZhPcwN+5o/wCQOJZsKoJ1Qlfl0/Csrs4trmdn8zPdLn1LW7gY6fzfhV9yZxXwwyk/rSPzP0oNS5lxwTC3LhjyZW120YHUQfbbrWB8Rv8AiXXeZzOzTrrLEzrrrWkcx4hsVhGtoTPlYgSZykHLHWdNO461l7CgJ/s+41awuJL3pylSJABg7++u3zo1v8Y4hfQ3USzYRklA5ctcRirL5x5dVn/MCdgNayKp1ji99MgW84Fv4BmJC+gU6R6UGhcyc3h8A9l7fhYhv3TWt4UqDnkwYKnqJk1m1kjMJMCRJ3gTrp1pL99nZndizMSWYmSSdyaaKD6Q5XwapbDKB5gDIEAyN46TqT6zRAKoOVOIW79hHtHywAPSAJHrG3uCOlXyig9lp001a4mgVjUTFv5TUkmvC+ukUGRcxcRuG4ywrqJbKfhJGaJH3ogwD1A3ql4lwSzcRbiIUzDMf4VAEkKOu/6g1ofE+XrbM8j4o/D/AJqoxfALp8NSwyJoCB5gNDrB1EjWgyhbAZoE5Zj1rUOTeEPaRWtvBI+8Mw19ARTuD8nAXCWiFJmRM6LB+ep9oo4wmECwBsAP+KAC5l5fvMRca9cueYEjKoVR3CqJb5k1R30gPbRXuWoU6+RkZSTIEHTzEQd962XwxXi/DbRbNkE9436UATyxZNxQrMdRAEnYAkAmNxP5UYcPwnhBQO+skknytqSfap+HwqJqqKvcgAflXnxHEpbALsFB2nqQCx/AGgn4a6c0dI1+pA/XpU8GqPB3wxLDbQT7CfrJP0qzw9/7p+VBLpaQUooENNNKahcQx6WhLddgN/8Aig9cTfVFLMYA/WnrWe89c1ObFy1ZDKWAh1kMokE6g+U6HbvTeZOPNdMAwBMDoNvqaEb1wtMmd567bigGuM8wYm6uW7ddx/mZm2/mPtVH4jvCST2BO3TSdhRRisJsQBG4Y/D6T2Hrt6jerLlTB4ZHS5i8Oly1cOXPJYI3lynytEHMfYQdtSE//wClmI8PTEoJE5HQ6EjUSGP1HahDm/lq5gjbF24rs+YgKDoBEyT7ivodro1Pb8omsa+0HjFrFOCFHh22Kh9nfMN0/wAmgMddD6AAKziisQBoSfckR+U/WiH7PeFjE4ko2qpbZwpJAd5CorEahTmO3b3oev213EgSAJ1O25rT/so4Kf2S9fH+JcaLZ9LJlZ7TczA+goJ3OGFY4VXwwVMmVskBf3Z006AqdwR/FWR47Ctbco4hhBOs7iRr863TjjCEuR+6ulAw/h8WEefQqxP8yetCHE+UlxNws1xrbrC3AFBnIsErJ0mBrrQD/LuHstaS04Du7m6Vg/4eUI6M3RjlFwAa/uukiZXFfs+a0j3hfTw1BMFWzR0Gkyx0FGvC+UbNi2PDktoczHWYIEwBpJmKUXmYxeAW3bcC2Bq125EiFHRJPzE6BaDJOKcKewLef4nWSNfKdDlPrDA/OvPhuDa7cW2ilmYwACB0k6nTp1rQMZyi2JutcuO6eKxcqCDlAGVIkQDGUewjoCLDgfKdvC3ZR2ZyCudoELoXygCJlrag+re1AW8gcMfD4S3auMrMC5OWcozOzZRIG0xtGlFKmqDgjhRk7Ex7EkwPQaiOgirpHoPeaQmmFq82ujvQSAaj3moM419p2DsFkXNeYaeQDLP87aH5UM437XJPkw5y9Qza/Uf2oD+83nPy96W0vUQR+torOcL9pSFpuW2UekGPfXX5UacL4vavIHQyDtoQfnIoLGw2VmUiIiNtQVGv1DD5VNtvVLxe9HhOBtcAY7EK3liBvLFNNuvSrOxcmgmA0tu2SNyD+u4pgbpUix8I+v1oFt2QNdSfUz9BsPkKoua+XmxeSLuQIHhcshmYAAsZkACR/qNEIr0VaCl4RFqbLCHXUn+Kdc49CZPvI6VazpTMfgM4BU5XX4W/NT/lOn0mq4cTCSt3yMIkN6mBB6gnqKC+wt/7p+R71KmqA4odNatcNiQV8xg0DOL4s2rRuCNCszsAWAJPsDWecZ4sWlmJnY+laXiEBBBEgiCKyzifKn/VpZGIdLNzNAgMysAWCI3Y6xmBiPWgE8fjiWCICWPQDMdTGw9qm4fl/FqGuXFyWlU5VkTJZYZguh6mSe2laZwXgdmwMiWwon1M+rMSSx96sOK4fPZuIBup/vQYoWI9+2leZJXJcRRNu5mgroWIUgHTY+F+fep2PtQxgfnXjgbIuP4Wvn3iSVyqWzDuQAw101Oo3AWHH+actj9ntMzZoMEzFsqDldt41mNzMHy7gWKZmMsZP5egHQVPvBDDAtJALkx8XULH3YiJqDiWiggXDr7VvnI2F8HAYdNj4Ydvd5uN+LGsDW0W8o3chV9zoPxNfR6IEXINguUR0gQKCm4tbDW72HJjOjZD2YhmUj1Dbfy164i0M5uxAdDmHYxp+cfKq3nS6bdpbw+4wk+nQ/WfrVt+0BrfplH6ig9cJflOxA27VEt4YZ5gljmAmNM2rR6Qv/se9ehgFSN9BvpBIG20yRSJdVrwUhlORmWZX7+Vtj6CPT50Eu6AuwkxHX8e1QMEQ965AMWYtz0Nxl8R8vsH19Y7VPuXcsNAIGrSYhRqW13jtSYDD5LQB+M5nf8Ancl217AmB6KKBtsw5jsP1+NW9m9pNDpvnNU3D4oxFBa3MVWd/anxe7+5wdtsgvSbjElQVBChWP8ABuW9FHSiC/j4eJqk5j4et+/Zus8KiXBllwWlkIAKCQIDT6DtNAG8d5VWzZzC4ouIMxBYnOvUrplX0HpQjW3eHcZllZQKRaAKxbYiCSFBDfX86yvm/g5w19hkyIxJQTsOqj0BP5UFLNHX2a8UIZrBOnxL/UfrvQGKteWcUbeJtttrB+Yj84oNyxFkXLTITGZSPaRuPak4FeLWkLfEVBb001+h0qFhsf5O57VX8vcSRQ6vcUXrl14QkSqZyEUD2gn1mgOMPqJ7n8NhUpRUSy20bDb5VLWgeKkrUW3UpTQegoV57u22W1YKhrjuGXuioQWYdp0X1k9qJr95UVnc5VUFmJ2AAkk/IVnPCL7Yu/cxTA+YwojRbY0VTrv39Se9AUcNtwoM9KtbdhmEgabVEwNjMwXtq39vc1foABA2oEcUIc3JkC3hvadX+SsCY+Q/GjA1W8Yso1tg4kEHTqfQTQOuoD5h/wAivPEN5GMxode2m9VnKuM/d/szyLlpRAYam1sp7Er8BjsN5BM7iVwKjMSIg77GdAPmYHzoMr42hBPlOpJ9Iih5mIIMAnTcAjfsdJoq4phgAcp8saDeOunYabUL4xhqDp7dNd9qCDxTOrZXQIQF8oED4QQTGkkEEnuTVFibpqfiXWYTMVAGrKFMkCdNes1W4jvQX3I2A8XG4VYkK3it7W5uL/7BB863PF4dyv7sqG7MCQT7g6fjQ1ynyxbwqC/ZJN65Ztgi4QVBgMwQgArJjedhVxhuKJcIBORx8SMYdT2I6jsRoaAb5gxrXMPibN63lZbbbSRKjMNx6Aio3J2PN20M2oWBPcif9q9uc7muIU7tbOU9SCka99QfrVHy3jLduyqsSInodaA3fzPbj4VbMfWFOWfnB+QpLd/96zCJACDtMlj77ih/D4+9fRzZT92cyhsxVmOxKkfDBkT71ZcKRrazeBL6gAGQxmZAn9RQSOLYoJ4aEy967bTX+Gc7COgKIwqxv4omVUZmjpsB3YnT+tZXzNzIRi7RGosXA7dZOgYD5SPnWg47iioMudA5+6WURP3m13oPRrMCZk9fWa8bWKAJBNQcLxBQ+UMGVo2OYZge47iqLmDjARzlNBacUx4FwQfpVNxdP2m9ZSFcKGm2zZVbMVElsrBTEicp3NDmN4z9ajcG4qqXc10ZgdJ1OX101oNV5P4TdwwuBiPCIXJbztc8JhOYB2UEqZ/Cs9+03F+JjIj4EUfUlv60V43m8WcNmQrcJ0GUkiTPxem1ZljMU1241xzLMZP9PlQRJp6XCCCNxB+lNakoNW5Yxy3kzZoYgSp6mIJXvVqeB2bhlhmPykf71mvL3EnSESJkkT+IPejfC4nEnzeGI7qcw3/h+L6A0Fth+H4qwf3Fw5f4XOZfoduuxq2tcw31Ui5hiXAMZSAGMaDXQCfXSqvD8ZdIDqf7+mVgG/Cp1njNljBgbbgr89aCVY5sRR+9VlMAkBLmk6aGIb3U1f3uMWLaq73VCsQqkSxYnYAKCT9NOtVOCxdh9FZT6ZlP9amXcGhGqj5/regHuYuLjG3v2Oy/7pSPFZTOdgZyAj7gMT3PoNb7h/DxaRURdf1rUSzwayrZwvm/i1zD2JP5UQcGuFlJKt/MVKzBjYgdtxoaCZgsPkWOu5Pc1JFNFOFA014XbQJn0j+ule9NNAKcewFwMl+wVW7bn4vhZW0ZHjUqdPYgHpQxzXzOL9r9ju2GtX7jaqScpFuX8SxeAhiHW3uARJMaVomLAkA9dB79qDeab4Ft2bDftFq2Zu29r9ob+JabZl0kQRsdZUgBn+H4xIZWYGNCSMreoddg3quhImBVbj7hzSNuka+tTeN4SyQuLw19bttWUFbhW3fTNMI4YDOp6HrG53qpxOLcg6BgeogkfIRH5UEfHsxbzlZhdEyx8Ag+TSYj51U4oyrH0NWN/DlSpKlAVBOYr3IJ8vwiR8JEiK8+E4fxL9q2R5bl60nuGuqp/A0H0Fh3EJMLKiAYGpAge/SKj8XQBczKDGxYAr7E6ZT06b1D5yN39lveCoa5kbKuXNM6EBCCGOWYWNag8ENq9ZS7YvX7OYee0HNwI+zoVuq+WCCIEdO9BT8YdMQz21bwrqrlyOPKc2iFLm2p+6SD6daDcFgrpmXYDqBbBIiZHmmKvudsO9gm/afK4QL5ZTyscpyptImTBj4TAgznpxVyCPEcf6m/vrQaxwu02HtW5IS2tsl80A52hsxYbQSwiOtQ+K8yeIMti9YCkHPd8VAyjsoIOXfeZ30FZ3juKXLpYFmCEyLZdmUdgMxqCaC8CWXxCIhlA652118wDHMd+uvWtQXjZtOoCqLTkgHKUYPuBkgZs2sHrH1x7hX+JHcR9WFbHcw7C35VAYZIYk9GElvlOg9utBR8538gDoCs9IykEkT5TrHyoMym55p0kLPqWA/rNXnNElGOUAMyLmGn3gIK/d2JionA+GFrQIOp1K+qkH2BgR7xQQOKctvbGYNmMarB/A9a9eXeXS48e8wt2l18w0ZYIMmfIPU1od7l13BZLmXPuGGYHSOhBG3eq+3hLuHBXEAtaMDOsshHZ7cSu280HpwLCgZ7NxRnt/ECBDp9y4NNZGhjqCKEOdb9lCMPaQAhizkAAeYaARvp9KPsfbDWwRJYwEZIzjNoHTTza6lOonfQUNpyTce8t7GMi5iGKWzmBiBlZtInbT11oM+t4VmRnAkIRm7jNMGO2kV4CtVvco2lvM6TbUrlKiChkRAUqc07mZk7a1T8L5HW7b8VmNt80ZEYELBgyTJB30n6bUAzwqwqkG4vlJgHNlAPow2NaVy5j7RK2i03DOVxKh4EkEbZgO41iYr34RwFLByICzvvOuW3J8zNuFkNoIzEr0oT5vt3sFiB4eUWn89s5ElG6qGiQQdQR0PoaDVrFto/xCB7LPygVHx3B7V8BbigrmkLAInKwlgZDfFOs6gHpQZytzsjEJigoOwuiQv/AHF+5/MNN9q0PxBAA+Ua/QDegjjhFtVCqqBRoPux7RpUjAcHWSVZ0jQLmlCO5ToZnYiq/H8zYa02Rjnuf/btjxHB7NlkJ00Jq65cx4voXCZCDlKkox9zkJjrQe2G4Y33yInZZ1Gu50irW2gAAGgFItPFAopRXV00CUhpTSGgrOMt5MobI7SLbdnymD9YrMsFzDcFlTdn9osrCNBPjps1i9HUxpc1WQGncMVc+JduRbt2ncASSpUan+fRumlZRxzCXrJzMGtj/Pbu2lJPa4pNon3oIXFbai9dOHz27JabaSMyAqJUrJGXMWgTERVa7SZZbbR3UK31iKk3MYQAXUr6nzIf9S7H2+lNbFr8TCR7Bh/5D8jBoK+86tAyqkD7uxMnU9zrv6CrTk+yf2/CCZ/fI3/ic5/AGqq54ZmJHbWf6Cin7L8Nnx6tEi1bd56AkC2J9YdvpQH/ANofF2w+Hz22y3AylTvqD1HUdx1E1L5c5js4q012VtOCBfGgAuEAAhj8QIWAd/LHShH7Vmnwl9SxGuwHShfk3jxwuLV2I8N/JcBgLrORj0GVoM9AWoNE554X4tpoOuUjrvuP171ih37enat05lxRQEZz77bjQSABuND3rF+MpF5zr5jOoAJneY/PrQQq6kmumgtOWMH4uLsW+9wE+yec/gpra8VoNfnWP8kqfHLzGVY9yxA+QAzE+3rV9f4ybRdkuTAZo1ALAEgMDowMD110jegg8531d0tjKWUElh1DEZR9BPzFXPAmTJroGk5p0Vog6ERBgn9CgLx8zl2GZmM5RIHsOsDaOwo74Jg3Nk3CwZ2AK2wIVcuqqC2k+u0/WgLeEYksqq7DMqgDXoPKD5T1I9fep37RnMDKwjbQgjudCIrNOXOYbKljiGImR8LEwWkCVGnQfKi/C80cNyx4oUdQVuSffMutBGSw9vH4dLfltXc+mwR1RmJXfLIG1EvEHa3GYMVgyQczLtqVHxjfuaruGY3D3bymxiEbKjnLmMgmF2LCNGI0irl8ShcI28DUnfU9PlQVVu95wzEKg+F2IysIBkdI1ivcWlS6riDbYy8fdfUAj0J0PqR3q1S0sELlB2BjT9fPWqXm3ia4WwxUKb7EIgEAs9xsoJURmiZIO8dJoLZ8WgkrqWbKoG7kDX5AddhQp9o3DmOEe8TNxWtkxstsMVKL6AvmJ3JXoAALngOANm0nimWVAvsBv8ydT/sKkcSxVl7bWXZIug2wJEnMMug3nWgxe20Ixyroh7nXfXT9RU/B8ZusoVrz5AAIZ2VIHSJiPeK8cFoSjbjMp94yn8jVFhrpEake29AcW8cq+W1Zb/8AJkuZT/IAskesijj7NeJE3Gtk6Ms6oysSNRqzsYAJ0rKsClsnMwYt3PmJ95o25RxS271sgtow3002PptIoNoBp4piU+gUUtJSg0CGmk0401qCr4hbknb5iRt1HUUD8awNsEz5AetsujAeuVoiY0MjTUUd4vrQrxskLMaid9ent60Ge4LhNv8AaTh2cHOha2UhVuATqogqriDmUAqdxl2PhxTljww2UoxbLAYm0SCwOWDpOkzp023qHxPHvbZWQDyP4ls75X3bSIIOxH9aJuAYq/cwoOQNdA3V0W6UMshOaQBBgFtTQZ9//k3GIAVC7GFtrcVrjR2RSSev0q+4KcXgfFZMLdD3cmyXCqLmbKolWnUxq0wBrua0Dk7h4F97jtfZ0QALejyZpkrlRVaQsZhOxqy5r4g1i290jyJbcnWMzQFVfQksevT1oMd49x67iro8VAhthhABBBmCGzHp8qo7zRJ7U/CJDQR0b/8AkkfiKseFcJN65Blbaea68E5VB/AnYToN+lAV4zH30wdvDXgQ9u1DkDPlEzbz6GCoyg/6tdKCOJl2MtLHvoflI3/OjrG8Ww902ma0pN1ibjSVJzMQkDqWEnXuO9DGMSzN1AgkO+SGaAF8pOp69qAbrqm4+woVHUyGmfQiOn1qCaA05J4OWttegmTAWYBVeraTvOlJxu0c5WJnYBSPcep9KXifM13DpYsWMqZbQz+QEydANdNhO3Wm8O422MC4e4AL/wD8VwHKHYfdcbAnXUb7dpCtwWHyz+7QGZzvnBXplAVlI1nr32qdhbruHsWXzEgzlRzkzGCVIJIEe5B1BGteJtXTeew4HjDMSJ8rjLJjuf7V78OsXiuUFcPZBIJGjXCNCZ/rQVa8LuI2S5bKMIkaEazGqkjodJ6HtRPwzgKtAIzMw20ED85qwTh+GdALhe6qmQpJVc38RC6t7tNPxGENwHwQtjI0LkAUlhIIjqP7Ggfwjkq0fF8S2D5lC66iFJMHf7w+lJj+Rbk/usSUGkA5jEfMn8qtOW+MeGht33BcMSzBWKZYABLCQug1n366E9vFoVDBlIOxDAg+x0mgBOA8uYlC4vY24iD4QsSx7+eY9ory45ytiPEt3rd5rz22DLbuwAxUz5GELP8AlMe9aEgDCRHsYrksINCqiewAP4b0AVg8ab8HF4V0EwSXvFAe7WvLlH1/rRbwjhlm2ueylqDrKAa+s71YG0GETI9fpt+B+deNjBhI8MkbyCSRPqTrQY3ibYTE31P3b11f/wBjChG2aL+ZGUYzFZdvGef5p8//ALzQgtBcYI7UV8Dchh39PX9fjQfg3os4K5kGO1BvODuZkVu6g/UVIqt4DcnD2j/lH9qsgaBRSikpaBDTGp9Nagrr4kGhXmMgKZOnf301oqudaFebbOa2wzZWYeQn4c28HtO1BjvHD5nAWHBIyjXXpA9f61peD4euRPKSEUIrKzB1CjLKsPPGgMTQbyuhvYsK6k+Cl9ySfgi2UUEAeaLjWyNf99H4YhyggevlH4Mh1B9s1BEuWrtpCA7XbWXVkAGJtiZlQBF0AdIzRPxbVU8x8T8SxZsu63/EFwhlUhbiCCGJ+FXA0K99QANiHHsVUsIEbGTIPp+ulZtxbEtcxgNoQVDXbgB8sqpDXAOkggGN/nQDF+wbT6yTbYBvVdCD/qX+tGdjhrJg7OEtmLmOuN4pGpGGtEgwegIBIn+IivS9wYYpPESBdC5WUiQ6jUabyJ0ZQTqQQdCKvCX72GuECBcS2FMqSApGylgG6yRA1JoPTjHEFxHFMNbRAtuzetLlABkrcUsNNCAFCj2NRuI3VfigDWpBDKV7kq7A9iNqr1z4a6l9RLo4cFpKsZJObUb6zHeop45c/bBjSFNwPnywcm2XLEzGXTegh4xAouJEEXjodwIaNPlUKP11r3xl43He4d3ZmMbDMZgemteMUEniOL8W61yIBPlG+VQIUT10Ak9TJrwXTUaEagjQgjUEHoQfypy+kep/XvTxFBa8R4utxbV4ZkxaMM5gZHAHxzM5iYkerdKkPxOSr5wQybZvhM+ZSJ0GbbuINUfh5tqjssGgNsBx62qw9wDp5QTA3MiDP161Z4XmnDAGbgMzodJG2ummnoeu9Z3YidSB7/8ANXdviVlFg5WP8K2RqfV7jkAeuU+1AXNxFcSviJcsnLmNu2TbzSDoBbd1CtI+Jg/QiJp2FLElmLh2AkwtxWO0NlzJ0qmwWMZbOuDs3s5Z8hZDdRAgljbW2PLFsnTXXUCdUwVzCuQYGHYnLleHtZu2aM6n309qA04fx1hAcT0BXbt7qfeiTCcQVhv9dJ+Y0P4UM4Hl0AA6LOxVio36DUfQ1Kfgt2ZRzPWMqMfU6FWPyFAV2o3HXf1ry4hiVso91jCopdvZVk/lQviMfewuXPDzBMeVgCSNwSJAj3g0Bc0c93sXbNkILNsnzAMXZ4MgFoELIGgGsbxpQUj4gsWdviclj7sSx/E1Cx+E8Mp2e1auA/z21LfR84/00oOmhqbxd1fDYNwIZVvWG/7d3xVP/jiKCPgjRVwYyV66x+VCGFOtFXBr3wqoJYmPruaDdOVz/wBNb9AR9GNW4qk5SYeAFBnIxQ+4AJ/EmrxaBaWkNcKBaa1OppoK27pNDvNGHDWXAGaekwdp8poljpQhxrEMl1rI1QoXg65SI+E9BQBXIKBr+MJWX8NLfbRrozA++QT7Vo2Ew4AAnXaT+TAdaCfsqsgvinMyWtr8hnI+fmNHVyRcIBgEA9N5igGua8ettXMhX0BBMGAJGn3hrv6Gqj7N+EB0v4t1kXSbSTrNtf8AEPsWhf8Atmon2nEhh1IBUE75dNPxP1rR+C4JLWGs20EKttI76rJJ9SSSfegAcLhmtm5a/hMAkTtsY9oPzoc4+5GJDMCZAE+cht9s066jqRptNaLxrDqLuYDVlE/JoH4flQVzi2V7QG2RjHSQU1oInHnlIkEgCR2nXTvpQdiySS3ei61YBw9tjqXzM099dvpQhNB4FKTLXvFNig8gKeq16RXCgl2oUZj9P1+tar7gJJNXPD7Cu1tG1GW4x1OpRCwB9NOlRsSgg+8foUFYVpakuIpjLrQIl1pBk6TGvff61Iu4u41vwyZXNm1EmYjft/Ydq8Ipy0Htw3iN6ySbN17ZJk5WgE92U6N8waKcDz7jFGVstyNSYFt49CAVX/woSRAQTXuEgAjTWg1XDXbWOti9bus8KFZHyhrZBJ1CiCddxoRFZbxnBCzfu29wrmPY+YD5Ax8qNPsiuE4m8k+VrBYjpmW7bUH6O31ql+0jCqnELuWfOttz7lADHp5Z+ZoBWKsywOByx5kxRM9ku2BofdrOnsag5KmYCwGTEzP7uyLiwYGYYmxa1HXy3X+tBBsrrRDwXFZGU9R/Yg1Q2jrU/COZFBvn2cD/AKJZ3LuT7sZ/rRUKEfs1cnCx2On0ouoOFOpBXUH/2Q==', + description: + 'Pioneers of hard rock and heavy metal, known for their powerful riffs, mystical lyrics, and epic live performances.', + genre: 'Hard Rock, Heavy Metal, Blues Rock', + yearFormed: 1968, + country: 'United Kingdom', + members: ['Jimmy Page', 'Robert Plant', 'John Paul Jones', 'John Bonham'], + albums: 9, + awards: ['Rock and Roll Hall of Fame', 'Grammy Lifetime Achievement Award'], + influence: + 'Defined hard rock and heavy metal, influenced generations of rock musicians', + activeYears: '1968-1980', + website: 'https://www.ledzeppelin.com', + }, + { + name: 'Pink Floyd', + image: + 'https://upload.wikimedia.org/wikipedia/en/d/d6/Pink_Floyd_-_all_members.jpg', + description: + 'Masters of progressive rock and concept albums, creating immersive sonic experiences and thought-provoking lyrics.', + genre: 'Progressive Rock, Psychedelic Rock, Art Rock', + yearFormed: 1965, + country: 'United Kingdom', + members: [ + 'Roger Waters', + 'David Gilmour', + 'Nick Mason', + 'Richard Wright', + 'Syd Barrett', + ], + albums: 15, + awards: ['Rock and Roll Hall of Fame', 'Grammy Hall of Fame'], + influence: + 'Pioneered progressive rock, influenced concept album development', + activeYears: '1965-1995, 2005, 2014', + website: 'https://www.pinkfloyd.com', + }, + { + name: 'The Rolling Stones', + image: + 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxMTEhUTExIWFhUXGBgXGBcYFx0YHhoYHRgXFxgdFxcdHSggGBolHhcWIjEhJSkrLi4uGB8zODMtNygtLisBCgoKDg0OGhAQGy0lICUtLS0tLS0tLS0tNS0tLy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLf/AABEIAL8BCAMBIgACEQEDEQH/xAAbAAACAwEBAQAAAAAAAAAAAAAEBQIDBgEAB//EAEMQAAIBAgQDBgMGBAUCBQUAAAECEQADBBIhMQVBUQYTImFxgTKRoSNCscHR8BRSYuEHcoKS8SRTFTNDstIWc5Oiw//EABoBAAIDAQEAAAAAAAAAAAAAAAIDAAEEBQb/xAAvEQACAgIBAwIEBQQDAAAAAAAAAQIRAyESBDFBIlETMmFxBRSBkbHB0eHwQmKh/9oADAMBAAIRAxEAPwDN8F7GqozYskSoZVTlqPj+e1aO5igAEUqAsADKBpoBVV6+S0TJKk/Dpz2PXSgsRYklo1yxofXlXNnmcjZGHuF2nm6AygTppGs/jXLzBEvWQd7V0AHmCjfnSjE44LoWhgQQJj9ipcZxYa2l5GHeIYj+ZGEEHqKLEnotrwaS9jCpytBDKCp1112nrpUQ5zaBgOupjSkOG4qLyZHEaGD57j0qdvieZe6RjnOk7/8AJ0rTGDboF6Qn7QIBeeDOv150lY1o73DV1LP1mevnSvH4S2oOViSACJ59Yrb2VGenYLhsQVMjpFWWVkgdaEtbxTq1w0qM+YGIO1VzS7lqEpK0LcTaIOoijuHYoW7YZVAuK4KtGscweoq3FOtx1zSBzqvH4busoncaj3qOW0icNN+Bl20wqzh8Qog4i0HYaRmAUGBy3rR9m8f3tgLmhlABB8tOlYVLgIhszQIHkJnToN6YcGx4s3QwBA2InlRNWqE1oY9ucH4bd0AfDlYjryn61h7tfUOK2hdtXEEHMDl33BkV8zxFhhqVIExtzpaeqGRKVorD0KtE2KKJbGto6VReFW2DpVd6m+AUX4a73im0xHVSeumlLbikEg7ipFoM0Vi1FxO8HxD4h+dKemGLLgqmKvaqiKplFls0ysNIpYlH4VqOILF2PXelZFPMetJnQztS59wolRrhq4WDUhYFLsYkyqydaINdW1pIGg5+sxr7H5VwtQtjYQ3TOg1yog612qcku4+OGb+Vn1dDopMRrrp5Ug47x/u0HdDUyuYkQD5Dek/F+0DGVUnQmByH6n6Uhe4WJLamsmLpm3chE5JKhlxC0fC5JbOAZP1prxvD23weHvI3iSLdwE6zGkD96GgsPdzYcSPgPTkfyqItd8MqkCNdRG1bIypbM8LnryDYMkxqd6Y4S/kYMpggyD6VT/Cd1IZgdjpRGDVOk+9PxtPaLmmtMbcSxFu6BdLAXGJDrEDbQge31oNbqRBAOnQV5sIh1Jj0il2ItsDCmRNXNJ6JCTiWXrxYgC2qwdxzkVYhuvCDUnz3q/hQP3kVtjqaeYVkS6GFhUIDAEsSM0aT06+1LSSLc23RS/C7YAFxwptmGZfEpOUtAI32I051dj+zs2+9tXluECWA1gbiDOp5nSB1Ok5Tity8JlwZMkAjnIB3mCBty08q7wniF22YVo011OvrVcnZNdg5EM76iaji38Rjy/Ciu5B8QgBoMA7TIMTrQmJSDWiIuWkDXcfdH/qN86XX8Qx3Ynnvzom/QN2hkSJ5WphwvCPduLbQSzEAClqV9Q4Hg7WAyXLsm+yjL90IzbAjeADqQPKOqpT4oYo2Hp2NsYayWuMLrD4yzFUXSSEC+InbUz6Cdc3xfhVgoLli6Mx1a0fuzsAenrzonG9qc7MFhVk+OCSeh1JgkzzPLUxSW3h2N/MskMCzdCJhiRsBJny0pKyzvYzhES3gQSDuKlg8TlbXY6H0q/iFhpDkEBxmUnmJKz8waDNoVptNC62WY+wEbQyp1BoQqavZ6iTVE4kVSi8OtULV9s0SKo5jBzpZcOtMsQJpXfSgmMiRJFWWSJ2oYOf+akt2OVIlFtG7DnxRabQVecxHImflIH4mg7lWrczMq7SQJOwkxJ+c0x4rwwW2IXvsqgS3dhuUkhgwAXlrEQd6uEGkL6jPGeXkuwnAjU16r8VhQsyWzCNGABMx0c8jXqtr3FfFfgrNdAp3iuy+IU+Be8UiQysNR6EivWOzGIPxKEG8swH4Gg+Pjq+SM/Bg3B7oDFJgOCp6a7H51PDXjZYwQTqNRPlRtjs/4ot4iy1wa5Ad/Q0v4th2S54lgnWOh2NSM4ylQCUoStE8Ri2uEFgNBGmlWWEoG29GWLla8aS0iTbbthF2QKEa8w50Y9zSg7rijkgERt49h5/uaNGOzq4EhspYD0gnX0B+VLHIo3AYG45DIhiQAzeFZOkZjoSZ+ESTOgNAsbk6SDslhcA7IhMHMYRZ1MgmSJ2gR70fc4Y4t3IScoJMfdgyd9fhnT5itFjOErZZEdQQoDITrlBGYToJIEbxFOuNXUFm/csqi95bZs4XKWYwozSIaWI16b9azyUlLiaYwi1Z85tAwJ/etWttV13COhAdSsiRI3B5qdmHmNK8V0rdFUjG9sV36CuIelNb4oC6elJmMigdE9Px/wCa2uL4r3uGs96+YnRm3KEGJb1kGB1rN4vg92zBv/ZEx4WnPHUoASo9YpzwO9YCXVyFityzLsAQbcqXhdQpBB6/CNSJBVODaDjNJ0aHCdn07nPeDu7eLwyW8oXWZ31FRxvAFto7v3oQoXXPACnVBmI2Mx4RJ0GlObeMCAg6soP+316ab+RpN2u44HtW7AaWMOw8Pw65JymCCTI/y+dIXqeja4JaMrj7i5VRSSqAhTqNSQSYPL5a0rd6IvN9NBQbGtCVIV1PBZKx9lr7/X/fBwmpKarJrqmrEF6VcgqhDRVsUSKqz13alN5efXb0p06yPWluMXWpIIXGo1NqiaSQ5FdR9QST0mdYrwUmvMuk1CNEWGpivV5n6aV6oFQTYxlxNEuOo6BiB8gYrl3Eu/xu7f5mJ/E1TR/C+EXLpEDKv8zTHt1qS4x2xKth/Z3xvbRbYzK2c3JM5eYI2pn23uKxBG+/tEGiLeJs4NMttgWM5jzJ18tKzOJ4q7OXLQxEDXYVlheTLz7JDJqo0C2zRVg1EYm42uaZ3NXWieZroRYlotY6ULcBphIih2WTqYHM9ABJPsJNG7ekSMS/A/YqHyg3HErInKs/d6O0aHcCI3Na7sqq3Li3GLMxkh7hDFVEZ4ERbX7sKCWZgNjFYji2Jm6xGgBIA/lVdIHlAplwbFOshWIJIVY5Rvz5ZgR5zXchwx43Apxsf9pOI5MVcuSVW4FClgcgdfjDKCzK3jU+UwRKmVXEePX8SoQ5AoXN4J8XiCSZAJ1YQIFHYrEEL3EHuTowYCGAzLM7r4iTMg6b6zSvCOllhIJ7t7ZCwG+zXvM4M6Ez3fkZO0VyJRcZPL+x0/yuTFhk5rSSr9WbfsdxX+Cs/wAPjjbe1cV2t2rkO1pwCQGQghUePYxpLGF/aq7gQB3CEXCTBQnJEKQWV5OoOhWAd4qj/EazatFQloLcyoCytoIUBbYta5IUA6nYjfllBi2uubjtmYkSfRQo0Hko+VM/CoLqMHxJ+TnqPGVojiDNG9nQLbPimH/kwUkTN1pyH/TBb1y0HdplxDCm3grQaFDsbkE+JyYAKrvkCwCx0zCBOsKjG3sVOWgG/wAUuYhwbzG42sFtY/cUBfwgMgTBO06eU1LhyeNj0Ee53/L50aLf79qU9go1/AMTgjZQ38UGuIB4bhyFSBqsEw8HSdZrHYuwLmKu3bbZlYk5uk6ZYO4EASNOm1LL25PUk/WnXBhAEckWfrM/Ks6xqLtGueZzSTAMRbOpIjU8+UkaeVCNVrYrOTz8/wB/6vpVT00XF2VmvVIWya9EUNjVjlV1ovw5otaX2m1p/wBn+FNiby2gYG7NEwo3PTmPnV2asLXFwfZgy0BilrdrZwVgtbyC9Hha5ccLrromoWfTXz5Uk43wMLb7+202y5XKRDLzGsnMOU6fpfKzGlsxjprVgsACalid/Kp3fhH75f3rPkbTSOjgwQ+HKcvC0V3BoPQH8aouLIorEb+w/OqbR3/ysPmpqsbtWL6yPDI4+yBGrlTG3nA19thXqYZr0aD/AMZRJ7rD216EiTpVGN41iH+JoBAMKI05UoNw1diCwySDqgIkcpMR5b0CwxW6FczjsTrua+5dk7mGs2baKMv2eaVUksQpLsSoObY6n9K+Fo06RvX1zhnGLy2U7xhba7DAagSdCFhWEmTKbieVH2G44pq7M326wFkXzdtEBWVGIUQDcLXATHIlVDbanN1rOExp+5pv2o4ybx7yQS7vB/pXwj6s8eUCkCGihb2NzTxxx8EvV5f9AwGajeEo58go9WP/AMQ1dtmq8WYQeZJ/AD5eL51t6aPLIvoZI6QPfxcXg6/dZXHrIfbpPKn3DnyvaKgkC8yqJjVRcKknqSfoKzmFXPdQciyz0ALAT6ain3B0DHDo40d7jFddf/MCg+R19prU53yZUPmVjfjNi+hzEBckEHMRo0tBOUCQdI31jWBVWCD4lyzKbrF8hMBfs1A+NgugIbVmGgUU+7V4q2uGcZSAzhI2kBcylIMFZiZ6AaVT/h3dJu20Ughg6lSYbKc0uvUrIEdLk/d0wdTPjBtHU6jM3iS/t9/5Be0VlsRjLeHZcjMWLgNm1VCJk8zlby0nnWOwtwwpHRZ+k0/7SY//AKvE3gTKq6CdCGKm3I10IZyfYUiUj7uo29q6nRRccaX/AFX7vZzZPbDrFnO6p/MQNNdzBgCmfaQozlst57oHiZyiqqjRVtWlJZEAEAN4uoBmruxrqMUpIJhLhGUEtmCMRlA1J00iD5jcbPtl/h5duYf+IDs95VBe2TnOUfEEfcsBy5xv15raSMeRtM+UYIwp89fnqPpFGYlgqMeiyPXYfWKL4FgA16GEgSxHLoPaSKH7V5VJAjV4AGmm506SBVSVQsqM05UZ43SBrH78qc8Fb7F25nNH/tH50jxbbCKZ4K6ww+UCTmI9pJNINDZTgkGQGNf3v5n8q61FWcA6KqlWzMfDzzbDwxuZIEfnVtrgmIuXGtLabMrFXnQKQdQxOgPlv0mqbSVsfgVuNeQGyYk0Xwbgd7EsBbU5Jhrh0VRz1OjH+ka1sMF2Vw9i33mJdWjfN4bYPSN31+fSl/Ge3MfZ4RcsQBcYRH/27ewHmem1ZPiubaxr9fB1OqnFqMU9Jf8ArG9jsph7S+IZiIksdyAy6HQiQ0kTEqDyqLcVw9q4LFtMxbQhdQCQqw25Ywig7/CJ2rA3r97EXBndrj6wWO3XfRR8hTHh2FVMtw3/ABgyq21zfDqCzkgKNOQOlFHppS+eTZil1EIPtRteGfZwVVZ0Bk8idYP719SaV9s7xOVSArHUhWzAgSFJ01Op18/lRwnjiG2JuKG5gnJB1JgkRloPGu+Jz4hYKLA1ZQQOXhJBJMToDvT4RfK2DKcVHRmr6eIVC/uP3z/SjLieIaVVxTA3LeVmWAdB8p8XTTl7bg0vJ86OpFpdMn7tfyDYjcegoXPXmbz9/wB7CoVIR4qjB1WZZcjkiA2Ferw2r1GZl2Clwo5t8qsu2AYAY6CNfn5da4t1SN6IvNPT28hEAbxp0qxdIf8AALtlUVABmjxSACTuSCTr5Aa6Vo8FhA0MWZbKXEZzOg1AkTpmg6/XSK+eWbbNoqk+g/E8q244kxs2rTQcigMBsxA+9G7fvc1LvQp41CXxG3RHjvAbN0zbAtZAU8LZ1JDHxEQILbmOdYkKQSDuDB9RvWvxnEgEheZA/T22pAmEFzEtbL5CzeE5S0sxBA01G++tGlRUcjnJv9ihDpRl2wDh1aBJLaxyGgk8hvRvGeAGy6LaLXg6kyEIMgw3g1gQVO533qg4fLZUZpJcyFZTAhdG10g8v7Gt/QNfE/QfJNKjOJKsx8tD55hz9dacYbiBV7VyDNsxvuCH05QfE306VYOFG6C4OVFILMxAgaDbdtWG3PnQzYIyFyuoBJLMCARAjKCAxnWNIMjbWmZZY4SlFNWSFKSb7DHtLxlb3dqilAsswJBl23Og8hTvs7xdLVpXj7REfI0CCzLcTxGZGXMToDMLqvMHhOVSFgMihmcQBnRQXfOefhBAkwOVKcLdN2zbs2Q/fbZSwKlswK5RplmNSx5Vzs0Fl0/ua3nWV8IKlvv7A2IAZVOYlmuuW13ACEfUsCec/O+5hTAYBvlptyIFNMVwu1bTvs7F0dw6BGKJqTla4wA7wZGmAdfYnlh2W6t62pTIwPe5oWQ7owBchTECVEnQ/Fz6X5+EZNRjf1MUnQHgsRcS4r2yy3FIKldweVfYeznbG42GVkwzPcU90QLiqmZQp3MsPCy7yZr5RgUe+6Wgc9y4fuSgLZi2Yu2wCyT4ObanSvomP7NLawjW7Fy6tyRccq7KGYKFbKAZjKAACSPDsK5OfqseN1N1YMsLyepLsI8bjpxV0d3ZwrAkXG+4RLFRIYhLhBXQblD5gr73Zu3i7qpbvlshY3XyBcoDQFXQatJAmQYLTuKL7JpYTEnvrQcIveOGQPLeNcpmdSSpExrOmmuut27F3D3bah8Ozs1xb9lUgTJUHKBAG2UmehE1WbJKUHHG9lYMUV6uOgGz2dw6J3a2EyRBlQ2b/Mx1J86+dLgblvFXsPYQtl7xQdws+K2WY7QG1k60fwfhf26m9fuOe8e2PtGEXbZkLcaZhkAYaiZjyOk41x2zYBGly4ToshQWmOerxpMSND6HmYcWXpsjXLk3/Jvm4ZsdtUkT4fwO2zW7txyWRgVYHdhlCZyfFPgzDkSxoPtR2l/hW7pUzXSAxZtFGadYBlj5GPWg8D2wVLTlkY3c3hUGEykAROvwlQdROvqRjuK425fuNcuGWbpoAOQUclA/ZMmt/wADlTyeBMJKEVDEU8T4ldvtnuuXPKdgP6VGij0oFevyqbVFqbSWkXdL6kht60wvrCe0fgKXMeXtTDFnw+4H50+Gos52d3OKADTjhVzImh1aZMD0IncHKfeTSnLtp++lOuE2hlZn+DU67GAc3sBE/wDNDGajtl5ccpxqIZwqyEtC/e0n4JE6bgjqZ5aaQdjNIuPcTa8/MIPhXptJMcz/AGqeO4ibrjeIMA8h1PKTp+xS++KzrHvnLv8AwdBZLjxXZA5rhrxqJNWUcr1RZ69UoBSVEmSKua2cobkDE+Zkj8D8qnYwdx2CKjMSYAAmT5fMfOjF4deYLbW2xbMZWD8X3RG2xO/nR0KKbTuLcqSoBIJBiZ/MGfmae8Nuwm0j5dDp+NVjABbSrcDyAQwEmMzGJ5D9aEGJy+HKQRuW0nptP6VdCZXLQysd3dxNlG2Z4bWNgSNj1jnzNazE8IT+ON7CLItkIzOZRHCrDKJLOYMa6Ag76RkcPwMXmX/qbFtiVgfasdRpGVGBPQA/pWt4Bg+4GW5eRiULKwDAMMxGXxAHMMv3gOUTSc03GLce5r6eCtKRscVwwMtm8FTMgacqSzfzAsxMzAOw1iZGlfIOO2Yu3O7T7JcsH4cxMCYUeew6edfTuI9prFvCd2rZnZu7G4gsDJk81UMY8h1rCX5ZlCiJgAbzv5+dN6aWRJtvx9vuVmcU6RruzuGt2cP3VxB3h7tLxMwzP4IgaZJOXXeTsIAy/HrVk3fCSIL2wNxNtipHXUaj1A56WYziuK+MIiozspJ2B5A+LMxkM20grI3qHch7lxngq5FwGMniCiT7meQmTQQg07DnKMo8UD2mJBFu3q66hgNnQiOQClWInprSfC8TXD4m3mIGUsDlGYKGOWSdiAJ1UGRtyq/jvGblh2bDTZzaAosRbA2B2YEzy0IaSTtlWVjJ1LHUkmST1JO9NkZk3Z9e4dxrBtbc3rlp2gHKCLhBAKAgqGJYA5RcMGIB51iePcS7y8dz8KJmLHwgBiQ5EvsonoBzpDwuywJJ5a++9HTz/t9Nh6VMcVFqXsMcnLuaj/Dq8q4szEm04X/NKkx/oFz619WVywBHyr4bwFj/ABNmHCfaL4iYgTrr5iR719m4XxFLltWQhhtKmRI0aD0ma8/+NY5KccnjsbelapxBMHg0GJ7q6p7thKkNlHPQneBty3XU5oGT43irlhbjQGFu81vVmtkMAACwXw3ATPhIBiYI3G944gWwbjwI1SZJZgJhANS0AxHODWE4pcxmLAtraUWnAYsTrGYghjPgbw7AMYYSRMVo6PO4YovhS8yel/lg5I8pP1X9O5hMfxW6S/j0uFGYQIJT4JEbid+fOajcxlvEX85t3mvXGDQjqdZB8EoWAHJTMARm50xxmHw+D8Fy2uIxB1YM027QOy/1tH7HOlO091QRZSzYB/7VsKfnrXQeR5PVCP2bdf5oyUo6k/0J8bwfc3Xtfy5eYMEqrFSRoSpJE84mlF0miQ5YSSSdZJ1J5yTzOtDXq0pPirBvegZmNQza1J6qNCXYTaWWUjXX1+VOMNw67fRzatlwvQgagTAkiT5DWlvBe8NwC0md9SFAn/j1Omtb3h2Im7ctBvHbPiCtoOba7GIjTmI30oZZJRVR7lRwRySTbqjJYPBLcdVkhFA7xx1LGAojeI9IbpFbfBYBMhBFthlKeCYyERDToTvtB1rPdqMSchAYK1xpaB8QgAkECJgLLedJcLdvOcvf3SVAyjOYgHaCY08/Khgnk2MnkjhTvZbxTgZw5kNmWSAdjOpgjYmAdRppy2pPdNN+LY5rrAsIyjKANhrJgcvx6k0puim062UmvAMxqoirWqpqCi3IjdWD7da9UsRbKmCNYB+YBH0NeqwDe8JbCqVLXh4CMoa4RoSQ2QBtNYaN/wAaYDAIMViLasfuOoDEfcBktMzOb6e1vZqxZt3GhF+FYlSTJA5mSOf0q8OqYq82YDwWhr/KEf8AetcaWZuUuLfby/sdJY6Suu/9wDiGCzuqIHNx9PiY6jUliW2G+um+ort3smpH2l4kR4WtiCSZ0GaZkQY684ElxhGtWLZysAsS5mSCxzAEiTAmff8App92bwwuxcM5RooPyPMzJBJPpWzpZTfoV/Vv/e4jOor1MR4Ts41yFSzbtyJkpM+ZLBh00j8zV2J7H4gFYuIx1ISFykCJzAW1EbDnuvtrcVh7puyjBRAhoJy8oC5gDILHUGIHU0WLXhAukXCDMlAPkOXOugsaRjeRnyzjFxmy9+pW6jTbIA7twQEhdIGksCJiI1kwtlRJHxArrJ05llPPzG40r6zxnCpftlLgkH5g8iDyYbzXyDtBh/4dij+JlOkeGRyJnaRBgTuKOqBbsqxllgZdiZ8QLGRyBJM7gQPlVnEcfaFle6vsb4OiKqukRqXLSOe2s+0hDxzEG4QM5ygDMsZfGJB2+LSNTVdhQBsNP0FC5eCkrGWH46yW7lt0W4WXwkwkNJIYhRFzX7p00HSkFm6DvvTS+nhWRzGVhsdfErf1Azr09QaUmyAY13oWWN+HgSAZgnX05xT3FYXAZT3dy+H+6XKFdxuAgJ0nY7xWfwY1AqR2ol2GRVk72HgE50MdDr8orSf4f9oRYdrdwgWm8WY/caQs+hkA+x61kLhq2yn2N1+htp/uLP8A/wAvrSs2GGaDhNaCU3B2j7jxK/buFGNwMNMqkiJH3kHMx+FZrtT2ot2Qbdhs93bqE828+i1l+wBJN204hL9oqhOmnwsU9S6CRoSI1jTOKCJBEEGCPMaGsS/C/leSTkl2X2Gfm9NRVAnEmJOYkkk6k8ydST5zVdtTRF8SSPT8qHUwYro0ZBhhwctVXhV+GOhqGIWi8BJC96ngcKbt1LYMF2Cz011MeQk+1cuCp8PxRtXUuDdGDQefUHpIke9KldOgl3Pq/D8DZwtrwAIvMndjsCx5knQeukV8+4hh2wd1mtz8UHMc28kBiIzAgEz5V9HVbWLw4PxW3HuD+TA/uKXXcIMSf4eT3KZRdcjW4wUBArNOw1Jjp1153T5VHk5d/Jrywuq/QyPDb9xhYuYllSzb+AlQblxQZyW15rpGYgCOZoK7iwb5uqgQFmIUbAGYHTpVfaLG95ibjgeFT3aDYBV8IA8pk+9ApePSK34lXq7GOdNOL7BbtMk7nWhbtWBqquU5lWDPVRq1xUCKAlkLvL0r1ducvSvVRZ9I4TehoQeMqiidiSM2Y9FUMJ/Uiq+K3Ft4y4rZmW4EnXlladDMaqI5gA76g08dweLsZrtlXSwsKrrdMxoBnIadWMCeQUbjXOX+JXbrG47sWA3LMTGukknQFzp/Uazx6bhK78b0aZZuWqNh/GIDc8BU5kj7sAKogrHpzERX0Dsef+ksnmUE+vP618jNxLq+BiWQA7eJrQnNKzq6jXciBE7Ct72F4qP4e2pZgrXntpK/EzMXAkE5NWO/tTOnxcJyfuBmnyijVccxDrYud2ftMukbjUSR0IWTWE7D47FG46/FpmZS5yfENc2pV4mNNdZ202tjEgmJJB8Y0A8M6CefLfWqeI3GttmRSQZJUQJjc+ZitdCEzq3MUTrasqvQ3GJPXUJA/tXz/t5YL3lF1MjFdMjhpiAZLKP6YOn6fR8LjFcSD5+28+lfMe1nE0v4hgpAAcAsdRAEctQBzAG6g8hVS8IiMxxcpP2awM7TJDEyF57ldDGgG/nVNs/Leu8StqhyhpIIaI11AOhBII5htjOlSsXcqloBjkRIOvMHcUsgItw7GfizR0MdPf6VS4+E+X4afpRBtlmJUEzsAJJ9I3OoNVusIP8AM3yhajKDsGdZqeIWPQ6g+X68varuF2SLffEjKGiOe0gxERy60fxPDIbdtldSLubKNAVdSAcyyYB0EzroaB5OM1Frv5+vsbceKLwSyKStP5fNe/7mcuNU8Jjrlok23KkiDGxG8EHQiQDrzAPKqrggkHccqqJphhex92UxDtjEcu5bLcliHuHS04XQAsYbKfKKu7SWl743UIKXibiwZgk+IHQQQZkVb2URrNt8SrWhcIy2kd7amJ8T5bhgrIKiASfF8OjUHj+I3cQ695llcwAUBR4nZzoNPiY/SnV6Ng/8tEreGUqCVExvr+tLuJYfKwIGhH12pyqbD2qnjljwIehj5ifyrMpbNMo+kC4ZcAdcwBEwZ6HStKcJbCv9khIAYSOQMN+M+xrLYO3LAdSB9YrS3L7C6ioJMiZjUE5SJOmoJ1OlTInQfTZVDurAHNv/ALFr/bNUME/7Vv8A2ivXpUkHlVDPSuHsbfzEWNuE8ffDnwomQ/EgBWY0BBkwfOP7E43tlmsMti33RJy6kSMwZiygaciJnQkdazmbXXUdKAShjgg3bRkz5m+xWN4HpFSTeoXSAxI21I9/Pn6+VeQ1oRlkX1FqKwqoT4yQP6QD+JFRvEchp5ijBAXWo5KMt2C85RtvVBoLV0O+FKMVOS0+z9wa5XqZcJwK3XYMSIA29wfy+depU80YumSONtWiWDu6fOrWtlw2VZIjNCCMo1ktGjSQJ3jTWlNtz1q9S/Ikct4pzBSfsOcHYSy6kvcS6JbLkETIyycw0OpkbaRtTjC8RCW5shlNprt5JUBBchWQATp8MZTM1jW7z+Y/7jXZcAnOfTMaFJ33D8dj6ta7WIhDNbYE27awxAGoLl5I+DaNZMHQVb/9UPcjIbdsoGYLdB9J0YAL6718jF1v5j86tVz1PzqTi5O02gFJLwfTOLcTfuvs7tlpCyEBBkkA6i4cv3jMiNuhrInBLqSQCQfCrLAbWNt1/X5p0arre4+fy1o4xpbdlOdvsDYtJunY68gI9v39KNt3MqkNa7wGNSSIg8iOeny9aBsakmvYxvF7D9fzqeCgjC4wWyJtAwP5iJPXQ6eggVDGXbbCVQJEaTMzO3U6fvSgwaItEhSRpqBsPM8/T8KhTbGXCwVBYgHQ6Osj4TGeNtJgGhjYU73AD7n0JgE/KfSieGAHrMHXQaRJlYg+tBsoo/BS7kMYASDmBMDNvv8ALpFD4fDZ7iJPxMFkcgTBOvQa+1XMBRfCbYktzAyjyLTt7Bh71SVui3pWH9ocWrlURQEtjKvkAIAB6aD5VXwi0Ac52B/5oJ9d/ei7mPAtLljMQdOmsEmrzyb7eSsSXkaDCkOTQfaKQqCBqzHzlQNuX3/w6Uk74jUk/sURefMlv0Y+5Yj8FFKhDdjZ5LVFvCVm4nWfw1rR3kncCPUVlbBiTVrXfWjlGwIT4odYjBNdVGWMwlHHmvwn0ykfKqTwJ+b2x6t/alN8+Genn5afKgHcdKU4P3GLKvYf4vg5RGY3bfhVjAaSYBMDqdKz1pajm8ql91vSrjGgZysqcADeTP0rtqqUoixuZPI1aBYbhADM6jLcOvUWnK//ALBaruj9iooPCfb8YqSmjKSK+8YfDInQxOvr1qplNEmq7209KEO3VN6Qy7NLDuP6V/P9a9XezgIusDvkn6j9a9XM6m/iM14vlM1YuUfaelCtRdq/XRsVhyKL2MiK4mh/GgzjfKojFE8qqx8s2Nl7W4MV1a53kivTRJmOaV6CLZr1y9FV54FCX2Jq3IBIMwtzlVd1pJPUk0NbJFWW2mqTIyYoiwdI8/y6/Oq0sFtAQTyExPpPOmNmwq4Yl7b53uwrZdAttSGAmJlrmsbd2vpRIFhnDhlRyInI+5ifCSY/D1IoF1B22onCXyisJEZGBJOokEDKp5zGup9KATEsohTFMe0ik6ONbrQ47hRw1vCq3x3M11x0ByhF9QBr5saV8I729etpmYguM0NrlGrx55QxojieLdnZndmaTowzRrPxNJgSRvVwpbJJ3oX4q5lB1A5Cfr9KFDA7H+1V4u6GM/ONvYVSL0bGKTKVsbF1DjQRiGAG/wC4oC5j3010GgHQSTp7mrOJ3yzfEWACgE+QH0Bml1DYLG2F4mPhfqDmHvuPenC2xGkmsjNaDgZd7ZADHKY0nY68vejjL3Kqwx15a6yPoI+v40I+H8j8qZXcIxtgd22bMTm12gaRGhkbz5VTh+Ggkm65tAbHuw8nplLL+dRsP4ckrpi42Y5VW48Jp1fwtnwzigR4RHdMDAAXSHbWBVvFr2FKFbCQ4y5WyuJMjNO8+U9POqRHFVoy6mrbGrCTAJirLOEc7Lz3On4xpUwvcurvlOVlbJM5oYGDGwMRQpkcHV0E2rYyP1An5EflNV2W8vkK7a7SZHlcLYynQo6s+ZSZILFs0HbSKs4XfS7cCIvduxyqC2YEnZQxAIPITM6a0y0ArI3GjcH5VSXB0E6+lau92Jx7CP4W9/8Aiaq7fYnGpr/C3uf/AKftzFC37B8ZCbs8x78zzRv/AHJXqMHDb1vEhSrq5ttoQJgFBtG23KvVizYnKVo0Y3oxIqxGqqpA1qMiZcgk1bmiqbVyK7cuzUoKy23LGBTG3wx+elL8DizbbMpIPlROK4zdfe45/wBRoklWyWcxS5TlqzC5R8e1LTcJOprly4aolhuOxCT4Nuh/WgQ9Vk14GqKsIV61XY60MXiLeHv3LgBBVGXUyJYKJ0UHxa8idtax4anfZ7Gi3dR+asDzEwZiRqKuPcvlqj6d2l7DrZw9w2cSMhIYrc0OVZMWyCFdyYjQEhSBOtYM8LH85/2f3r7HwHtXZxVt2vYeQBsWDSdAI8AKnz69KwHGeI4dLjNlIUk5RqZHKOnSneAYuN0xZ2f4cq3JZtAGMFd9MsDXeGJ/00o49iwzQp0Gg+ZOp5nX8Ko4rxkufAMg9daSNcPWgc9UE0rtFzETvrUe6HWqFbnXWuUsll6kVXdtgnSqWapLdqUS0Ra0aKwXEblkEJpJk7+nI0OLprszUInW0Gnj97+YfX9aqbi94/eoJhXKspyb7sL/APE7v/cP0/SotxG6fvn6fpQteqWUXnF3P52+dSvXGgAkk+etDTVl061CJkM1dDVGvVCWb7sJxPE4lzh+8zEIWUvcC6AgEZmMHfbfStbjMDetAlzbaNTF4NAG5LAECPWvlnZO3mxKAgEQxIIkHwkbe9P+1lu1bsgpZtqxcDMEAIEM2h9hV/cOMqQTf44TfS6tsHIjJBaJkgztIGm34V6sX/GHrXqlILmf/9k=', + description: + 'The "World\'s Greatest Rock and Roll Band" with over 60 years of blues-based rock and unforgettable performances.', + genre: 'Rock, Blues Rock, Hard Rock', + yearFormed: 1962, + country: 'United Kingdom', + members: [ + 'Mick Jagger', + 'Keith Richards', + 'Charlie Watts', + 'Ronnie Wood', + 'Bill Wyman', + ], + albums: 30, + awards: ['Rock and Roll Hall of Fame', 'Grammy Lifetime Achievement Award'], + influence: 'Defined rock and roll attitude, influenced fashion and culture', + activeYears: '1962-Present', + website: 'https://www.rollingstones.com', + }, + { + name: 'Queen', + image: 'https://i.scdn.co/image/af2b8e57f6d7b5d43a616bd1e27ba552cd8bfd42', + description: + "Revolutionary band blending rock, opera, and pop with Freddie Mercury's incredible vocals and Brian May's distinctive guitar sound.", + genre: 'Rock, Hard Rock, Glam Rock', + yearFormed: 1970, + country: 'United Kingdom', + members: ['Freddie Mercury', 'Brian May', 'Roger Taylor', 'John Deacon'], + albums: 15, + awards: ['Rock and Roll Hall of Fame', 'Grammy Hall of Fame'], + influence: 'Pioneered stadium rock, influenced vocal and guitar techniques', + activeYears: '1970-1991, 2004-Present', + website: 'https://www.queenonline.com', + }, + { + name: 'The Who', + image: + 'https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcQoKRCDsHx1DyrnXBxOgpPUHZZIwebCq91Ge8oQylUJXlWzlOwtzQtepNBzfbT_497Ymm1sU1Oq2OV0qGmxa6igziOxHJX4Eo8oyk088RpF', + description: + "Pioneers of rock opera and power chords, known for their explosive live shows and Pete Townshend's windmill guitar style.", + genre: 'Rock, Hard Rock, Mod Rock', + yearFormed: 1964, + country: 'United Kingdom', + members: [ + 'Roger Daltrey', + 'Pete Townshend', + 'John Entwistle', + 'Keith Moon', + ], + albums: 12, + awards: ['Rock and Roll Hall of Fame', 'Grammy Lifetime Achievement Award'], + influence: 'Pioneered rock opera, influenced punk and hard rock', + activeYears: '1964-1983, 1989-Present', + website: 'https://www.thewho.com', + }, + { + name: 'Black Sabbath', + image: + 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ8qFdD0l8GNdMw30NkViSV7BNfUab-7wZXWs1jC3qYIt6llh-WVpJ9MLmgv4QKo9Gq-FOOYJ3-aOr9jQjDhcToYWj2VO9LbBIh7MO6e34PNw', + description: + 'The fathers of heavy metal, creating dark, heavy riffs and occult themes that defined a new genre of music.', + genre: 'Heavy Metal, Doom Metal, Hard Rock', + yearFormed: 1968, + country: 'United Kingdom', + members: ['Ozzy Osbourne', 'Tony Iommi', 'Geezer Butler', 'Bill Ward'], + albums: 19, + awards: ['Rock and Roll Hall of Fame', 'Grammy Hall of Fame'], + influence: 'Invented heavy metal, influenced all metal subgenres', + activeYears: '1968-2017', + website: 'https://www.blacksabbath.com', + }, + { + name: 'The Doors', + image: + 'https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcQVPqssHHK-u-EaYkVo8YuiCBINkJYVsAkqIlhojm2swhdc1Xn9lWkzokObi1_lAcr1CZ_dEbKnBTD9hfkUcPJ9vrwg-ARlgurH2rA5I4iwXQ', + description: + "Psychedelic rock pioneers with Jim Morrison's poetic lyrics and Ray Manzarek's innovative keyboard work.", + genre: 'Psychedelic Rock, Blues Rock, Acid Rock', + yearFormed: 1965, + country: 'United States', + members: ['Jim Morrison', 'Ray Manzarek', 'Robby Krieger', 'John Densmore'], + albums: 9, + awards: ['Rock and Roll Hall of Fame', 'Grammy Hall of Fame'], + influence: + 'Pioneered psychedelic rock, influenced alternative and indie rock', + activeYears: '1965-1973', + website: 'https://www.thedoors.com', + }, + { + name: 'Jimi Hendrix Experience', + image: + 'https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcQkDuNxy0xKisGP4lUk7N0QqrOQpyHNy9U2lpQhKBH5BqnRn6drBm8HYWVwcFfea1brVTxs7jSSVH1hjyrwTlzGRZCgfC91C-YgEStSzuvMwg', + description: + 'Revolutionary guitarist Jimi Hendrix redefined electric guitar playing with innovative techniques and psychedelic rock.', + genre: 'Psychedelic Rock, Blues Rock, Hard Rock', + yearFormed: 1966, + country: 'United States', + members: ['Jimi Hendrix', 'Noel Redding', 'Mitch Mitchell'], + albums: 3, + awards: ['Rock and Roll Hall of Fame', 'Grammy Hall of Fame'], + influence: + 'Revolutionized electric guitar playing, influenced all rock guitarists', + activeYears: '1966-1970', + website: 'https://www.jimihendrix.com', + }, + { + name: 'Cream', + image: + 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxMTEhUTExMWFhUXGBoYGRgYGB0XGhodGhcXFxcdGRgaHiggGB0lGxgYITEhJSkrLi4uGh8zODMtNygtLisBCgoKBQUFDgUFDisZExkrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrK//AABEIALcBFAMBIgACEQEDEQH/xAAcAAACAgMBAQAAAAAAAAAAAAAFBgQHAAIDAQj/xAA/EAABAgQEAwYDCAIBAgcBAAABAhEAAwQhBRIxQQZRYRMicYGRoQcysRRCUsHR4fDxI2IWU6IkMzRDRHKyF//EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwAlV4xnUSm3m0Da7EUgEG5Hh7mF2uxm7JBvYk6wOTVAuTb3JeAMzq9GUlS7GwEB62uCu7cjkLDzJ3iPPmJFspcX2AjlUZiB3SX0/m8AawbGJKRkVLSQOjnreDdQUyimdKPcUWKf1hew2SmnR2kwZpig6U/h5ExGm4kpZbMEOfSAaqnGJcwORtfn4QCk4gEqBJAJ1HK/9RlM5Uy2J1CxorxjtW4P94A9QesA0UdehUrurS/59YZcIrzkZmPTSKuoxlLeGjw6YPiSUsSG2LwDZRYktKmNwPKGCmr0qhORVJV8uvS39x2TPIuGcHzgHlJj2I1Gt0JPSO5MB7AzFsYlyR3jfYCMxLEMoISxV9PGEWtnqWs951M6l8nfSAnV3FExRZNuQH7XjlT1izeYoWDtqRyd4Bzq2XKLJLqOj8yHBJiCawiWpzc3J8NYA9WY8EghLliz8yenK0c1YqEh1Eg+Jt06mE04mElg5O5015fzSOdXWTVDMyUpGj+zB2/uAdRxRMT8pLdf3gvhfFhXqb8jb05xTypyz8xUU/6gv/POJuH4jKll0yVK2da+WjszeDmAupePlIfUfvE2nxkHlFXorMycyWlga5SWIF9FCJUviGUkOFt0LsfNoCxp2JKN0B9j/UBq6tn5TuL7NAPBsd7RJVpcszN5h4PU9ao6F+adx4c4ABPrZirAMrx1bl16Rzn4mUtmNyQD0P8AcHauUlQKmuDe1x1DQicQzFSyQrKS/e6/hPQ6QB//AJHmsSwFucdp2P8AduoDVyduUVlIxElTA/UfnHWZXldlajQtYwDyeIjnzBbhtIKYdjqlr12ZoqgVRK+6bDfaGnBavIkXuq4L9d4C2sIW6nd7fxoPIhBwfEcqQxDn7x/mkPFGXSLuYCUBGR6IyA+PRiBdhBjC6JayCx56c7RC4OwwzqlKWcC5i78K4fSlLsHf6QCdR4B3MygM2ge+XrGtTgqmD947bCLLGGjlbwj1dAlrjSApDEXSshYN7+HhEOmuoMwP3XZvAvDzxhhySoM1x/PeEcpax2MATRLUpRSUhChe3yny2g3h1aFDKb2b02eA+G1+ZJlqdx3knfqI6005KFlOxVmv1gN59NmXmQSPxAlm8+UF5FQMrZhb8Jf9jEZS0JfQuN9NN4DqqkhVgBfbT94BwpArLmChby9oKS6gljv1DGFg1qclrkjb9YlYbiCkkA94PaAtCgq2QATEPEeIUpdKWJvp0+g6wl1+OFMs5DdlEnYAa/31hGreJZjlKH6nc+PIdIB2xTiJrFQCXc9Tf+NtCzUY7MWciDlBNxqb8+sA5BUogrJMxVgCLIG5bnyiSqYJScsvvLIueT7DrAZWVOU8iLku5c6frHGdPWoJcs4MdZGHZiQW0zKJ2Yf2Yj4nND2By6DZ20YbCA3TMALAFh6nwjAlc0upVh6AX9TtHGikqX31gJCdBt19PrE6oKUnIksEgW5kh/eA0OQCwZPM6n8z9Ik0NOlQKilkjSzdXflA5EkrWl+g6XIAgjjWIFLpT3QLWGwtbrADsVrVqOUem9+mwt5xF7FYQUvYEC+gJd/pEnDmSRMNyo2HU8+Z0iUkBQPJ2bmdz5WgC/DeaUBd0bjn5cxrDpQpCklctRUBs7keI/OEbDVlMzIN7j1D225wYw2pXJmKUjQEFtilQdj4G3lAH5NW5UEuCLF/HrrCLxiDmJmF1E7eFriLBNOiekTZasr+j8i3X6QkcVYbNQFKUXBfKAXfSzne8Ah9oXygteJilrSDd1EX5geEQ1ylJAWNXLHw+t40plrzBg5J994DvTKWo5QCx1tDGiYJZCQrYWO0RKOpQGBUytLAfWNKlAzOltXfk3WAaqLEFuGudRsB67Q+8O8QKAaZc6vpFQ0lQc1j69fHrB2gxYhkJJcfNs+2vKAuVHEErctGRXdNjCcrZQW1L/tHsAk/CSQ9Qo/6xedMkBLRR3whm99Y3tF6UibB4D1IMaVEtTW1ialIjZSAYCr+N6dQUCE23OvWECdSla+4Rn3SSz72P5ReOP4N2oZ2in+KMCMqbADqrD5iUpUzKTqG28IhKrDnzaGCCMWUhGVaiWte9uTmB85KVkqTz/ggJ8mqz2OsbJlqfvDu+/7RGprDkfaJPapBIU/XrAczMIUQLX0P5QbpVZXubByoNZ7nzv7wuqSkHMlyH8Wjeoqj2YB0J7zWzAaOfGAl1tbnJN8pYeQ0/WIktGdQRJSMx3Op5noBHCjSZyr+XIf1DHRolUcszFEKWQyR01PqW9IDmvD+zGVJdTDMd9DYeNzHOXQ9jMJJCrP0FiSSfLXpHKXXqKHUe8pbq8wH9BaOGO4iCpSU/Kf+5mYeFoDtV16CjKPl15FXU/68h1iGkpSDMVfpoE+fP3iIqaAHd1FPu+p5ty0jgqYZhAJZKTpzO56k84Av9oBT2irJDMNi10j19hETDZKlrJPzKc6aPqY3z5hpYBkp2H6mJ0hIQQ5Ys55trARUz8qybMOnLSI2KLClBWymP89faMxioGgIcjTlENNS6HVZiwP1gJBUUpBF7FmO5LP0tEmVUgBIGrW8SdW8oHSKoKdO5unx6RIoLKc/sGgGCnAlhKn75UD4XEF8PqUqm9mVauB4Akp87n0hTm1Lrz3azHYc7xCRiikzlLBuF5h5O/8AOsBZ2F1/YrKVOxJH9/UGOuLze4op7yVbgsfMFwqFWRjaZlwNRpyYXHhHcYiycuo5A3G501gBfEElAloAIHfUSAlKctkhrWOhPnCzWU5llKg/MFm6xNr67/IVEnVgCHy32JjnVTjMsolntz9ICBLSp7wWo0FnUH5dYhrpTLPe0EETNeUCMxI2a0B0ky+85Ntn+jx0lYhkLgMef0jSZWyuzSnIQvUqBP0iGs9HHv5wB6kkKWnNn16tHsc6KvyoACj6CMgJHwhXLliZNmFgC1/aLFmcdSA4Cgw3e0Uhh8qZ9jJQ7FZdojqw6cQe6pwCo2sB1PPpAXPM+JNOC2d35CC+D8ZSpxYKHrHzgmSo6A89ILcPT5iJ0tnZSgm3UtAfRC8cQV5X2eEri6dKmKJUpiNImcRcMLlyFTpSzmSl2PQXilKvFJi1OVEwBqsmIch3jhQlKFH8JgGuoUdY2RUQDWlaD/Nf3iHiOVixci46iB0ueSloiT56nDwE/D68BTH5SbvEmslJKmQ7E2bTz/SBtPhkyYnPLGcD5kpupN906t1EMWEUKyHSgSkjVawSfU/SA1lICVJALZUuS25P9RErJ5nLB2+VI5AQQx0oQCkKuWJV0Y5f50iHgi0JSFki2nkXP5QG2LzwhfZJ0QkAn/fU/wD6gMqcTdt7eH9GOVZXFUxbksSfcvHKXO0G+0BNIJOUcmj2T3bk/rEZM0gO+8b0ySSnxgDmFd8uBYecT1U65s05Q+UeF+UdsEwwsTnygn84cMKn00hDWJ5loCt8cwlaSCxJe5ZmtAyokpTTkk9/MwHT7xizMZxymynMgN/NIXpdbhs0FClFJVa4sPPaAV8IwRc1IXte/hEqTQzJa2mS1EHkHB6iH/hnC5MpJyTgtB2sb8x5Qco6NOZSkkMn6wCDW4SpclMyWkswDMWfTSEOaopUQdQbx9NSJSFIuxilvidgYlT+1lhkqd+TwADCKllDlBmbUM5HL+4U6dZEF6Sozam7f1AR61ehs/Qfy8e0s5h3nJOh5RrOlEnxFvWO0qRoHHnAd5s0lICi42iZhGICWCDe9v0jmqjOU3fe0bYRhqllmJ8BARq6YVqUo78oyjngHvaQWxfCMic0Ly1DSAMrSglwsgGPIHInBt4yAtL4Z4QldAhwLkn3iRjvDc8LzyDlJFwdD4coj/CrFAKVCH6e5EPa57nlAV9K4bqG/wAqpbaMkFzBzD+GJacuZKQ12YawzLCUubPEGmrHU2rmAKYtLenUDplL+kUP/wATCyoJbciPoCvDylDofpFSYROH2lSQ+pgEmvwLs0uZaj1Fx7aQKp6HMe6CIveZhKVaecDZmDyU6pD+EBWNNg5QCoi0AK9LFotPH1y0y2AiqsTX3z4wBDApYzBRmZALsLk+ABt5wUrMTzqUVOUBXdD2ADDQed4V6MnMAPOOsyeQL/zlASa3EjNzHcq9gGHtEIzlANsIm8M0nbLUj73zN9Y1x2hMpYS2odoAdMuXjwRsmUoaiNkJfm8Bsg2hh4cw5SyFAWGkDsMoysgNvFtcLYQkBL2GmkApVtYZQZaSBC7V8RqLhILmL2qOHpE0ATJaVeIhfxbg6UhQmS5CVAfdTY+VoCk66qUq6kt0e+m4/OIqG5RYfEGEyJy85lTUqZiEpZ25hjfqGjrwzwUmbMKjKmBHJbAdG3gF/hGimrmASyT4GLRxaWulpMrFzdSuu94KcPcIy6UhSR3oNcW4aqoop8lFlqlqCfFnHvAUwr4iLR3UhwOcZM4qTXS1yZqAFEd1Q2O0JeK0C5KglTuUhWnP67jyjKCc12uICMzEjcWghIAa+kQAoqUTzL+8EKmeyAlhd3/L6+0B0RNzKH86QVpsORnuXhbppzG4eD+H1jkWvANuG4alSFvytEnA8MXKfKM3QEC3R7RArcQXKQlWUsQ1x6QJHFtQn5CAOTPAPVdgvbSy6QjxIcluloq7GaIyZhSfKCqOJqgllzG8Rb0jTiNKpktMwsoP8w6wC7n6R7GmSMgCfC2OKkJYHQn3ixsK4uzh1RSlPN2EPXA+E/aDZTAG8BaIxRcwMgOG1ELdb8R5NMUyEoUVILLJG738YcaJMillncgX3cxVvFNAiqnrmS0Mo3tv+sA4V/xQp+ytctpCpgONionBSUsoEk+B0hEnYVND9xTDW0PfwwTKlqUJllK3P0gH6XigGsDcTxYMb+cSMbwsqSVSSH1MV5iQmpPeJgOWO1xUVQl1CnVB6rXaF2ZMZUBMw+U2ZXIN6t+TxGqJo19OggpKrAJKgwc2SwZzufIfy8CpySBdiYDpgmJmnnonC+U3HMGyh6RalVw3LxFMuokzBla25G5SRzEUyowX4b4hqKReaSsgH5km6VeI59YBgx7AjImKcqb/AGFz6QDopZVMCQLkwYxvi9VSO8gAnzuwFoH8NozT5Y5qHteAsTAMDSlAIF936w34cgjKwsI8oqU9mGETqSjKS5Pj+cAXpakGJoIMBZCSNb9YJUyoDFUgVqPbWOolpQLMI2XMYPC/i2JjOkFQSh79fOAPy5gVpHdOkBFY5SywM05A6Zh9Iky8epzpNT6wFW8UYHMRNUJkjt0Z1FKg2ZIJJAO8V5xFh5p1BLNnct0BsI+jqkIWokKBsHa/80ijvi4B9uCRomUn3Kj+kAqUaXvyiZNpsyCp77DnE7BsMURobsPUtDQOFVEM9vCAryVTqfQwy8JUSu3Q4cOHhooOEkgZlAmDuCYOEmw3gD/FfDaZ9IlKAAUlJty0MIkvhNMlYK8x6ZXA8TFyyEPLSOgjqqmSRcCAqb/iNNPmZk5iSPkTYPzPIRLxPgXsaOakKe2YdGvFny5aRoAPKIGPqAkTH/CfpAfMpjIxZubbxkAuyjeLBwCpVSALSf8AzEuR4Qgy5Z1a3OH+skhciVMQzBLH+bQHSdxhVKCwmQVDmAbRAwzF8RzPKkqLl+6kGJmHSqoI/wALH94GVUvEUKzCXMSf9d/IQBfEOI8SWCn7EUAjvf4TfqXEA5OMTELBVJUC2gG72LR0VVYspwpNQedjG2E0dYpdpKgdyoX94BmwPjVQIStJCSWL2Ie0dsflCY5S2kBMdw6ZLAz6n2grS0p7EqUdt4BExNTEsekBQrvc4n4grvEdYidhcMYArhaAsh02HUt6aeUQ8TcqOw5RKkVRCcocP7RGqkn2gBZESKGSVKCRqbRqUwzfD6g7WqAIskPAR8UwUyUJUX73tZ4I8ASQupS+wLRY/F+AImSfldhy03tCHwfJMmqUDbx8YC6KFACRE9AG0BsPqwUi8FJU02Zm3/m0B2cuwTyvtHfM2unOPZXgGiQpDwEQoMw2cJ+sRMe4el1EkylOncKGoPPrHTE8YlyRdQHmIHHi2nSM02ehA6m/kNYCq8e+G8ySFzDMC23Yv5wCwCjnz1iQlRSAHcubPtFqYjx/QLHZd9SFd0zAGA6sbn0jlQYhhypwRTTAZiQ4swIGoB3IF2gCWFyBTUwQFE/iUrUneKXxuu+1VsyYbgqYf/VPdHqz+cWj8SMSEikmKGqmSm+6rW8A58opXC5zLBPOAsvh+nKlJUQGSzDqLOYdqOnKr7Qm4FNsG35Q9YWhw0BNVSJCLQJoFoSQtawkBZs+sMiZGYMYqrjnhiaZylJmHIS4F7c4C5qatlEAhaWOlxEiRNBfKoEdC8fOWHS5+dMoqWz5dTF7cF4MimpwlJJKu8okuSTAF52kJ3GdaRTzsp0QfpDlOTYxXfH8zJTTE7qtAUsEmMjuERkAQrqCXLp0y8veK1F/VvpAOTWlA7Mu3KLF4jwQqkpWLKSSfQl4Q8bw4gBY3gLH4QxqQmSHZ0/WDtXxPJ+UMTbyeKFkVakGxIjFYosFwoudYC8qnjCVLcaveJMniWnUAqzNc8niglYksi5JjpLxRaQADblAWZxfj0orAQoEa87wrY1xUVS+zQIWBPJudTGqZTmAlYbh8yepkpKlE7QRpEopzNRUSiZjd0HY84tX4ZYJKRTImBlKU5UdweXSDHE3CFPVsZiWUNFCx/eAoCUXUw3P5xPxaiUQWD5ACttnFreRPlFtYD8L6aWsLUpayDZyw/eGap4XlZ86EgBUvs1pYd4Bwk31IClC/OA+YFGLP+CuGuZ048wgeQc/WBmLfDmb2j04JSqbMQAoMU5FZb9LEv4Ra/BnDwo5CZQ1F1HmTrAFKimduW4bWK641wnslJmoDNYt1i1UyrQF4koBNllJG0BXWEY6zObi0NmHYySRsOTi8VTjFIqSop22idg2NZWCngLnpcQsCD5R4cWKiUuB53hRwbHkqDD00PW0MaKRM4XAt6uPCAhV/C9PUOZ+db6d4oI8MrPC9P4EwlbgTZwUP9ySOjHUw8DD5jd1rDkxH6whcV8JVSiZkqWSrcg38oAPXfClSnNLVoWNkrsfAlP6QoLoavD6hHaSyFJLpI7yTqLEW5xInUtdIUX7VHqH84YpOJLXTkVC3LedruTAAuPscXORTSj91HaEdVWT6JH/AHQrUyrx0xSr7aapexskckgMn2Eb0aQ+kA78IYkQoJMWlhM0BtYqXAZyEkKa4h5w/FXcPaAbarHQh7gAWeF/EMfpSHnTkjoLn0EC5OFJmzM09ZUl7JdvpePKnD8MlFzJY9XUPeAk0/EGFA9o68ydm16w04dx7QlLIm5T+FQI9IR5OO4clX/okE88oMPHDlbSTQCmnQnqUAeloBqoq5E5AUgggxTvxMxN5ikDQFv1i06dcuWshAYHYaRUHH8xE2rMuUB3dW3UdYAJhYlZP8iXL82sw/eMi7eCeHBJo5aJ0qUpd1EsFFlFw5I1vHsBFThHaJWldg5b6veEDiPAhJzIJzC5SdPERatNOJJBhe43w4zJRUgXTfx5wHzzXU2VZfR4jECGDGKcOS0CFoHKAgTCI8SmJhljlG6ZUByRLjqnu3jqmXHtNQTahWSTLUshnYWD2BUo2SOpIgLU+CtcVS50vUJIUOjxZE1BMVRwLKOGzFdqtKysBxKOYDRhmIAPzDp1i2J2KSkmShTpVPAyAAm5G/IdYDemtEHiLFZskyEyhLzTZmR5j5QMpVtuWtBaUZYmdmT32zNzHMDk5gdxVhqZ0q6M+QlWQ6KsXHjuOoEARRKGpYnm0a9neAfANb2lMEEKGRsuZnKDdBLE3A7p6pMMi0QGZbRHqKdwYmoTHikwFWcZ4JmJIDxWFbSZFaNH0NjVGFQgY9w2lb7QFbU09aC6SYb+HeNFSy0z1hcxTClyj0gQqbeAvjDuNJZ19Xhgk8QSVJfMI+cKacQQyiIdsK4LxGakLHdBDgrVlcG4tqIC1Z1VT1CSnuqG/SKW+LEyTJUKeTqrvL/1D2A5Al7dI6Y7VV1AQicCl/lIuFeChY/WEKumzJ8xcxRKlG5JgIcrWCqKcgAwMpqdS1hCElSibAaw4UVDMSOzmoKFgOx3EAIp55SYKUuJKcMq8Qq+jymIiC0BaGAI7axUz9YcKThWQR3zm8YpfCsbmSS6TDJI46nNAWhSYBSJLCUjxa8G0YNJbuhvCKY/5fN1zGC+GcW1MyySYB+rqaYcyKcAr0zEsEPuf2hJ/wD53WypnaDJNcuSlTG5vZTRY3CkkiTmV8yy5P8APODiRAQ6NU1SASgS9spOY2G5FoyJ4EZAKeG1ecJUxAWkEOGfy25x0xWR2iCgFs1oCY5xOino/tKxcMAgG+c6J6X9o84H4iVWSkzJiAklRZtxqk+n0gK242wE083K7hQcH6iEqfKaL8+JeFCdSlYHelHN5fe9r+UUtIwybUTOzkIVMUT90aPzOiR1MAGTLiZTUylqCEJUpR0ADk+QiyuH/hCsgKq5uQf9OWxPms2Hk8WVgnDlNSJaRKSl2dWqi3NRvAVbwx8KZk1l1ilSkf8ATS2c+JuEj1PhB/i6gpqSmTR08sIStQdjctclRJzLLPryiyMsIXxKpWVJnEsgKyENpmSpObUaPvs/OARqanM2clCZikhSkJY2IcgDzPd03i5ZtbJE1Ehjn0T3VMGD2WzAsNHG0VdhakyKwKKgpl5iLF0HL3ksByQphtpFiq4iRL7k0BBKVLlqBdEwAP3Dza7HrrACOJKycVidJkTCZLtODHdlJMpwpSbEP7wz4diCZ0tMxNgpKVB/9khX5t5QCqcRM+np6qVYqUApOZu6p0zGcOopDlmctChxbNrO0lmhkTkokgZTkUm4GV8pHecOCGe8A210gUdSmpDiTMOSYHOVBUfmyizFTEk6X5mG6WoEAguIR8K4iFdTrp5yTT1SkKGRaSl3Hzywsd4dLke8bcB41/8ADVKyTJIZd99Ab3Vm1fqOcA9iNFxpnjhMqdWBtAcK7rCxi6mL7QdqVKOunjCvjgUxvaATuIpgUDziu6uT3rQ14ytTnUwDkyCtWkAb+HuA9vUS83ygufK5j6JQLMNIrr4W0ASlc1uSQfc/l6xYIWWtAV78bMdTKpRTAArn3vfKlJDkdSbDzikAsiWWbvW1v6RYfxwlNUylkuVSyG5ZVbesJNTgcxFMKhQDKUAL3DuzpaAMfDaZSS5kxdRNEs5WSSPVrawa4u4rpJhlIp0qOU3mqDODsBrrCDJw6cqWqamWrs0h1KawGmsNHBnAy65ImdoEIz5TZyANTrYvYCA6VNJnBPOF2ppikxZGKcMLo2Tmzo0CyGPgRzhexSgcPAKiBHeU8brkMYkU0l4DtQ0ylG0WDgeHZAkAX3hdwqUEM9yYeaCYEyzMVokE+0B0oeNewrTImEdgwS/4F7nw5iLKlLBuI+WZ1Ypc1SzqpRV6mLm+H/GKFy5cib3VJAQlWx2D8uUBYjxkax5AfPPG2KSp1AQgkE1JN9CA+VudiDG3wvx9CP8AEtQSQoW5gnukeCrEcjAXjFXaCVKTlupSzlYXygOw08OkTOCeA500pnTGSM3+JKrdo3zKO4QPc+4XvUBCwQohlJLjpu/6wJwSpoZSuwkKloJZkggFXg91GEnG+IDKlfZUKzF3mrQfnUSxSlTfKlgH3ZrR3wOX9hNNOrEjtJ6iAVf+ylhtsouH5CAs949eNEKSwIIY3BdwX0bnrHQCAyAnF1B21NMTuzjy19oO5Y0WgHaAoudMQqnC1ghScwKmICW+ZGYfdUoWewdW7GJ9FP8At1IKW+cf5KZRUxP45b82Nn1tpaJfE9OKWtXLPdlVSQAcrhKg+UhLMSCwOtrb3FTqg9lLWJYSUn5kM0sgAyyAL5goKYfhcXIDBvwcaxK0qBqBSyipKjLSFEH5lAoUCSHLmxIdosTFlTKiSj7JPl9qRmGeyZiWILM5DP8Ad0Ni0cuEMaTMQtBZFR86wLAkgDtUjkbO2hvvHLDa5NTTzZapGdcmapMwIZMwXOWcgczckDcKZ9CHvEc6mT2AqUlKc4SibtLmAuO+C6C6WfTV44cS0cn7TKVLnS0VZy9zNlVOQ+njYsYTKmlVU/8AhlVCxlX3O2KgklTsm93N73D8nu3pwtCpkmZX0k4TacJCJiM01ByF0k9m6tbsp/HWAdKGqExL5SkiyknUFn2sbEaRtNQ8K+MzZlNVy6tz9lWgJWLpY97vKSdCxs4GhGrQ3SVhSQpOhAI216HSAEVyikQkY5XAlosTE5Kciio5QASTyADkxUNLiCKpauzCgzOFi7HQ2MAOr0BR0jlJw5g7Q4UfD6iXYARrWUffypGnpAZwridTTz0UypYVTzAFJUBdJUNSdxmBB5RZVLdA8IrHAElWLyWKyg0aZmUEhKSXBJDsXLXD6iG3hzG/80yjmghUtaky1nRaQXSH/Fl9WgFj444ZmppU8B+zmMfBX7gRWvEvE6KiVLky5SpSEsVjM4UQBbwF2i/eOMGNVRTpKSApQBBOgIUCPpHz9xZwpMo8hUUqSpxmT+IXNjpYwHPEOJVLkimlJySgGN7qALjN+kMXw444lUEqbLnS1rzKC05G1ZiC5HIR0+HvCUtaDU1DFLEoSbC2/XTSAODYBUYlUTOxQhOqlFsktPIWBYnl4wBvij4lTaoIQmUmUhK8x72YqZwASwYXjaXUiagKTofWFHHsJXS1EyRMYqQWcaGzgh9oe6niOlqZcmVTSJiFy5YCiyWYAA6F1X36wC7V0/KOCJagQwgpNN7xIpcgLmAIYNQrIClQQ4xxDsqXsx8y7eW8dqesGUFmAhN4mqzMXrYbQEXh+iE6oRLP3vXy6xcdJwnLk1EifLITLSwWFaWFj0JPPdopPDp65cxMxBZSSFA9RpFkzOPJ66YpMi0wFJmXyuR3sobXo9oCz5+PU6FFKpoBGo5RkUxSz1FO56xkBy4C4cNZMNbNAElDS5aHvMVYd4jRLm51P1ZeOcZ+yp7FNpi0s6bdnLe+U81nlpblGRkAK4WwiWhMysnB5FOSoJFytYygOCdE+6i8NOP05xCTQ5XTLnHOpRCSUvLJ8XYqZt2J0jIyAYcGwpVORJCiuUzpKiMySGGVmuLghuRgvLSrMrMzfdI16gjxjyMgO2WPCiMjICrfjfKKZUiaNQsj2zD6H1gJSkz6chBexVMb7ic2d0hYAV3nOr2trGRkBDwqonWnBf8AmlHMk6Ol150qGjES1H152tHhyQmZUKq5bhFVToPIhUtRSoePfF+kZGQEHE+FpyqCaifPM9ctRmyln5wlL91SjYkpcbXMMHBlaZ9JKWp8zFJe5OUlIJO5IAPnHkZAFK+gROQqXMSFJVqD6g+IN4HYGhaQuRMY9mQEEaqQQMpVyLghukZGQHnFtCZlHUpT8ypKwL2+UmKi+FuEzlVSlobsRaY5vldg3XMPQHnGRkBbWNzkU9PNmAfIhSgG5BxAXAuHZiM6ps0ThMAWhWXIRmBdOVywulrmMjIDhw/gBkVkuZMvM7Jco5VdwBIlGWkDe2cu2vlC/wAdSlSa3tEFjMSJqCCRdLi7cmfxjIyAeeG8cFVTBSwywEpmACzqsCOhPpFMfE/E5cyYinlgnsMwUo/eV0HRtYyMgBcniucJPZZU2RkQoWKbMot94kekS+CuMV4fLnJRKStcwghSiQAwa4HzD0j2MgAWN1kyomKnzGzKN2DD0jng8+ZLmpmSlZVpuOvMEbg8oyMgDCsYE6YSpCZebZD5Qd2G14J0WHFREZGQB+tpChIRuYVsVpCkttGRkAyUvC0nsaGao3mzMq03ZQdxcacofOK/s9PTdgadIlLSoJIA7q2sw1HjGRkAg4T2aUNMzu7hm0YNGRkZAf/Z', + description: + 'The first supergroup, featuring Eric Clapton, Jack Bruce, and Ginger Baker, pioneering blues rock and power trios.', + genre: 'Blues Rock, Hard Rock, Psychedelic Rock', + yearFormed: 1966, + country: 'United Kingdom', + members: ['Eric Clapton', 'Jack Bruce', 'Ginger Baker'], + albums: 4, + awards: ['Rock and Roll Hall of Fame', 'Grammy Hall of Fame'], + influence: 'Pioneered power trio format, influenced blues rock development', + activeYears: '1966-1968', + website: 'https://www.ericclapton.com', + }, +]; diff --git a/packages/ui/src/components/Table/types.ts b/packages/ui/src/components/Table/types.ts new file mode 100644 index 0000000000..6cdc339d18 --- /dev/null +++ b/packages/ui/src/components/Table/types.ts @@ -0,0 +1,35 @@ +/* + * 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 { CellProps as ReactAriaCellProps } from 'react-aria-components'; + +/** @public */ +export interface CellProps extends ReactAriaCellProps { + title: string; + description?: string; + color?: 'primary' | 'secondary'; + leadingIcon?: React.ReactNode | null; + href?: string; +} + +/** @public */ +export interface CellProfileProps extends ReactAriaCellProps { + src?: string; + name?: string; + href?: string; + description?: string; + color?: 'primary' | 'secondary'; +} diff --git a/packages/ui/src/components/TablePagination/TablePagination.stories.tsx b/packages/ui/src/components/TablePagination/TablePagination.stories.tsx new file mode 100644 index 0000000000..2782837428 --- /dev/null +++ b/packages/ui/src/components/TablePagination/TablePagination.stories.tsx @@ -0,0 +1,59 @@ +/* + * 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. + */ + +// TODO: Bring useArgs() back when we update Storybook to 9 +// import { useArgs } from 'storybook/preview-api'; +import type { Meta, StoryObj } from '@storybook/react'; +import { TablePagination } from './TablePagination'; + +const meta = { + title: 'Backstage UI/TablePagination', + component: TablePagination, + argTypes: { + offset: { control: 'number' }, + pageSize: { control: 'radio', options: [5, 10, 20, 30, 40, 50] }, + rowCount: { control: 'number' }, + showPageSizeOptions: { control: 'boolean', defaultValue: true }, + setOffset: { action: 'setOffset' }, + setPageSize: { action: 'setPageSize' }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + offset: 0, + pageSize: 10, + rowCount: 100, + }, + render: args => { + // const [{}, updateArgs] = useArgs(); + + return ( + { + // updateArgs({ offset: value }); + // }} + // setPageSize={value => { + // updateArgs({ pageSize: value }); + // }} + /> + ); + }, +}; diff --git a/packages/canon/css/datatable.css b/packages/ui/src/components/TablePagination/TablePagination.styles.css similarity index 55% rename from packages/canon/css/datatable.css rename to packages/ui/src/components/TablePagination/TablePagination.styles.css index edef29f500..f7cbb44fc2 100644 --- a/packages/canon/css/datatable.css +++ b/packages/ui/src/components/TablePagination/TablePagination.styles.css @@ -1,23 +1,23 @@ -.canon-DataTablePagination { - padding-top: var(--canon-space-5); - justify-content: space-between; - align-items: center; +.bui-DataTablePagination { display: flex; + align-items: center; + justify-content: space-between; + padding-top: var(--bui-space-5); } -.canon-DataTablePagination--left { - justify-content: space-between; - align-items: center; +.bui-DataTablePagination--left { display: flex; + align-items: center; + justify-content: space-between; } -.canon-DataTablePagination--right { - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); +.bui-DataTablePagination--right { display: flex; + align-items: center; + justify-content: space-between; + gap: var(--bui-space-2); } -.canon-DataTablePagination--select { +.bui-DataTablePagination--select { min-width: 10.5rem; } diff --git a/packages/ui/src/components/TablePagination/TablePagination.tsx b/packages/ui/src/components/TablePagination/TablePagination.tsx new file mode 100644 index 0000000000..e7827e6ab9 --- /dev/null +++ b/packages/ui/src/components/TablePagination/TablePagination.tsx @@ -0,0 +1,120 @@ +/* + * 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 clsx from 'clsx'; +import { Text, ButtonIcon, Select, Icon } from '../..'; +import type { TablePaginationProps } from './types'; + +/** + * Pagination controls for Table components with page navigation and size selection. + * + * @public + */ +export function TablePagination(props: TablePaginationProps) { + const { + className, + offset, + pageSize, + rowCount, + onNextPage, + onPreviousPage, + onPageSizeChange, + setOffset, + setPageSize, + showPageSizeOptions = true, + ...rest + } = props; + + const currentOffset = offset ?? 0; + const currentPageSize = pageSize ?? 10; + + const fromCount = currentOffset + 1; + const toCount = Math.min(currentOffset + currentPageSize, rowCount ?? 0); + + const nextPage = () => { + const totalRows = rowCount ?? 0; + const nextOffset = currentOffset + currentPageSize; + + // Check if there are more items to navigate to + if (nextOffset < totalRows) { + onNextPage?.(); // Analytics tracking + setOffset?.(nextOffset); // Navigate to next page + } + }; + + const previousPage = () => { + // Check if we can go to previous page + if (currentOffset > 0) { + onPreviousPage?.(); // Analytics tracking + const prevOffset = Math.max(0, currentOffset - currentPageSize); + setOffset?.(prevOffset); // Navigate to previous page + } + }; + + return ( +
    +
    + {showPageSizeOptions && ( + +
    + + + ); + }, +); + +TextField.displayName = 'TextField'; diff --git a/packages/canon/src/components/TextField/index.ts b/packages/ui/src/components/TextField/index.ts similarity index 93% rename from packages/canon/src/components/TextField/index.ts rename to packages/ui/src/components/TextField/index.ts index 29df00ccaf..95a838fb46 100644 --- a/packages/canon/src/components/TextField/index.ts +++ b/packages/ui/src/components/TextField/index.ts @@ -15,4 +15,4 @@ */ export * from './TextField'; -export type { TextFieldProps } from './types'; +export * from './types'; diff --git a/packages/canon/src/components/TextField/types.ts b/packages/ui/src/components/TextField/types.ts similarity index 60% rename from packages/canon/src/components/TextField/types.ts rename to packages/ui/src/components/TextField/types.ts index 8111749845..75a5c68aaa 100644 --- a/packages/canon/src/components/TextField/types.ts +++ b/packages/ui/src/components/TextField/types.ts @@ -14,16 +14,19 @@ * limitations under the License. */ +import type { TextFieldProps as AriaTextFieldProps } from 'react-aria-components'; +import { ReactNode } from 'react'; import type { Breakpoint } from '../../types'; -import type { IconNames } from '../Icon'; +import type { FieldLabelProps } from '../FieldLabel/types'; /** @public */ export interface TextFieldProps - extends Omit, 'size'> { + extends AriaTextFieldProps, + Omit { /** - * The class name of the text field + * An icon to render before the input */ - className?: string; + icon?: ReactNode; /** * The size of the text field @@ -32,32 +35,7 @@ export interface TextFieldProps size?: 'small' | 'medium' | Partial>; /** - * The label of the text field + * Text to display in the input when it has no value */ - label?: string; - - /** - * The description of the text field - */ - description?: string; - - /** - * The name of the text field - */ - name: string; - - /** - * The error message of the text field - */ - error?: string | null; - - /** - * An icon to render before the input - */ - icon?: IconNames; - - /** - * Handler to call when the clear button is pressed - */ - onClear?: React.MouseEventHandler; + placeholder?: string; } diff --git a/packages/ui/src/components/Tooltip/Tooltip.stories.tsx b/packages/ui/src/components/Tooltip/Tooltip.stories.tsx new file mode 100644 index 0000000000..ff2e19f2a2 --- /dev/null +++ b/packages/ui/src/components/Tooltip/Tooltip.stories.tsx @@ -0,0 +1,125 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { Placement } from '@react-types/overlays'; +import { TooltipTrigger, Tooltip } from './Tooltip'; +import { Button } from '../Button/Button'; + +const meta = { + title: 'Backstage UI/Tooltip', + component: TooltipTrigger, + parameters: { layout: 'centered' }, + argTypes: { + isOpen: { + control: { type: 'boolean' }, + }, + isDisabled: { + control: { type: 'boolean' }, + }, + placement: { + options: ['top', 'right', 'bottom', 'left'], + control: { type: 'inline-radio' }, + }, + delay: { + control: { type: 'number' }, + }, + closeDelay: { + control: { type: 'number' }, + }, + }, + render: ({ tooltip, isOpen, isDisabled, placement, delay, closeDelay }) => ( + + + {tooltip ?? 'I am a tooltip'} + + ), +} as Meta<{ + tooltip?: string; + isOpen?: boolean; + isDisabled?: boolean; + placement?: Placement; + delay?: number; + closeDelay?: number; +}>; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + tooltip: 'I am a tooltip', + }, +}; + +export const IsOpen: Story = { + args: { + ...Default.args, + isOpen: true, + }, +}; + +export const IsDisabled: Story = { + args: { + ...Default.args, + isDisabled: true, + }, +}; + +export const NoDelays: Story = { + args: { + ...Default.args, + delay: 0, + closeDelay: 0, + }, +}; + +export const OrthogonalPlacements: Story = { + parameters: { + controls: { + exclude: ['placement'], + }, + }, + args: { + ...Default.args, + isOpen: true, + }, + render: ({ isOpen, tooltip }) => { + return ( + + + {tooltip} + {tooltip} + {tooltip} + {tooltip} + + ); + }, +}; + +export const WithLongText: Story = { + args: { + ...Default.args, + isOpen: true, + tooltip: + 'I am a tooltip with a very long text. orem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + }, +}; diff --git a/packages/ui/src/components/Tooltip/Tooltip.styles.css b/packages/ui/src/components/Tooltip/Tooltip.styles.css new file mode 100644 index 0000000000..f80a12bbc1 --- /dev/null +++ b/packages/ui/src/components/Tooltip/Tooltip.styles.css @@ -0,0 +1,123 @@ +/* + * 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. + */ + +.bui-Tooltip { + box-shadow: 0 10px 15px -3px rgba(0 0 0 / 0.1), + 0 4px 6px -4px rgba(0 0 0 / 0.1); + border-radius: 4px; + background: var(--bui-bg-surface-1); + border: 1px solid var(--bui-gray-3); + forced-color-adjust: none; + outline: none; + padding: var(--bui-space-2) var(--bui-space-3); + max-width: 240px; + /* fixes FF gap */ + transform: translate3d(0, 0, 0); + transition: transform 200ms, opacity 200ms; + font-size: var(--bui-font-size-3); + font-family: var(--bui-font-regular); + color: var(--bui-fg-primary); + + &[data-entering], + &[data-exiting] { + transform: var(--origin); + opacity: 0; + } + + --tooltip-offset: var(--bui-space-3); + + &[data-placement='top'] { + margin-bottom: var(--tooltip-offset); + --origin: translateY(4px); + } + + &[data-placement='right'] { + margin-left: var(--tooltip-offset); + --origin: translateX(-4px); + } + + &[data-placement='bottom'] { + margin-top: var(--tooltip-offset); + --origin: translateY(-4px); + } + + &[data-placement='left'] { + margin-right: var(--tooltip-offset); + --origin: translateX(4px); + } +} + +.bui-TooltipArrow { + & svg { + display: block; + + /* The tooltip is rendered overlaying the main + tooltip element by 1px. This causes the borders + to overlap, which causes minor visual artifacts + with transparent border colors. To mitigate this, + we split the stroke and fill across separate + elements in order to guarantee that the stroke is + always overlaying a consistent color. */ + path:nth-child(1) { + fill: var(--bui-bg-surface-1); + } + + path:nth-child(2) { + fill: var(--bui-gray-3); + } + + /* The arrow svg overlaps the tooltip by 2px, so we + need to adjust the margins accordingly. */ + --tooltip-arrow-overlap: -2px; + } + + &[data-placement='top'] svg { + margin-top: var(--tooltip-arrow-overlap); + } + + &[data-placement='bottom'] svg { + margin-bottom: var(--tooltip-arrow-overlap); + transform: rotate(180deg); + } + + &[data-placement='right'] svg { + margin-right: var(--tooltip-arrow-overlap); + transform: rotate(90deg); + } + + &[data-placement='left'] svg { + margin-left: var(--tooltip-arrow-overlap); + transform: rotate(-90deg); + } +} + +[data-theme='dark'] { + .bui-Tooltip { + background: var(--bui-bg-surface-2); + box-shadow: none; + border: 1px solid var(--bui-gray-4); + } + + .bui-TooltipArrow { + svg path:nth-child(1) { + fill: var(--bui-bg-surface-2); + } + + svg path:nth-child(2) { + fill: var(--bui-gray-4); + } + } +} diff --git a/packages/ui/src/components/Tooltip/Tooltip.tsx b/packages/ui/src/components/Tooltip/Tooltip.tsx new file mode 100644 index 0000000000..4503a1ce90 --- /dev/null +++ b/packages/ui/src/components/Tooltip/Tooltip.tsx @@ -0,0 +1,58 @@ +/* + * 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 { forwardRef } from 'react'; +import { + OverlayArrow, + Tooltip as AriaTooltip, + TooltipTrigger as AriaTooltipTrigger, + TooltipTriggerComponentProps, +} from 'react-aria-components'; +import clsx from 'clsx'; +import { TooltipProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; + +/** @public */ +export const TooltipTrigger = (props: TooltipTriggerComponentProps) => { + const { delay = 600 } = props; + + return ; +}; + +/** @public */ +export const Tooltip = forwardRef( + ({ className, children, ...rest }, ref) => { + const { classNames } = useStyles('Tooltip'); + + return ( + + + + + + + + {children} + + ); + }, +); + +Tooltip.displayName = 'Tooltip'; diff --git a/packages/canon/src/components/Text/index.ts b/packages/ui/src/components/Tooltip/index.ts similarity index 86% rename from packages/canon/src/components/Text/index.ts rename to packages/ui/src/components/Tooltip/index.ts index ae1f532b2c..613631091b 100644 --- a/packages/canon/src/components/Text/index.ts +++ b/packages/ui/src/components/Tooltip/index.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -export { Text } from './Text'; -export type { TextProps } from './types'; +export { TooltipTrigger, Tooltip } from './Tooltip'; +export type { TooltipProps } from './types'; diff --git a/packages/canon/src/components/Table/TableCellProfile/types.ts b/packages/ui/src/components/Tooltip/types.ts similarity index 77% rename from packages/canon/src/components/Table/TableCellProfile/types.ts rename to packages/ui/src/components/Tooltip/types.ts index d5a7b18cc2..2ef44fbbf4 100644 --- a/packages/canon/src/components/Table/TableCellProfile/types.ts +++ b/packages/ui/src/components/Tooltip/types.ts @@ -14,11 +14,9 @@ * limitations under the License. */ +import { TooltipProps as AriaTooltipProps } from 'react-aria-components'; + /** @public */ -export interface TableCellProfileProps - extends React.HTMLAttributes { - src?: string; - name?: string; - to?: string; - withImage?: boolean; +export interface TooltipProps extends Omit { + children: React.ReactNode; } diff --git a/packages/canon/src/css/components.css b/packages/ui/src/css/components.css similarity index 62% rename from packages/canon/src/css/components.css rename to packages/ui/src/css/components.css index 53c2882302..f83cc50ad2 100644 --- a/packages/canon/src/css/components.css +++ b/packages/ui/src/css/components.css @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,25 +17,30 @@ @import '../components/Avatar/Avatar.styles.css'; @import '../components/Box/styles.css'; @import '../components/Button/styles.css'; +@import '../components/ButtonIcon/styles.css'; +@import '../components/Card/Card.styles.css'; +@import '../components/Checkbox/styles.css'; @import '../components/Collapsible/Collapsible.styles.css'; -@import '../components/DataTable/Root/DataTableRoot.styles.css'; -@import '../components/DataTable/Pagination/DataTablePagination.styles.css'; +@import '../components/Container/styles.css'; +@import '../components/FieldError/FieldError.styles.css'; +@import '../components/FieldLabel/FieldLabel.styles.css'; @import '../components/Flex/styles.css'; @import '../components/Grid/styles.css'; -@import '../components/Container/styles.css'; +@import '../components/Header/Header.styles.css'; +@import '../components/HeaderPage/HeaderPage.styles.css'; @import '../components/Icon/styles.css'; -@import '../components/Checkbox/styles.css'; -@import '../components/Table/styles.css'; -@import '../components/Table/TableCell/TableCell.styles.css'; -@import '../components/Table/TableCellText/TableCellText.styles.css'; -@import '../components/Table/TableCellLink/TableCellLink.styles.css'; -@import '../components/Table/TableCellProfile/TableCellProfile.styles.css'; -@import '../components/Text/styles.css'; -@import '../components/Heading/styles.css'; -@import '../components/IconButton/styles.css'; -@import '../components/TextField/TextField.styles.css'; -@import '../components/Menu/Menu.styles.css'; @import '../components/Link/styles.css'; +@import '../components/Menu/Menu.styles.css'; +@import '../components/Popover/Popover.styles.css'; +@import '../components/RadioGroup/RadioGroup.styles.css'; +@import '../components/Table/Table.styles.css'; +@import '../components/TablePagination/TablePagination.styles.css'; +@import '../components/Tabs/Tabs.styles.css'; +@import '../components/Text/styles.css'; +@import '../components/TextField/TextField.styles.css'; +@import '../components/SearchField/SearchField.styles.css'; +@import '../components/Skeleton/Skeleton.styles.css'; @import '../components/Tooltip/Tooltip.styles.css'; @import '../components/ScrollArea/ScrollArea.styles.css'; @import '../components/Select/Select.styles.css'; +@import '../components/Switch/Switch.styles.css'; diff --git a/packages/ui/src/css/core.css b/packages/ui/src/css/core.css new file mode 100644 index 0000000000..088d4d6f2b --- /dev/null +++ b/packages/ui/src/css/core.css @@ -0,0 +1,200 @@ +/* + * 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. + */ + +/* Normalize */ +@import './normalize.css'; +@import './utilities.css'; + +/* Global reset */ +:where(a) { + color: inherit; + text-decoration: none; +} + +@keyframes pulse { + 50% { + opacity: 0.5; + } +} + +/* Light theme tokens */ +:root { + /* Font families */ + --bui-font-regular: system-ui; + --bui-font-monospace: ui-monospace, 'Menlo', 'Monaco', 'Consolas', + 'Liberation Mono', 'Courier New', monospace; + + /* Font weights */ + --bui-font-weight-regular: 400; + --bui-font-weight-bold: 600; + + /* Font sizes */ + --bui-font-size-1: 0.625rem; /* 10px */ + --bui-font-size-2: 0.75rem; /* 12px */ + --bui-font-size-3: 0.875rem; /* 14px */ + --bui-font-size-4: 1rem; /* 16px */ + --bui-font-size-5: 1.25rem; /* 20px */ + --bui-font-size-6: 1.5rem; /* 24px */ + --bui-font-size-7: 2rem; /* 32px */ + --bui-font-size-8: 3rem; /* 48px */ + --bui-font-size-9: 4rem; /* 64px */ + --bui-font-size-10: 5.75rem; /* 92px */ + + /* Spacing */ + --bui-space: 0.25rem; /* This is the spacing multiplier */ + --bui-space-0_5: calc(var(--bui-space) * 0.5); /* 2px */ + --bui-space-1: var(--bui-space); /* 4px */ + --bui-space-1_5: calc(var(--bui-space) * 1.5); /* 6px */ + --bui-space-2: calc(var(--bui-space) * 2); /* 8px */ + --bui-space-3: calc(var(--bui-space) * 3); /* 12px */ + --bui-space-4: calc(var(--bui-space) * 4); /* 16px */ + --bui-space-5: calc(var(--bui-space) * 5); /* 20px */ + --bui-space-6: calc(var(--bui-space) * 6); /* 24px */ + --bui-space-7: calc(var(--bui-space) * 7); /* 28px */ + --bui-space-8: calc(var(--bui-space) * 8); /* 32px */ + --bui-space-9: calc(var(--bui-space) * 9); /* 36px */ + --bui-space-10: calc(var(--bui-space) * 10); /* 40px */ + --bui-space-11: calc(var(--bui-space) * 11); /* 44px */ + --bui-space-12: calc(var(--bui-space) * 12); /* 48px */ + --bui-space-13: calc(var(--bui-space) * 13); /* 52px */ + --bui-space-14: calc(var(--bui-space) * 14); /* 56px */ + + /* Radius */ + --bui-radius-1: calc(0.125rem); + --bui-radius-2: calc(0.25rem); + --bui-radius-3: calc(0.5rem); + --bui-radius-4: calc(0.75rem); + --bui-radius-5: calc(1rem); + --bui-radius-6: calc(1.25rem); + --bui-radius-full: 9999px; + + /* Base Colors */ + --bui-black: #000000; + --bui-white: #ffffff; + + /* Gray scale */ + --bui-gray-1: #f8f8f8; + --bui-gray-2: #ececec; + --bui-gray-3: #d9d9d9; + --bui-gray-4: #c1c1c1; + --bui-gray-5: #9e9e9e; + --bui-gray-6: #8c8c8c; + --bui-gray-7: #757575; + --bui-gray-8: #595959; + + /* Background Colors */ + --bui-bg: var(--bui-gray-1); + --bui-bg-surface-1: var(--bui-white); + --bui-bg-surface-2: var(--bui-gray-2); + --bui-bg-solid: #1f5493; + --bui-bg-solid-hover: #163a66; + --bui-bg-solid-pressed: #0f2b4e; + --bui-bg-solid-disabled: #ebebeb; + --bui-bg-tint: transparent; + --bui-bg-tint-hover: rgba(31, 84, 147, 0.4); + --bui-bg-tint-pressed: rgba(31, 84, 147, 0.6); + --bui-bg-tint-disabled: #ebebeb; + --bui-bg-danger: #feebe7; + --bui-bg-warning: #fff2b2; + --bui-bg-success: #e6f6eb; + + /* Foreground Colors */ + --bui-fg-primary: var(--bui-black); + --bui-fg-secondary: var(--bui-gray-7); + --bui-fg-link: #1f5493; + --bui-fg-link-hover: #1f2d5c; + --bui-fg-disabled: #9e9e9e; + --bui-fg-solid: var(--bui-white); + --bui-fg-solid-disabled: #9c9c9c; + --bui-fg-tint: #1f5493; + --bui-fg-tint-disabled: var(--bui-gray-5); + --bui-fg-danger: #e22b2b; + --bui-fg-warning: #e36d05; + --bui-fg-success: #1db954; + + /* Border Colors */ + --bui-border: rgba(0, 0, 0, 0.1); + --bui-border-hover: rgba(0, 0, 0, 0.2); + --bui-border-pressed: rgba(0, 0, 0, 0.4); + --bui-border-disabled: rgba(0, 0, 0, 0.1); + --bui-border-danger: #f87a7a; + --bui-border-warning: #e36d05; + --bui-border-success: #53db83; + + /* Special Colors */ + --bui-ring: #1f5493; + --bui-scrollbar: #a0a0a03b; + --bui-scrollbar-thumb: #a0a0a0; + + --bui-animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +/* Dark theme tokens */ +[data-theme-mode='dark'] { + /* Gray scale */ + --bui-gray-1: #191919; + --bui-gray-2: #242424; + --bui-gray-3: #373737; + --bui-gray-4: #464646; + --bui-gray-5: #575757; + --bui-gray-6: #7b7b7b; + --bui-gray-7: #9e9e9e; + --bui-gray-8: #b4b4b4; + + /* Background Colors */ + --bui-bg: #333333; + --bui-bg-surface-1: #424242; + --bui-bg-surface-2: var(--bui-gray-2); + --bui-bg-solid: #9cc9ff; + --bui-bg-solid-hover: #83b9fd; + --bui-bg-solid-pressed: #83b9fd; + --bui-bg-solid-disabled: #222222; + --bui-bg-tint: transparent; + --bui-bg-tint-hover: rgba(156, 201, 255, 0.12); + --bui-bg-tint-pressed: rgba(156, 201, 255, 0.16); + --bui-bg-tint-disabled: transparent; + --bui-bg-danger: #3b1219; + --bui-bg-warning: #302008; + --bui-bg-success: #132d21; + + /* Foreground Colors */ + --bui-fg-primary: var(--bui-white); + --bui-fg-secondary: var(--bui-gray-7); + --bui-fg-link: #9cc9ff; + --bui-fg-link-hover: #7eb5f7; + --bui-fg-disabled: var(--bui-gray-7); + --bui-fg-solid: #101821; + --bui-fg-solid-disabled: var(--bui-gray-5); + --bui-fg-tint: #9cc9ff; + --bui-fg-tint-disabled: var(--bui-gray-5); + --bui-fg-danger: #e22b2b; + --bui-fg-warning: #e36d05; + --bui-fg-success: #1db954; + + /* Border Colors */ + --bui-border: rgba(255, 255, 255, 0.12); + --bui-border-hover: rgba(255, 255, 255, 0.4); + --bui-border-pressed: rgba(255, 255, 255, 0.5); + --bui-border-disabled: rgba(255, 255, 255, 0.2); + --bui-border-danger: #f87a7a; + --bui-border-warning: #e36d05; + --bui-border-success: #53db83; + + /* Special Colors */ + --bui-ring: #1f5493; + --bui-scrollbar: #3636363a; + --bui-scrollbar-thumb: #575757; +} diff --git a/packages/canon/src/css/normalize.css b/packages/ui/src/css/normalize.css similarity index 100% rename from packages/canon/src/css/normalize.css rename to packages/ui/src/css/normalize.css diff --git a/packages/ui/src/css/stories.module.css b/packages/ui/src/css/stories.module.css new file mode 100644 index 0000000000..10ed37bb7a --- /dev/null +++ b/packages/ui/src/css/stories.module.css @@ -0,0 +1,16 @@ +.fakeBox { + background: #eaf2fd; + border-radius: 4px; + box-shadow: 0 0 0 1px #2563eb; + height: 48px; + transition-property: background-color, box-shadow, background-image; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; + background-image: url('data:image/svg+xml,%3Csvg%20width%3D%226%22%20height%3D%226%22%20viewBox%3D%220%200%206%206%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cg%20fill%3D%22%232563eb%22%20fill-opacity%3D%220.3%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M5%200h1L0%206V5zM6%205v1H5z%22/%3E%3C/g%3E%3C/svg%3E'); +} + +[data-theme='dark'] .fakeBox { + background: #1a1a1a; + box-shadow: 0 0 0 1px #4765ff; + background-image: url('data:image/svg+xml,%3Csvg%20width%3D%226%22%20height%3D%226%22%20viewBox%3D%220%200%206%206%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cg%20fill%3D%22%234765FF%22%20fill-opacity%3D%221%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M5%200h1L0%206V5zM6%205v1H5z%22/%3E%3C/g%3E%3C/svg%3E'); +} diff --git a/packages/canon/src/css/styles.css b/packages/ui/src/css/styles.css similarity index 100% rename from packages/canon/src/css/styles.css rename to packages/ui/src/css/styles.css diff --git a/packages/canon/src/css/utilities.css b/packages/ui/src/css/utilities.css similarity index 100% rename from packages/canon/src/css/utilities.css rename to packages/ui/src/css/utilities.css diff --git a/packages/canon/src/css/utilities/display.css b/packages/ui/src/css/utilities/display.css similarity index 56% rename from packages/canon/src/css/utilities/display.css rename to packages/ui/src/css/utilities/display.css index 8c353730ca..179d80f860 100644 --- a/packages/canon/src/css/utilities/display.css +++ b/packages/ui/src/css/utilities/display.css @@ -1,110 +1,110 @@ -.cu-display-none { +.bui-display-none { display: none; } -.cu-display-inline { +.bui-display-inline { display: inline; } -.cu-display-inline-block { +.bui-display-inline-block { display: inline-block; } -.cu-display-block { +.bui-display-block { display: block; } /* Breakpoint xs */ @media (min-width: 640px) { - .xs\:cu-display-none { + .xs\:bui-display-none { display: none; } - .xs\:cu-display-inline { + .xs\:bui-display-inline { display: inline; } - .xs\:cu-display-inline-block { + .xs\:bui-display-inline-block { display: inline-block; } - .xs\:cu-display-block { + .xs\:bui-display-block { display: block; } } /* Breakpoint sm */ @media (min-width: 768px) { - .sm\:cu-display-none { + .sm\:bui-display-none { display: none; } - .sm\:cu-display-inline { + .sm\:bui-display-inline { display: inline; } - .sm\:cu-display-inline-block { + .sm\:bui-display-inline-block { display: inline-block; } - .sm\:cu-display-block { + .sm\:bui-display-block { display: block; } } /* Breakpoint md */ @media (min-width: 1024px) { - .md\:cu-display-none { + .md\:bui-display-none { display: none; } - .md\:cu-display-inline { + .md\:bui-display-inline { display: inline; } - .md\:cu-display-inline-block { + .md\:bui-display-inline-block { display: inline-block; } - .md\:cu-display-block { + .md\:bui-display-block { display: block; } } /* Breakpoint lg */ @media (min-width: 1280px) { - .lg\:cu-display-none { + .lg\:bui-display-none { display: none; } - .lg\:cu-display-inline { + .lg\:bui-display-inline { display: inline; } - .lg\:cu-display-inline-block { + .lg\:bui-display-inline-block { display: inline-block; } - .lg\:cu-display-block { + .lg\:bui-display-block { display: block; } } /* Breakpoint xl */ @media (min-width: 1536px) { - .xl\:cu-display-none { + .xl\:bui-display-none { display: none; } - .xl\:cu-display-inline { + .xl\:bui-display-inline { display: inline; } - .xl\:cu-display-inline-block { + .xl\:bui-display-inline-block { display: inline-block; } - .xl\:cu-display-block { + .xl\:bui-display-block { display: block; } } diff --git a/packages/canon/src/css/utilities/flex.css b/packages/ui/src/css/utilities/flex.css similarity index 60% rename from packages/canon/src/css/utilities/flex.css rename to packages/ui/src/css/utilities/flex.css index 5311aac3e8..d68c63556f 100644 --- a/packages/canon/src/css/utilities/flex.css +++ b/packages/ui/src/css/utilities/flex.css @@ -1,302 +1,302 @@ -.cu-align-start { +.bui-align-start { align-items: start; } -.cu-align-center { +.bui-align-center { align-items: center; } -.cu-align-end { +.bui-align-end { align-items: end; } -.cu-align-stretch { +.bui-align-stretch { align-items: stretch; } -.cu-jc-start { +.bui-jc-start { justify-content: start; } -.cu-jc-center { +.bui-jc-center { justify-content: center; } -.cu-jc-end { +.bui-jc-end { justify-content: end; } -.cu-jc-between { +.bui-jc-between { justify-content: space-between; } -.cu-fd-row { +.bui-fd-row { flex-direction: row; } -.cu-fd-column { +.bui-fd-column { flex-direction: column; } -.cu-fd-row-reverse { +.bui-fd-row-reverse { flex-direction: row-reverse; } -.cu-fd-column-reverse { +.bui-fd-column-reverse { flex-direction: column-reverse; } /* Breakpoint xs */ @media (min-width: 640px) { - .xs\:cu-align-start { + .xs\:bui-align-start { align-items: start; } - .xs\:cu-align-center { + .xs\:bui-align-center { align-items: center; } - .xs\:cu-align-end { + .xs\:bui-align-end { align-items: end; } - .xs\:cu-align-stretch { + .xs\:bui-align-stretch { align-items: stretch; } - .xs\:cu-jc-start { + .xs\:bui-jc-start { justify-content: start; } - .xs\:cu-jc-center { + .xs\:bui-jc-center { justify-content: center; } - .xs\:cu-jc-end { + .xs\:bui-jc-end { justify-content: end; } - .xs\:cu-jc-between { + .xs\:bui-jc-between { justify-content: space-between; } - .xs\:cu-fd-row { + .xs\:bui-fd-row { flex-direction: row; } - .xs\:cu-fd-column { + .xs\:bui-fd-column { flex-direction: column; } - .xs\:cu-fd-row-reverse { + .xs\:bui-fd-row-reverse { flex-direction: row-reverse; } - .xs\:cu-fd-column-reverse { + .xs\:bui-fd-column-reverse { flex-direction: column-reverse; } } /* Breakpoint sm */ @media (min-width: 768px) { - .sm\:cu-align-start { + .sm\:bui-align-start { align-items: start; } - .sm\:cu-align-center { + .sm\:bui-align-center { align-items: center; } - .sm\:cu-align-end { + .sm\:bui-align-end { align-items: end; } - .sm\:cu-align-stretch { + .sm\:bui-align-stretch { align-items: stretch; } - .sm\:cu-jc-start { + .sm\:bui-jc-start { justify-content: start; } - .sm\:cu-jc-center { + .sm\:bui-jc-center { justify-content: center; } - .sm\:cu-jc-end { + .sm\:bui-jc-end { justify-content: end; } - .sm\:cu-jc-between { + .sm\:bui-jc-between { justify-content: space-between; } - .sm\:cu-fd-row { + .sm\:bui-fd-row { flex-direction: row; } - .sm\:cu-fd-column { + .sm\:bui-fd-column { flex-direction: column; } - .sm\:cu-fd-row-reverse { + .sm\:bui-fd-row-reverse { flex-direction: row-reverse; } - .sm\:cu-fd-column-reverse { + .sm\:bui-fd-column-reverse { flex-direction: column-reverse; } } /* Breakpoint md */ @media (min-width: 1024px) { - .md\:cu-align-start { + .md\:bui-align-start { align-items: start; } - .md\:cu-align-center { + .md\:bui-align-center { align-items: center; } - .md\:cu-align-end { + .md\:bui-align-end { align-items: end; } - .md\:cu-align-stretch { + .md\:bui-align-stretch { align-items: stretch; } - .md\:cu-jc-start { + .md\:bui-jc-start { justify-content: start; } - .md\:cu-jc-center { + .md\:bui-jc-center { justify-content: center; } - .md\:cu-jc-end { + .md\:bui-jc-end { justify-content: end; } - .md\:cu-jc-between { + .md\:bui-jc-between { justify-content: space-between; } - .md\:cu-fd-row { + .md\:bui-fd-row { flex-direction: row; } - .md\:cu-fd-column { + .md\:bui-fd-column { flex-direction: column; } - .md\:cu-fd-row-reverse { + .md\:bui-fd-row-reverse { flex-direction: row-reverse; } - .md\:cu-fd-column-reverse { + .md\:bui-fd-column-reverse { flex-direction: column-reverse; } } /* Breakpoint lg */ @media (min-width: 1280px) { - .lg\:cu-align-start { + .lg\:bui-align-start { align-items: start; } - .lg\:cu-align-center { + .lg\:bui-align-center { align-items: center; } - .lg\:cu-align-end { + .lg\:bui-align-end { align-items: end; } - .lg\:cu-align-stretch { + .lg\:bui-align-stretch { align-items: stretch; } - .lg\:cu-jc-start { + .lg\:bui-jc-start { justify-content: start; } - .lg\:cu-jc-center { + .lg\:bui-jc-center { justify-content: center; } - .lg\:cu-jc-end { + .lg\:bui-jc-end { justify-content: end; } - .lg\:cu-jc-between { + .lg\:bui-jc-between { justify-content: space-between; } - .lg\:cu-fd-row { + .lg\:bui-fd-row { flex-direction: row; } - .lg\:cu-fd-column { + .lg\:bui-fd-column { flex-direction: column; } - .lg\:cu-fd-row-reverse { + .lg\:bui-fd-row-reverse { flex-direction: row-reverse; } - .lg\:cu-fd-column-reverse { + .lg\:bui-fd-column-reverse { flex-direction: column-reverse; } } /* Breakpoint xl */ @media (min-width: 1536px) { - .xl\:cu-align-start { + .xl\:bui-align-start { align-items: start; } - .xl\:cu-align-center { + .xl\:bui-align-center { align-items: center; } - .xl\:cu-align-end { + .xl\:bui-align-end { align-items: end; } - .xl\:cu-align-stretch { + .xl\:bui-align-stretch { align-items: stretch; } - .xl\:cu-jc-start { + .xl\:bui-jc-start { justify-content: start; } - .xl\:cu-jc-center { + .xl\:bui-jc-center { justify-content: center; } - .xl\:cu-jc-end { + .xl\:bui-jc-end { justify-content: end; } - .xl\:cu-jc-between { + .xl\:bui-jc-between { justify-content: space-between; } - .xl\:cu-fd-row { + .xl\:bui-fd-row { flex-direction: row; } - .xl\:cu-fd-column { + .xl\:bui-fd-column { flex-direction: column; } - .xl\:cu-fd-row-reverse { + .xl\:bui-fd-row-reverse { flex-direction: row-reverse; } - .xl\:cu-fd-column-reverse { + .xl\:bui-fd-column-reverse { flex-direction: column-reverse; } } diff --git a/packages/ui/src/css/utilities/gap.css b/packages/ui/src/css/utilities/gap.css new file mode 100644 index 0000000000..ec4168fa6b --- /dev/null +++ b/packages/ui/src/css/utilities/gap.css @@ -0,0 +1,422 @@ +.bui-gap { + gap: var(--gap); +} + +.bui-gap-0\.5 { + gap: var(--bui-space-0_5); +} + +.bui-gap-1 { + gap: var(--bui-space-1); +} + +.bui-gap-1\.5 { + gap: var(--bui-space-1_5); +} + +.bui-gap-2 { + gap: var(--bui-space-2); +} + +.bui-gap-3 { + gap: var(--bui-space-3); +} + +.bui-gap-4 { + gap: var(--bui-space-4); +} + +.bui-gap-5 { + gap: var(--bui-space-5); +} + +.bui-gap-6 { + gap: var(--bui-space-6); +} + +.bui-gap-7 { + gap: var(--bui-space-7); +} + +.bui-gap-8 { + gap: var(--bui-space-8); +} + +.bui-gap-9 { + gap: var(--bui-space-9); +} + +.bui-gap-10 { + gap: var(--bui-space-10); +} + +.bui-gap-11 { + gap: var(--bui-space-11); +} + +.bui-gap-12 { + gap: var(--bui-space-12); +} + +.bui-gap-13 { + gap: var(--bui-space-13); +} + +.bui-gap-14 { + gap: var(--bui-space-14); +} + +/* Breakpoint xs */ +@media (min-width: 640px) { + .xs\:bui-gap { + gap: var(--gap-xs); + } + + .xs\:bui-gap-0\.5 { + gap: var(--bui-space-0_5); + } + + .xs\:bui-gap-1 { + gap: var(--bui-space-1); + } + + .xs\:bui-gap-1\.5 { + gap: var(--bui-space-1_5); + } + + .xs\:bui-gap-2 { + gap: var(--bui-space-2); + } + + .xs\:bui-gap-3 { + gap: var(--bui-space-3); + } + + .xs\:bui-gap-4 { + gap: var(--bui-space-4); + } + + .xs\:bui-gap-5 { + gap: var(--bui-space-5); + } + + .xs\:bui-gap-6 { + gap: var(--bui-space-6); + } + + .xs\:bui-gap-7 { + gap: var(--bui-space-7); + } + + .xs\:bui-gap-8 { + gap: var(--bui-space-8); + } + + .xs\:bui-gap-9 { + gap: var(--bui-space-9); + } + + .xs\:bui-gap-10 { + gap: var(--bui-space-10); + } + + .xs\:bui-gap-11 { + gap: var(--bui-space-11); + } + + .xs\:bui-gap-12 { + gap: var(--bui-space-12); + } + + .xs\:bui-gap-13 { + gap: var(--bui-space-13); + } + + .xs\:bui-gap-14 { + gap: var(--bui-space-14); + } +} + +/* Breakpoint sm */ +@media (min-width: 768px) { + .sm\:bui-gap { + gap: var(--gap-sm); + } + + .sm\:bui-gap-0\.5 { + gap: var(--bui-space-0_5); + } + + .sm\:bui-gap-1 { + gap: var(--bui-space-1); + } + + .sm\:bui-gap-1\.5 { + gap: var(--bui-space-1_5); + } + + .sm\:bui-gap-2 { + gap: var(--bui-space-2); + } + + .sm\:bui-gap-3 { + gap: var(--bui-space-3); + } + + .sm\:bui-gap-4 { + gap: var(--bui-space-4); + } + + .sm\:bui-gap-5 { + gap: var(--bui-space-5); + } + + .sm\:bui-gap-6 { + gap: var(--bui-space-6); + } + + .sm\:bui-gap-7 { + gap: var(--bui-space-7); + } + + .sm\:bui-gap-8 { + gap: var(--bui-space-8); + } + + .sm\:bui-gap-9 { + gap: var(--bui-space-9); + } + + .sm\:bui-gap-10 { + gap: var(--bui-space-10); + } + + .sm\:bui-gap-11 { + gap: var(--bui-space-11); + } + + .sm\:bui-gap-12 { + gap: var(--bui-space-12); + } + + .sm\:bui-gap-13 { + gap: var(--bui-space-13); + } + + .sm\:bui-gap-14 { + gap: var(--bui-space-14); + } +} + +/* Breakpoint md */ +@media (min-width: 1024px) { + .md\:bui-gap { + gap: var(--gap-md); + } + + .md\:bui-gap-0\.5 { + gap: var(--bui-space-0_5); + } + + .md\:bui-gap-1 { + gap: var(--bui-space-1); + } + + .md\:bui-gap-1\.5 { + gap: var(--bui-space-1_5); + } + + .md\:bui-gap-2 { + gap: var(--bui-space-2); + } + + .md\:bui-gap-3 { + gap: var(--bui-space-3); + } + + .md\:bui-gap-4 { + gap: var(--bui-space-4); + } + + .md\:bui-gap-5 { + gap: var(--bui-space-5); + } + + .md\:bui-gap-6 { + gap: var(--bui-space-6); + } + + .md\:bui-gap-7 { + gap: var(--bui-space-7); + } + + .md\:bui-gap-8 { + gap: var(--bui-space-8); + } + + .md\:bui-gap-9 { + gap: var(--bui-space-9); + } + + .md\:bui-gap-10 { + gap: var(--bui-space-10); + } + + .md\:bui-gap-11 { + gap: var(--bui-space-11); + } + + .md\:bui-gap-12 { + gap: var(--bui-space-12); + } + + .md\:bui-gap-13 { + gap: var(--bui-space-13); + } + + .md\:bui-gap-14 { + gap: var(--bui-space-14); + } +} + +/* Breakpoint lg */ +@media (min-width: 1280px) { + .lg\:bui-gap { + gap: var(--gap-lg); + } + + .lg\:bui-gap-0\.5 { + gap: var(--bui-space-0_5); + } + + .lg\:bui-gap-1 { + gap: var(--bui-space-1); + } + + .lg\:bui-gap-1\.5 { + gap: var(--bui-space-1_5); + } + + .lg\:bui-gap-2 { + gap: var(--bui-space-2); + } + + .lg\:bui-gap-3 { + gap: var(--bui-space-3); + } + + .lg\:bui-gap-4 { + gap: var(--bui-space-4); + } + + .lg\:bui-gap-5 { + gap: var(--bui-space-5); + } + + .lg\:bui-gap-6 { + gap: var(--bui-space-6); + } + + .lg\:bui-gap-7 { + gap: var(--bui-space-7); + } + + .lg\:bui-gap-8 { + gap: var(--bui-space-8); + } + + .lg\:bui-gap-9 { + gap: var(--bui-space-9); + } + + .lg\:bui-gap-10 { + gap: var(--bui-space-10); + } + + .lg\:bui-gap-11 { + gap: var(--bui-space-11); + } + + .lg\:bui-gap-12 { + gap: var(--bui-space-12); + } + + .lg\:bui-gap-13 { + gap: var(--bui-space-13); + } + + .lg\:bui-gap-14 { + gap: var(--bui-space-14); + } +} + +/* Breakpoint xl */ +@media (min-width: 1536px) { + .xl\:bui-gap { + gap: var(--gap-xl); + } + + .xl\:bui-gap-0\.5 { + gap: var(--bui-space-0_5); + } + + .xl\:bui-gap-1 { + gap: var(--bui-space-1); + } + + .xl\:bui-gap-1\.5 { + gap: var(--bui-space-1_5); + } + + .xl\:bui-gap-2 { + gap: var(--bui-space-2); + } + + .xl\:bui-gap-3 { + gap: var(--bui-space-3); + } + + .xl\:bui-gap-4 { + gap: var(--bui-space-4); + } + + .xl\:bui-gap-5 { + gap: var(--bui-space-5); + } + + .xl\:bui-gap-6 { + gap: var(--bui-space-6); + } + + .xl\:bui-gap-7 { + gap: var(--bui-space-7); + } + + .xl\:bui-gap-8 { + gap: var(--bui-space-8); + } + + .xl\:bui-gap-9 { + gap: var(--bui-space-9); + } + + .xl\:bui-gap-10 { + gap: var(--bui-space-10); + } + + .xl\:bui-gap-11 { + gap: var(--bui-space-11); + } + + .xl\:bui-gap-12 { + gap: var(--bui-space-12); + } + + .xl\:bui-gap-13 { + gap: var(--bui-space-13); + } + + .xl\:bui-gap-14 { + gap: var(--bui-space-14); + } +} diff --git a/packages/canon/src/css/utilities/grid.css b/packages/ui/src/css/utilities/grid.css similarity index 62% rename from packages/canon/src/css/utilities/grid.css rename to packages/ui/src/css/utilities/grid.css index a0afe55533..11de40bd7f 100644 --- a/packages/canon/src/css/utilities/grid.css +++ b/packages/ui/src/css/utilities/grid.css @@ -1,1292 +1,1292 @@ -.cu-columns-1 { +.bui-columns-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } -.cu-columns-2 { +.bui-columns-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } -.cu-columns-3 { +.bui-columns-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } -.cu-columns-4 { +.bui-columns-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } -.cu-columns-5 { +.bui-columns-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); } -.cu-columns-6 { +.bui-columns-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); } -.cu-columns-7 { +.bui-columns-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); } -.cu-columns-8 { +.bui-columns-8 { grid-template-columns: repeat(8, minmax(0, 1fr)); } -.cu-columns-9 { +.bui-columns-9 { grid-template-columns: repeat(9, minmax(0, 1fr)); } -.cu-columns-10 { +.bui-columns-10 { grid-template-columns: repeat(10, minmax(0, 1fr)); } -.cu-columns-11 { +.bui-columns-11 { grid-template-columns: repeat(11, minmax(0, 1fr)); } -.cu-columns-12 { +.bui-columns-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); } -.cu-columns-auto { +.bui-columns-auto { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); } -.cu-col-span-1 { +.bui-col-span-1 { grid-column: span 1 / span 1; } -.cu-col-span-2 { +.bui-col-span-2 { grid-column: span 2 / span 2; } -.cu-col-span-3 { +.bui-col-span-3 { grid-column: span 3 / span 3; } -.cu-col-span-4 { +.bui-col-span-4 { grid-column: span 4 / span 4; } -.cu-col-span-5 { +.bui-col-span-5 { grid-column: span 5 / span 5; } -.cu-col-span-6 { +.bui-col-span-6 { grid-column: span 6 / span 6; } -.cu-col-span-7 { +.bui-col-span-7 { grid-column: span 7 / span 7; } -.cu-col-span-8 { +.bui-col-span-8 { grid-column: span 8 / span 8; } -.cu-col-span-9 { +.bui-col-span-9 { grid-column: span 9 / span 9; } -.cu-col-span-10 { +.bui-col-span-10 { grid-column: span 10 / span 10; } -.cu-col-span-11 { +.bui-col-span-11 { grid-column: span 11 / span 11; } -.cu-col-span-12 { +.bui-col-span-12 { grid-column: span 12 / span 12; } -.cu-col-span-auto { +.bui-col-span-auto { grid-column: span auto / span auto; } -.cu-col-start-1 { +.bui-col-start-1 { grid-column-start: 1; } -.cu-col-start-2 { +.bui-col-start-2 { grid-column-start: 2; } -.cu-col-start-3 { +.bui-col-start-3 { grid-column-start: 3; } -.cu-col-start-4 { +.bui-col-start-4 { grid-column-start: 4; } -.cu-col-start-5 { +.bui-col-start-5 { grid-column-start: 5; } -.cu-col-start-6 { +.bui-col-start-6 { grid-column-start: 6; } -.cu-col-start-7 { +.bui-col-start-7 { grid-column-start: 7; } -.cu-col-start-8 { +.bui-col-start-8 { grid-column-start: 8; } -.cu-col-start-9 { +.bui-col-start-9 { grid-column-start: 9; } -.cu-col-start-10 { +.bui-col-start-10 { grid-column-start: 10; } -.cu-col-start-11 { +.bui-col-start-11 { grid-column-start: 11; } -.cu-col-start-12 { +.bui-col-start-12 { grid-column-start: 12; } -.cu-col-start-13 { +.bui-col-start-13 { grid-column-start: 13; } -.cu-col-start-auto { +.bui-col-start-auto { grid-column-start: auto; } -.cu-col-end-1 { +.bui-col-end-1 { grid-column-end: 1; } -.cu-col-end-2 { +.bui-col-end-2 { grid-column-end: 2; } -.cu-col-end-3 { +.bui-col-end-3 { grid-column-end: 3; } -.cu-col-end-4 { +.bui-col-end-4 { grid-column-end: 4; } -.cu-col-end-5 { +.bui-col-end-5 { grid-column-end: 5; } -.cu-col-end-6 { +.bui-col-end-6 { grid-column-end: 6; } -.cu-col-end-7 { +.bui-col-end-7 { grid-column-end: 7; } -.cu-col-end-8 { +.bui-col-end-8 { grid-column-end: 8; } -.cu-col-end-9 { +.bui-col-end-9 { grid-column-end: 9; } -.cu-col-end-10 { +.bui-col-end-10 { grid-column-end: 10; } -.cu-col-end-11 { +.bui-col-end-11 { grid-column-end: 11; } -.cu-col-end-12 { +.bui-col-end-12 { grid-column-end: 12; } -.cu-col-end-13 { +.bui-col-end-13 { grid-column-end: 13; } -.cu-col-end-auto { +.bui-col-end-auto { grid-column-end: auto; } -.cu-row-span-1 { +.bui-row-span-1 { grid-row: span 1 / span 1; } -.cu-row-span-2 { +.bui-row-span-2 { grid-row: span 2 / span 2; } -.cu-row-span-3 { +.bui-row-span-3 { grid-row: span 3 / span 3; } -.cu-row-span-4 { +.bui-row-span-4 { grid-row: span 4 / span 4; } -.cu-row-span-5 { +.bui-row-span-5 { grid-row: span 5 / span 5; } -.cu-row-span-6 { +.bui-row-span-6 { grid-row: span 6 / span 6; } -.cu-row-span-7 { +.bui-row-span-7 { grid-row: span 7 / span 7; } -.cu-row-span-8 { +.bui-row-span-8 { grid-row: span 8 / span 8; } -.cu-row-span-9 { +.bui-row-span-9 { grid-row: span 9 / span 9; } -.cu-row-span-10 { +.bui-row-span-10 { grid-row: span 10 / span 10; } -.cu-row-span-11 { +.bui-row-span-11 { grid-row: span 11 / span 11; } -.cu-row-span-12 { +.bui-row-span-12 { grid-row: span 12 / span 12; } -.cu-row-span-auto { +.bui-row-span-auto { grid-row: span auto / span auto; } /* Breakpoint xs */ @media (min-width: 640px) { - .xs\:cu-columns-1 { + .xs\:bui-columns-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } - .xs\:cu-columns-2 { + .xs\:bui-columns-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .xs\:cu-columns-3 { + .xs\:bui-columns-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } - .xs\:cu-columns-4 { + .xs\:bui-columns-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } - .xs\:cu-columns-5 { + .xs\:bui-columns-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); } - .xs\:cu-columns-6 { + .xs\:bui-columns-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); } - .xs\:cu-columns-7 { + .xs\:bui-columns-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); } - .xs\:cu-columns-8 { + .xs\:bui-columns-8 { grid-template-columns: repeat(8, minmax(0, 1fr)); } - .xs\:cu-columns-9 { + .xs\:bui-columns-9 { grid-template-columns: repeat(9, minmax(0, 1fr)); } - .xs\:cu-columns-10 { + .xs\:bui-columns-10 { grid-template-columns: repeat(10, minmax(0, 1fr)); } - .xs\:cu-columns-11 { + .xs\:bui-columns-11 { grid-template-columns: repeat(11, minmax(0, 1fr)); } - .xs\:cu-columns-12 { + .xs\:bui-columns-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); } - .xs\:cu-columns-auto { + .xs\:bui-columns-auto { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); } - .xs\:cu-col-span-1 { + .xs\:bui-col-span-1 { grid-column: span 1 / span 1; } - .xs\:cu-col-span-2 { + .xs\:bui-col-span-2 { grid-column: span 2 / span 2; } - .xs\:cu-col-span-3 { + .xs\:bui-col-span-3 { grid-column: span 3 / span 3; } - .xs\:cu-col-span-4 { + .xs\:bui-col-span-4 { grid-column: span 4 / span 4; } - .xs\:cu-col-span-5 { + .xs\:bui-col-span-5 { grid-column: span 5 / span 5; } - .xs\:cu-col-span-6 { + .xs\:bui-col-span-6 { grid-column: span 6 / span 6; } - .xs\:cu-col-span-7 { + .xs\:bui-col-span-7 { grid-column: span 7 / span 7; } - .xs\:cu-col-span-8 { + .xs\:bui-col-span-8 { grid-column: span 8 / span 8; } - .xs\:cu-col-span-9 { + .xs\:bui-col-span-9 { grid-column: span 9 / span 9; } - .xs\:cu-col-span-10 { + .xs\:bui-col-span-10 { grid-column: span 10 / span 10; } - .xs\:cu-col-span-11 { + .xs\:bui-col-span-11 { grid-column: span 11 / span 11; } - .xs\:cu-col-span-12 { + .xs\:bui-col-span-12 { grid-column: span 12 / span 12; } - .xs\:cu-col-span-auto { + .xs\:bui-col-span-auto { grid-column: span auto / span auto; } - .xs\:cu-col-start-1 { + .xs\:bui-col-start-1 { grid-column-start: 1; } - .xs\:cu-col-start-2 { + .xs\:bui-col-start-2 { grid-column-start: 2; } - .xs\:cu-col-start-3 { + .xs\:bui-col-start-3 { grid-column-start: 3; } - .xs\:cu-col-start-4 { + .xs\:bui-col-start-4 { grid-column-start: 4; } - .xs\:cu-col-start-5 { + .xs\:bui-col-start-5 { grid-column-start: 5; } - .xs\:cu-col-start-6 { + .xs\:bui-col-start-6 { grid-column-start: 6; } - .xs\:cu-col-start-7 { + .xs\:bui-col-start-7 { grid-column-start: 7; } - .xs\:cu-col-start-8 { + .xs\:bui-col-start-8 { grid-column-start: 8; } - .xs\:cu-col-start-9 { + .xs\:bui-col-start-9 { grid-column-start: 9; } - .xs\:cu-col-start-10 { + .xs\:bui-col-start-10 { grid-column-start: 10; } - .xs\:cu-col-start-11 { + .xs\:bui-col-start-11 { grid-column-start: 11; } - .xs\:cu-col-start-12 { + .xs\:bui-col-start-12 { grid-column-start: 12; } - .xs\:cu-col-start-13 { + .xs\:bui-col-start-13 { grid-column-start: 13; } - .xs\:cu-col-start-auto { + .xs\:bui-col-start-auto { grid-column-start: auto; } - .xs\:cu-col-end-1 { + .xs\:bui-col-end-1 { grid-column-end: 1; } - .xs\:cu-col-end-2 { + .xs\:bui-col-end-2 { grid-column-end: 2; } - .xs\:cu-col-end-3 { + .xs\:bui-col-end-3 { grid-column-end: 3; } - .xs\:cu-col-end-4 { + .xs\:bui-col-end-4 { grid-column-end: 4; } - .xs\:cu-col-end-5 { + .xs\:bui-col-end-5 { grid-column-end: 5; } - .xs\:cu-col-end-6 { + .xs\:bui-col-end-6 { grid-column-end: 6; } - .xs\:cu-col-end-7 { + .xs\:bui-col-end-7 { grid-column-end: 7; } - .xs\:cu-col-end-8 { + .xs\:bui-col-end-8 { grid-column-end: 8; } - .xs\:cu-col-end-9 { + .xs\:bui-col-end-9 { grid-column-end: 9; } - .xs\:cu-col-end-10 { + .xs\:bui-col-end-10 { grid-column-end: 10; } - .xs\:cu-col-end-11 { + .xs\:bui-col-end-11 { grid-column-end: 11; } - .xs\:cu-col-end-12 { + .xs\:bui-col-end-12 { grid-column-end: 12; } - .xs\:cu-col-end-13 { + .xs\:bui-col-end-13 { grid-column-end: 13; } - .xs\:cu-col-end-auto { + .xs\:bui-col-end-auto { grid-column-end: auto; } - .xs\:cu-row-span-1 { + .xs\:bui-row-span-1 { grid-row: span 1 / span 1; } - .xs\:cu-row-span-2 { + .xs\:bui-row-span-2 { grid-row: span 2 / span 2; } - .xs\:cu-row-span-3 { + .xs\:bui-row-span-3 { grid-row: span 3 / span 3; } - .xs\:cu-row-span-4 { + .xs\:bui-row-span-4 { grid-row: span 4 / span 4; } - .xs\:cu-row-span-5 { + .xs\:bui-row-span-5 { grid-row: span 5 / span 5; } - .xs\:cu-row-span-6 { + .xs\:bui-row-span-6 { grid-row: span 6 / span 6; } - .xs\:cu-row-span-7 { + .xs\:bui-row-span-7 { grid-row: span 7 / span 7; } - .xs\:cu-row-span-8 { + .xs\:bui-row-span-8 { grid-row: span 8 / span 8; } - .xs\:cu-row-span-9 { + .xs\:bui-row-span-9 { grid-row: span 9 / span 9; } - .xs\:cu-row-span-10 { + .xs\:bui-row-span-10 { grid-row: span 10 / span 10; } - .xs\:cu-row-span-11 { + .xs\:bui-row-span-11 { grid-row: span 11 / span 11; } - .xs\:cu-row-span-12 { + .xs\:bui-row-span-12 { grid-row: span 12 / span 12; } - .xs\:cu-row-span-auto { + .xs\:bui-row-span-auto { grid-row: span auto / span auto; } } /* Breakpoint sm */ @media (min-width: 768px) { - .sm\:cu-columns-1 { + .sm\:bui-columns-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } - .sm\:cu-columns-2 { + .sm\:bui-columns-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .sm\:cu-columns-3 { + .sm\:bui-columns-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } - .sm\:cu-columns-4 { + .sm\:bui-columns-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } - .sm\:cu-columns-5 { + .sm\:bui-columns-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); } - .sm\:cu-columns-6 { + .sm\:bui-columns-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); } - .sm\:cu-columns-7 { + .sm\:bui-columns-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); } - .sm\:cu-columns-8 { + .sm\:bui-columns-8 { grid-template-columns: repeat(8, minmax(0, 1fr)); } - .sm\:cu-columns-9 { + .sm\:bui-columns-9 { grid-template-columns: repeat(9, minmax(0, 1fr)); } - .sm\:cu-columns-10 { + .sm\:bui-columns-10 { grid-template-columns: repeat(10, minmax(0, 1fr)); } - .sm\:cu-columns-11 { + .sm\:bui-columns-11 { grid-template-columns: repeat(11, minmax(0, 1fr)); } - .sm\:cu-columns-12 { + .sm\:bui-columns-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); } - .sm\:cu-columns-auto { + .sm\:bui-columns-auto { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); } - .sm\:cu-col-span-1 { + .sm\:bui-col-span-1 { grid-column: span 1 / span 1; } - .sm\:cu-col-span-2 { + .sm\:bui-col-span-2 { grid-column: span 2 / span 2; } - .sm\:cu-col-span-3 { + .sm\:bui-col-span-3 { grid-column: span 3 / span 3; } - .sm\:cu-col-span-4 { + .sm\:bui-col-span-4 { grid-column: span 4 / span 4; } - .sm\:cu-col-span-5 { + .sm\:bui-col-span-5 { grid-column: span 5 / span 5; } - .sm\:cu-col-span-6 { + .sm\:bui-col-span-6 { grid-column: span 6 / span 6; } - .sm\:cu-col-span-7 { + .sm\:bui-col-span-7 { grid-column: span 7 / span 7; } - .sm\:cu-col-span-8 { + .sm\:bui-col-span-8 { grid-column: span 8 / span 8; } - .sm\:cu-col-span-9 { + .sm\:bui-col-span-9 { grid-column: span 9 / span 9; } - .sm\:cu-col-span-10 { + .sm\:bui-col-span-10 { grid-column: span 10 / span 10; } - .sm\:cu-col-span-11 { + .sm\:bui-col-span-11 { grid-column: span 11 / span 11; } - .sm\:cu-col-span-12 { + .sm\:bui-col-span-12 { grid-column: span 12 / span 12; } - .sm\:cu-col-span-auto { + .sm\:bui-col-span-auto { grid-column: span auto / span auto; } - .sm\:cu-col-start-1 { + .sm\:bui-col-start-1 { grid-column-start: 1; } - .sm\:cu-col-start-2 { + .sm\:bui-col-start-2 { grid-column-start: 2; } - .sm\:cu-col-start-3 { + .sm\:bui-col-start-3 { grid-column-start: 3; } - .sm\:cu-col-start-4 { + .sm\:bui-col-start-4 { grid-column-start: 4; } - .sm\:cu-col-start-5 { + .sm\:bui-col-start-5 { grid-column-start: 5; } - .sm\:cu-col-start-6 { + .sm\:bui-col-start-6 { grid-column-start: 6; } - .sm\:cu-col-start-7 { + .sm\:bui-col-start-7 { grid-column-start: 7; } - .sm\:cu-col-start-8 { + .sm\:bui-col-start-8 { grid-column-start: 8; } - .sm\:cu-col-start-9 { + .sm\:bui-col-start-9 { grid-column-start: 9; } - .sm\:cu-col-start-10 { + .sm\:bui-col-start-10 { grid-column-start: 10; } - .sm\:cu-col-start-11 { + .sm\:bui-col-start-11 { grid-column-start: 11; } - .sm\:cu-col-start-12 { + .sm\:bui-col-start-12 { grid-column-start: 12; } - .sm\:cu-col-start-13 { + .sm\:bui-col-start-13 { grid-column-start: 13; } - .sm\:cu-col-start-auto { + .sm\:bui-col-start-auto { grid-column-start: auto; } - .sm\:cu-col-end-1 { + .sm\:bui-col-end-1 { grid-column-end: 1; } - .sm\:cu-col-end-2 { + .sm\:bui-col-end-2 { grid-column-end: 2; } - .sm\:cu-col-end-3 { + .sm\:bui-col-end-3 { grid-column-end: 3; } - .sm\:cu-col-end-4 { + .sm\:bui-col-end-4 { grid-column-end: 4; } - .sm\:cu-col-end-5 { + .sm\:bui-col-end-5 { grid-column-end: 5; } - .sm\:cu-col-end-6 { + .sm\:bui-col-end-6 { grid-column-end: 6; } - .sm\:cu-col-end-7 { + .sm\:bui-col-end-7 { grid-column-end: 7; } - .sm\:cu-col-end-8 { + .sm\:bui-col-end-8 { grid-column-end: 8; } - .sm\:cu-col-end-9 { + .sm\:bui-col-end-9 { grid-column-end: 9; } - .sm\:cu-col-end-10 { + .sm\:bui-col-end-10 { grid-column-end: 10; } - .sm\:cu-col-end-11 { + .sm\:bui-col-end-11 { grid-column-end: 11; } - .sm\:cu-col-end-12 { + .sm\:bui-col-end-12 { grid-column-end: 12; } - .sm\:cu-col-end-13 { + .sm\:bui-col-end-13 { grid-column-end: 13; } - .sm\:cu-col-end-auto { + .sm\:bui-col-end-auto { grid-column-end: auto; } - .sm\:cu-row-span-1 { + .sm\:bui-row-span-1 { grid-row: span 1 / span 1; } - .sm\:cu-row-span-2 { + .sm\:bui-row-span-2 { grid-row: span 2 / span 2; } - .sm\:cu-row-span-3 { + .sm\:bui-row-span-3 { grid-row: span 3 / span 3; } - .sm\:cu-row-span-4 { + .sm\:bui-row-span-4 { grid-row: span 4 / span 4; } - .sm\:cu-row-span-5 { + .sm\:bui-row-span-5 { grid-row: span 5 / span 5; } - .sm\:cu-row-span-6 { + .sm\:bui-row-span-6 { grid-row: span 6 / span 6; } - .sm\:cu-row-span-7 { + .sm\:bui-row-span-7 { grid-row: span 7 / span 7; } - .sm\:cu-row-span-8 { + .sm\:bui-row-span-8 { grid-row: span 8 / span 8; } - .sm\:cu-row-span-9 { + .sm\:bui-row-span-9 { grid-row: span 9 / span 9; } - .sm\:cu-row-span-10 { + .sm\:bui-row-span-10 { grid-row: span 10 / span 10; } - .sm\:cu-row-span-11 { + .sm\:bui-row-span-11 { grid-row: span 11 / span 11; } - .sm\:cu-row-span-12 { + .sm\:bui-row-span-12 { grid-row: span 12 / span 12; } - .sm\:cu-row-span-auto { + .sm\:bui-row-span-auto { grid-row: span auto / span auto; } } /* Breakpoint md */ @media (min-width: 1024px) { - .md\:cu-columns-1 { + .md\:bui-columns-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } - .md\:cu-columns-2 { + .md\:bui-columns-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .md\:cu-columns-3 { + .md\:bui-columns-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } - .md\:cu-columns-4 { + .md\:bui-columns-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } - .md\:cu-columns-5 { + .md\:bui-columns-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); } - .md\:cu-columns-6 { + .md\:bui-columns-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); } - .md\:cu-columns-7 { + .md\:bui-columns-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); } - .md\:cu-columns-8 { + .md\:bui-columns-8 { grid-template-columns: repeat(8, minmax(0, 1fr)); } - .md\:cu-columns-9 { + .md\:bui-columns-9 { grid-template-columns: repeat(9, minmax(0, 1fr)); } - .md\:cu-columns-10 { + .md\:bui-columns-10 { grid-template-columns: repeat(10, minmax(0, 1fr)); } - .md\:cu-columns-11 { + .md\:bui-columns-11 { grid-template-columns: repeat(11, minmax(0, 1fr)); } - .md\:cu-columns-12 { + .md\:bui-columns-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); } - .md\:cu-columns-auto { + .md\:bui-columns-auto { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); } - .md\:cu-col-span-1 { + .md\:bui-col-span-1 { grid-column: span 1 / span 1; } - .md\:cu-col-span-2 { + .md\:bui-col-span-2 { grid-column: span 2 / span 2; } - .md\:cu-col-span-3 { + .md\:bui-col-span-3 { grid-column: span 3 / span 3; } - .md\:cu-col-span-4 { + .md\:bui-col-span-4 { grid-column: span 4 / span 4; } - .md\:cu-col-span-5 { + .md\:bui-col-span-5 { grid-column: span 5 / span 5; } - .md\:cu-col-span-6 { + .md\:bui-col-span-6 { grid-column: span 6 / span 6; } - .md\:cu-col-span-7 { + .md\:bui-col-span-7 { grid-column: span 7 / span 7; } - .md\:cu-col-span-8 { + .md\:bui-col-span-8 { grid-column: span 8 / span 8; } - .md\:cu-col-span-9 { + .md\:bui-col-span-9 { grid-column: span 9 / span 9; } - .md\:cu-col-span-10 { + .md\:bui-col-span-10 { grid-column: span 10 / span 10; } - .md\:cu-col-span-11 { + .md\:bui-col-span-11 { grid-column: span 11 / span 11; } - .md\:cu-col-span-12 { + .md\:bui-col-span-12 { grid-column: span 12 / span 12; } - .md\:cu-col-span-auto { + .md\:bui-col-span-auto { grid-column: span auto / span auto; } - .md\:cu-col-start-1 { + .md\:bui-col-start-1 { grid-column-start: 1; } - .md\:cu-col-start-2 { + .md\:bui-col-start-2 { grid-column-start: 2; } - .md\:cu-col-start-3 { + .md\:bui-col-start-3 { grid-column-start: 3; } - .md\:cu-col-start-4 { + .md\:bui-col-start-4 { grid-column-start: 4; } - .md\:cu-col-start-5 { + .md\:bui-col-start-5 { grid-column-start: 5; } - .md\:cu-col-start-6 { + .md\:bui-col-start-6 { grid-column-start: 6; } - .md\:cu-col-start-7 { + .md\:bui-col-start-7 { grid-column-start: 7; } - .md\:cu-col-start-8 { + .md\:bui-col-start-8 { grid-column-start: 8; } - .md\:cu-col-start-9 { + .md\:bui-col-start-9 { grid-column-start: 9; } - .md\:cu-col-start-10 { + .md\:bui-col-start-10 { grid-column-start: 10; } - .md\:cu-col-start-11 { + .md\:bui-col-start-11 { grid-column-start: 11; } - .md\:cu-col-start-12 { + .md\:bui-col-start-12 { grid-column-start: 12; } - .md\:cu-col-start-13 { + .md\:bui-col-start-13 { grid-column-start: 13; } - .md\:cu-col-start-auto { + .md\:bui-col-start-auto { grid-column-start: auto; } - .md\:cu-col-end-1 { + .md\:bui-col-end-1 { grid-column-end: 1; } - .md\:cu-col-end-2 { + .md\:bui-col-end-2 { grid-column-end: 2; } - .md\:cu-col-end-3 { + .md\:bui-col-end-3 { grid-column-end: 3; } - .md\:cu-col-end-4 { + .md\:bui-col-end-4 { grid-column-end: 4; } - .md\:cu-col-end-5 { + .md\:bui-col-end-5 { grid-column-end: 5; } - .md\:cu-col-end-6 { + .md\:bui-col-end-6 { grid-column-end: 6; } - .md\:cu-col-end-7 { + .md\:bui-col-end-7 { grid-column-end: 7; } - .md\:cu-col-end-8 { + .md\:bui-col-end-8 { grid-column-end: 8; } - .md\:cu-col-end-9 { + .md\:bui-col-end-9 { grid-column-end: 9; } - .md\:cu-col-end-10 { + .md\:bui-col-end-10 { grid-column-end: 10; } - .md\:cu-col-end-11 { + .md\:bui-col-end-11 { grid-column-end: 11; } - .md\:cu-col-end-12 { + .md\:bui-col-end-12 { grid-column-end: 12; } - .md\:cu-col-end-13 { + .md\:bui-col-end-13 { grid-column-end: 13; } - .md\:cu-col-end-auto { + .md\:bui-col-end-auto { grid-column-end: auto; } - .md\:cu-row-span-1 { + .md\:bui-row-span-1 { grid-row: span 1 / span 1; } - .md\:cu-row-span-2 { + .md\:bui-row-span-2 { grid-row: span 2 / span 2; } - .md\:cu-row-span-3 { + .md\:bui-row-span-3 { grid-row: span 3 / span 3; } - .md\:cu-row-span-4 { + .md\:bui-row-span-4 { grid-row: span 4 / span 4; } - .md\:cu-row-span-5 { + .md\:bui-row-span-5 { grid-row: span 5 / span 5; } - .md\:cu-row-span-6 { + .md\:bui-row-span-6 { grid-row: span 6 / span 6; } - .md\:cu-row-span-7 { + .md\:bui-row-span-7 { grid-row: span 7 / span 7; } - .md\:cu-row-span-8 { + .md\:bui-row-span-8 { grid-row: span 8 / span 8; } - .md\:cu-row-span-9 { + .md\:bui-row-span-9 { grid-row: span 9 / span 9; } - .md\:cu-row-span-10 { + .md\:bui-row-span-10 { grid-row: span 10 / span 10; } - .md\:cu-row-span-11 { + .md\:bui-row-span-11 { grid-row: span 11 / span 11; } - .md\:cu-row-span-12 { + .md\:bui-row-span-12 { grid-row: span 12 / span 12; } - .md\:cu-row-span-auto { + .md\:bui-row-span-auto { grid-row: span auto / span auto; } } /* Breakpoint lg */ @media (min-width: 1280px) { - .lg\:cu-columns-1 { + .lg\:bui-columns-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } - .lg\:cu-columns-2 { + .lg\:bui-columns-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .lg\:cu-columns-3 { + .lg\:bui-columns-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } - .lg\:cu-columns-4 { + .lg\:bui-columns-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } - .lg\:cu-columns-5 { + .lg\:bui-columns-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); } - .lg\:cu-columns-6 { + .lg\:bui-columns-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); } - .lg\:cu-columns-7 { + .lg\:bui-columns-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); } - .lg\:cu-columns-8 { + .lg\:bui-columns-8 { grid-template-columns: repeat(8, minmax(0, 1fr)); } - .lg\:cu-columns-9 { + .lg\:bui-columns-9 { grid-template-columns: repeat(9, minmax(0, 1fr)); } - .lg\:cu-columns-10 { + .lg\:bui-columns-10 { grid-template-columns: repeat(10, minmax(0, 1fr)); } - .lg\:cu-columns-11 { + .lg\:bui-columns-11 { grid-template-columns: repeat(11, minmax(0, 1fr)); } - .lg\:cu-columns-12 { + .lg\:bui-columns-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); } - .lg\:cu-columns-auto { + .lg\:bui-columns-auto { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); } - .lg\:cu-col-span-1 { + .lg\:bui-col-span-1 { grid-column: span 1 / span 1; } - .lg\:cu-col-span-2 { + .lg\:bui-col-span-2 { grid-column: span 2 / span 2; } - .lg\:cu-col-span-3 { + .lg\:bui-col-span-3 { grid-column: span 3 / span 3; } - .lg\:cu-col-span-4 { + .lg\:bui-col-span-4 { grid-column: span 4 / span 4; } - .lg\:cu-col-span-5 { + .lg\:bui-col-span-5 { grid-column: span 5 / span 5; } - .lg\:cu-col-span-6 { + .lg\:bui-col-span-6 { grid-column: span 6 / span 6; } - .lg\:cu-col-span-7 { + .lg\:bui-col-span-7 { grid-column: span 7 / span 7; } - .lg\:cu-col-span-8 { + .lg\:bui-col-span-8 { grid-column: span 8 / span 8; } - .lg\:cu-col-span-9 { + .lg\:bui-col-span-9 { grid-column: span 9 / span 9; } - .lg\:cu-col-span-10 { + .lg\:bui-col-span-10 { grid-column: span 10 / span 10; } - .lg\:cu-col-span-11 { + .lg\:bui-col-span-11 { grid-column: span 11 / span 11; } - .lg\:cu-col-span-12 { + .lg\:bui-col-span-12 { grid-column: span 12 / span 12; } - .lg\:cu-col-span-auto { + .lg\:bui-col-span-auto { grid-column: span auto / span auto; } - .lg\:cu-col-start-1 { + .lg\:bui-col-start-1 { grid-column-start: 1; } - .lg\:cu-col-start-2 { + .lg\:bui-col-start-2 { grid-column-start: 2; } - .lg\:cu-col-start-3 { + .lg\:bui-col-start-3 { grid-column-start: 3; } - .lg\:cu-col-start-4 { + .lg\:bui-col-start-4 { grid-column-start: 4; } - .lg\:cu-col-start-5 { + .lg\:bui-col-start-5 { grid-column-start: 5; } - .lg\:cu-col-start-6 { + .lg\:bui-col-start-6 { grid-column-start: 6; } - .lg\:cu-col-start-7 { + .lg\:bui-col-start-7 { grid-column-start: 7; } - .lg\:cu-col-start-8 { + .lg\:bui-col-start-8 { grid-column-start: 8; } - .lg\:cu-col-start-9 { + .lg\:bui-col-start-9 { grid-column-start: 9; } - .lg\:cu-col-start-10 { + .lg\:bui-col-start-10 { grid-column-start: 10; } - .lg\:cu-col-start-11 { + .lg\:bui-col-start-11 { grid-column-start: 11; } - .lg\:cu-col-start-12 { + .lg\:bui-col-start-12 { grid-column-start: 12; } - .lg\:cu-col-start-13 { + .lg\:bui-col-start-13 { grid-column-start: 13; } - .lg\:cu-col-start-auto { + .lg\:bui-col-start-auto { grid-column-start: auto; } - .lg\:cu-col-end-1 { + .lg\:bui-col-end-1 { grid-column-end: 1; } - .lg\:cu-col-end-2 { + .lg\:bui-col-end-2 { grid-column-end: 2; } - .lg\:cu-col-end-3 { + .lg\:bui-col-end-3 { grid-column-end: 3; } - .lg\:cu-col-end-4 { + .lg\:bui-col-end-4 { grid-column-end: 4; } - .lg\:cu-col-end-5 { + .lg\:bui-col-end-5 { grid-column-end: 5; } - .lg\:cu-col-end-6 { + .lg\:bui-col-end-6 { grid-column-end: 6; } - .lg\:cu-col-end-7 { + .lg\:bui-col-end-7 { grid-column-end: 7; } - .lg\:cu-col-end-8 { + .lg\:bui-col-end-8 { grid-column-end: 8; } - .lg\:cu-col-end-9 { + .lg\:bui-col-end-9 { grid-column-end: 9; } - .lg\:cu-col-end-10 { + .lg\:bui-col-end-10 { grid-column-end: 10; } - .lg\:cu-col-end-11 { + .lg\:bui-col-end-11 { grid-column-end: 11; } - .lg\:cu-col-end-12 { + .lg\:bui-col-end-12 { grid-column-end: 12; } - .lg\:cu-col-end-13 { + .lg\:bui-col-end-13 { grid-column-end: 13; } - .lg\:cu-col-end-auto { + .lg\:bui-col-end-auto { grid-column-end: auto; } - .lg\:cu-row-span-1 { + .lg\:bui-row-span-1 { grid-row: span 1 / span 1; } - .lg\:cu-row-span-2 { + .lg\:bui-row-span-2 { grid-row: span 2 / span 2; } - .lg\:cu-row-span-3 { + .lg\:bui-row-span-3 { grid-row: span 3 / span 3; } - .lg\:cu-row-span-4 { + .lg\:bui-row-span-4 { grid-row: span 4 / span 4; } - .lg\:cu-row-span-5 { + .lg\:bui-row-span-5 { grid-row: span 5 / span 5; } - .lg\:cu-row-span-6 { + .lg\:bui-row-span-6 { grid-row: span 6 / span 6; } - .lg\:cu-row-span-7 { + .lg\:bui-row-span-7 { grid-row: span 7 / span 7; } - .lg\:cu-row-span-8 { + .lg\:bui-row-span-8 { grid-row: span 8 / span 8; } - .lg\:cu-row-span-9 { + .lg\:bui-row-span-9 { grid-row: span 9 / span 9; } - .lg\:cu-row-span-10 { + .lg\:bui-row-span-10 { grid-row: span 10 / span 10; } - .lg\:cu-row-span-11 { + .lg\:bui-row-span-11 { grid-row: span 11 / span 11; } - .lg\:cu-row-span-12 { + .lg\:bui-row-span-12 { grid-row: span 12 / span 12; } - .lg\:cu-row-span-auto { + .lg\:bui-row-span-auto { grid-row: span auto / span auto; } } /* Breakpoint xl */ @media (min-width: 1536px) { - .xl\:cu-columns-1 { + .xl\:bui-columns-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } - .xl\:cu-columns-2 { + .xl\:bui-columns-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .xl\:cu-columns-3 { + .xl\:bui-columns-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } - .xl\:cu-columns-4 { + .xl\:bui-columns-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } - .xl\:cu-columns-5 { + .xl\:bui-columns-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); } - .xl\:cu-columns-6 { + .xl\:bui-columns-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); } - .xl\:cu-columns-7 { + .xl\:bui-columns-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); } - .xl\:cu-columns-8 { + .xl\:bui-columns-8 { grid-template-columns: repeat(8, minmax(0, 1fr)); } - .xl\:cu-columns-9 { + .xl\:bui-columns-9 { grid-template-columns: repeat(9, minmax(0, 1fr)); } - .xl\:cu-columns-10 { + .xl\:bui-columns-10 { grid-template-columns: repeat(10, minmax(0, 1fr)); } - .xl\:cu-columns-11 { + .xl\:bui-columns-11 { grid-template-columns: repeat(11, minmax(0, 1fr)); } - .xl\:cu-columns-12 { + .xl\:bui-columns-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); } - .xl\:cu-columns-auto { + .xl\:bui-columns-auto { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); } - .xl\:cu-col-span-1 { + .xl\:bui-col-span-1 { grid-column: span 1 / span 1; } - .xl\:cu-col-span-2 { + .xl\:bui-col-span-2 { grid-column: span 2 / span 2; } - .xl\:cu-col-span-3 { + .xl\:bui-col-span-3 { grid-column: span 3 / span 3; } - .xl\:cu-col-span-4 { + .xl\:bui-col-span-4 { grid-column: span 4 / span 4; } - .xl\:cu-col-span-5 { + .xl\:bui-col-span-5 { grid-column: span 5 / span 5; } - .xl\:cu-col-span-6 { + .xl\:bui-col-span-6 { grid-column: span 6 / span 6; } - .xl\:cu-col-span-7 { + .xl\:bui-col-span-7 { grid-column: span 7 / span 7; } - .xl\:cu-col-span-8 { + .xl\:bui-col-span-8 { grid-column: span 8 / span 8; } - .xl\:cu-col-span-9 { + .xl\:bui-col-span-9 { grid-column: span 9 / span 9; } - .xl\:cu-col-span-10 { + .xl\:bui-col-span-10 { grid-column: span 10 / span 10; } - .xl\:cu-col-span-11 { + .xl\:bui-col-span-11 { grid-column: span 11 / span 11; } - .xl\:cu-col-span-12 { + .xl\:bui-col-span-12 { grid-column: span 12 / span 12; } - .xl\:cu-col-span-auto { + .xl\:bui-col-span-auto { grid-column: span auto / span auto; } - .xl\:cu-col-start-1 { + .xl\:bui-col-start-1 { grid-column-start: 1; } - .xl\:cu-col-start-2 { + .xl\:bui-col-start-2 { grid-column-start: 2; } - .xl\:cu-col-start-3 { + .xl\:bui-col-start-3 { grid-column-start: 3; } - .xl\:cu-col-start-4 { + .xl\:bui-col-start-4 { grid-column-start: 4; } - .xl\:cu-col-start-5 { + .xl\:bui-col-start-5 { grid-column-start: 5; } - .xl\:cu-col-start-6 { + .xl\:bui-col-start-6 { grid-column-start: 6; } - .xl\:cu-col-start-7 { + .xl\:bui-col-start-7 { grid-column-start: 7; } - .xl\:cu-col-start-8 { + .xl\:bui-col-start-8 { grid-column-start: 8; } - .xl\:cu-col-start-9 { + .xl\:bui-col-start-9 { grid-column-start: 9; } - .xl\:cu-col-start-10 { + .xl\:bui-col-start-10 { grid-column-start: 10; } - .xl\:cu-col-start-11 { + .xl\:bui-col-start-11 { grid-column-start: 11; } - .xl\:cu-col-start-12 { + .xl\:bui-col-start-12 { grid-column-start: 12; } - .xl\:cu-col-start-13 { + .xl\:bui-col-start-13 { grid-column-start: 13; } - .xl\:cu-col-start-auto { + .xl\:bui-col-start-auto { grid-column-start: auto; } - .xl\:cu-col-end-1 { + .xl\:bui-col-end-1 { grid-column-end: 1; } - .xl\:cu-col-end-2 { + .xl\:bui-col-end-2 { grid-column-end: 2; } - .xl\:cu-col-end-3 { + .xl\:bui-col-end-3 { grid-column-end: 3; } - .xl\:cu-col-end-4 { + .xl\:bui-col-end-4 { grid-column-end: 4; } - .xl\:cu-col-end-5 { + .xl\:bui-col-end-5 { grid-column-end: 5; } - .xl\:cu-col-end-6 { + .xl\:bui-col-end-6 { grid-column-end: 6; } - .xl\:cu-col-end-7 { + .xl\:bui-col-end-7 { grid-column-end: 7; } - .xl\:cu-col-end-8 { + .xl\:bui-col-end-8 { grid-column-end: 8; } - .xl\:cu-col-end-9 { + .xl\:bui-col-end-9 { grid-column-end: 9; } - .xl\:cu-col-end-10 { + .xl\:bui-col-end-10 { grid-column-end: 10; } - .xl\:cu-col-end-11 { + .xl\:bui-col-end-11 { grid-column-end: 11; } - .xl\:cu-col-end-12 { + .xl\:bui-col-end-12 { grid-column-end: 12; } - .xl\:cu-col-end-13 { + .xl\:bui-col-end-13 { grid-column-end: 13; } - .xl\:cu-col-end-auto { + .xl\:bui-col-end-auto { grid-column-end: auto; } - .xl\:cu-row-span-1 { + .xl\:bui-row-span-1 { grid-row: span 1 / span 1; } - .xl\:cu-row-span-2 { + .xl\:bui-row-span-2 { grid-row: span 2 / span 2; } - .xl\:cu-row-span-3 { + .xl\:bui-row-span-3 { grid-row: span 3 / span 3; } - .xl\:cu-row-span-4 { + .xl\:bui-row-span-4 { grid-row: span 4 / span 4; } - .xl\:cu-row-span-5 { + .xl\:bui-row-span-5 { grid-row: span 5 / span 5; } - .xl\:cu-row-span-6 { + .xl\:bui-row-span-6 { grid-row: span 6 / span 6; } - .xl\:cu-row-span-7 { + .xl\:bui-row-span-7 { grid-row: span 7 / span 7; } - .xl\:cu-row-span-8 { + .xl\:bui-row-span-8 { grid-row: span 8 / span 8; } - .xl\:cu-row-span-9 { + .xl\:bui-row-span-9 { grid-row: span 9 / span 9; } - .xl\:cu-row-span-10 { + .xl\:bui-row-span-10 { grid-row: span 10 / span 10; } - .xl\:cu-row-span-11 { + .xl\:bui-row-span-11 { grid-row: span 11 / span 11; } - .xl\:cu-row-span-12 { + .xl\:bui-row-span-12 { grid-row: span 12 / span 12; } - .xl\:cu-row-span-auto { + .xl\:bui-row-span-auto { grid-row: span auto / span auto; } } diff --git a/packages/canon/src/css/utilities/height.css b/packages/ui/src/css/utilities/height.css similarity index 75% rename from packages/canon/src/css/utilities/height.css rename to packages/ui/src/css/utilities/height.css index 130dfc5020..5c245cbfb9 100644 --- a/packages/canon/src/css/utilities/height.css +++ b/packages/ui/src/css/utilities/height.css @@ -1,86 +1,86 @@ -.cu-h { +.bui-h { height: var(--height); } -.cu-min-h { +.bui-min-h { min-height: var(--min-height); } -.cu-max-h { +.bui-max-h { max-height: var(--max-height); } /* Breakpoint xs */ @media (min-width: 640px) { - .xs\:cu-h { + .xs\:bui-h { height: var(--height); } - .xs\:cu-min-h { + .xs\:bui-min-h { min-height: var(--min-height); } - .xs\:cu-max-h { + .xs\:bui-max-h { max-height: var(--max-height); } } /* Breakpoint sm */ @media (min-width: 768px) { - .sm\:cu-h { + .sm\:bui-h { height: var(--height); } - .sm\:cu-min-h { + .sm\:bui-min-h { min-height: var(--min-height); } - .sm\:cu-max-h { + .sm\:bui-max-h { max-height: var(--max-height); } } /* Breakpoint md */ @media (min-width: 1024px) { - .md\:cu-h { + .md\:bui-h { height: var(--height); } - .md\:cu-min-h { + .md\:bui-min-h { min-height: var(--min-height); } - .md\:cu-max-h { + .md\:bui-max-h { max-height: var(--max-height); } } /* Breakpoint lg */ @media (min-width: 1280px) { - .lg\:cu-h { + .lg\:bui-h { height: var(--height); } - .lg\:cu-min-h { + .lg\:bui-min-h { min-height: var(--min-height); } - .lg\:cu-max-h { + .lg\:bui-max-h { max-height: var(--max-height); } } /* Breakpoint xl */ @media (min-width: 1536px) { - .xl\:cu-h { + .xl\:bui-h { height: var(--height); } - .xl\:cu-min-h { + .xl\:bui-min-h { min-height: var(--min-height); } - .xl\:cu-max-h { + .xl\:bui-max-h { max-height: var(--max-height); } } diff --git a/packages/ui/src/css/utilities/m.css b/packages/ui/src/css/utilities/m.css new file mode 100644 index 0000000000..b2c1099d9b --- /dev/null +++ b/packages/ui/src/css/utilities/m.css @@ -0,0 +1,422 @@ +.bui-m { + margin: var(--m); +} + +.bui-m-0\.5 { + margin: var(--bui-space-0_5); +} + +.bui-m-1 { + margin: var(--bui-space-1); +} + +.bui-m-1\.5 { + margin: var(--bui-space-1_5); +} + +.bui-m-2 { + margin: var(--bui-space-2); +} + +.bui-m-3 { + margin: var(--bui-space-3); +} + +.bui-m-4 { + margin: var(--bui-space-4); +} + +.bui-m-5 { + margin: var(--bui-space-5); +} + +.bui-m-6 { + margin: var(--bui-space-6); +} + +.bui-m-7 { + margin: var(--bui-space-7); +} + +.bui-m-8 { + margin: var(--bui-space-8); +} + +.bui-m-9 { + margin: var(--bui-space-9); +} + +.bui-m-10 { + margin: var(--bui-space-10); +} + +.bui-m-11 { + margin: var(--bui-space-11); +} + +.bui-m-12 { + margin: var(--bui-space-12); +} + +.bui-m-13 { + margin: var(--bui-space-13); +} + +.bui-m-14 { + margin: var(--bui-space-14); +} + +/* Breakpoint xs */ +@media (min-width: 640px) { + .xs\:bui-m { + margin: var(--p-xs); + } + + .xs\:bui-m-0\.5 { + margin: var(--bui-space-0_5); + } + + .xs\:bui-m-1 { + margin: var(--bui-space-1); + } + + .xs\:bui-m-1\.5 { + margin: var(--bui-space-1_5); + } + + .xs\:bui-m-2 { + margin: var(--bui-space-2); + } + + .xs\:bui-m-3 { + margin: var(--bui-space-3); + } + + .xs\:bui-m-4 { + margin: var(--bui-space-4); + } + + .xs\:bui-m-5 { + margin: var(--bui-space-5); + } + + .xs\:bui-m-6 { + margin: var(--bui-space-6); + } + + .xs\:bui-m-7 { + margin: var(--bui-space-7); + } + + .xs\:bui-m-8 { + margin: var(--bui-space-8); + } + + .xs\:bui-m-9 { + margin: var(--bui-space-9); + } + + .xs\:bui-m-10 { + margin: var(--bui-space-10); + } + + .xs\:bui-m-11 { + margin: var(--bui-space-11); + } + + .xs\:bui-m-12 { + margin: var(--bui-space-12); + } + + .xs\:bui-m-13 { + margin: var(--bui-space-13); + } + + .xs\:bui-m-14 { + margin: var(--bui-space-14); + } +} + +/* Breakpoint sm */ +@media (min-width: 768px) { + .sm\:bui-m { + margin: var(--p-sm); + } + + .sm\:bui-m-0\.5 { + margin: var(--bui-space-0_5); + } + + .sm\:bui-m-1 { + margin: var(--bui-space-1); + } + + .sm\:bui-m-1\.5 { + margin: var(--bui-space-1_5); + } + + .sm\:bui-m-2 { + margin: var(--bui-space-2); + } + + .sm\:bui-m-3 { + margin: var(--bui-space-3); + } + + .sm\:bui-m-4 { + margin: var(--bui-space-4); + } + + .sm\:bui-m-5 { + margin: var(--bui-space-5); + } + + .sm\:bui-m-6 { + margin: var(--bui-space-6); + } + + .sm\:bui-m-7 { + margin: var(--bui-space-7); + } + + .sm\:bui-m-8 { + margin: var(--bui-space-8); + } + + .sm\:bui-m-9 { + margin: var(--bui-space-9); + } + + .sm\:bui-m-10 { + margin: var(--bui-space-10); + } + + .sm\:bui-m-11 { + margin: var(--bui-space-11); + } + + .sm\:bui-m-12 { + margin: var(--bui-space-12); + } + + .sm\:bui-m-13 { + margin: var(--bui-space-13); + } + + .sm\:bui-m-14 { + margin: var(--bui-space-14); + } +} + +/* Breakpoint md */ +@media (min-width: 1024px) { + .md\:bui-m { + margin: var(--p-md); + } + + .md\:bui-m-0\.5 { + margin: var(--bui-space-0_5); + } + + .md\:bui-m-1 { + margin: var(--bui-space-1); + } + + .md\:bui-m-1\.5 { + margin: var(--bui-space-1_5); + } + + .md\:bui-m-2 { + margin: var(--bui-space-2); + } + + .md\:bui-m-3 { + margin: var(--bui-space-3); + } + + .md\:bui-m-4 { + margin: var(--bui-space-4); + } + + .md\:bui-m-5 { + margin: var(--bui-space-5); + } + + .md\:bui-m-6 { + margin: var(--bui-space-6); + } + + .md\:bui-m-7 { + margin: var(--bui-space-7); + } + + .md\:bui-m-8 { + margin: var(--bui-space-8); + } + + .md\:bui-m-9 { + margin: var(--bui-space-9); + } + + .md\:bui-m-10 { + margin: var(--bui-space-10); + } + + .md\:bui-m-11 { + margin: var(--bui-space-11); + } + + .md\:bui-m-12 { + margin: var(--bui-space-12); + } + + .md\:bui-m-13 { + margin: var(--bui-space-13); + } + + .md\:bui-m-14 { + margin: var(--bui-space-14); + } +} + +/* Breakpoint lg */ +@media (min-width: 1280px) { + .lg\:bui-m { + margin: var(--p-lg); + } + + .lg\:bui-m-0\.5 { + margin: var(--bui-space-0_5); + } + + .lg\:bui-m-1 { + margin: var(--bui-space-1); + } + + .lg\:bui-m-1\.5 { + margin: var(--bui-space-1_5); + } + + .lg\:bui-m-2 { + margin: var(--bui-space-2); + } + + .lg\:bui-m-3 { + margin: var(--bui-space-3); + } + + .lg\:bui-m-4 { + margin: var(--bui-space-4); + } + + .lg\:bui-m-5 { + margin: var(--bui-space-5); + } + + .lg\:bui-m-6 { + margin: var(--bui-space-6); + } + + .lg\:bui-m-7 { + margin: var(--bui-space-7); + } + + .lg\:bui-m-8 { + margin: var(--bui-space-8); + } + + .lg\:bui-m-9 { + margin: var(--bui-space-9); + } + + .lg\:bui-m-10 { + margin: var(--bui-space-10); + } + + .lg\:bui-m-11 { + margin: var(--bui-space-11); + } + + .lg\:bui-m-12 { + margin: var(--bui-space-12); + } + + .lg\:bui-m-13 { + margin: var(--bui-space-13); + } + + .lg\:bui-m-14 { + margin: var(--bui-space-14); + } +} + +/* Breakpoint xl */ +@media (min-width: 1536px) { + .xl\:bui-m { + margin: var(--p-xl); + } + + .xl\:bui-m-0\.5 { + margin: var(--bui-space-0_5); + } + + .xl\:bui-m-1 { + margin: var(--bui-space-1); + } + + .xl\:bui-m-1\.5 { + margin: var(--bui-space-1_5); + } + + .xl\:bui-m-2 { + margin: var(--bui-space-2); + } + + .xl\:bui-m-3 { + margin: var(--bui-space-3); + } + + .xl\:bui-m-4 { + margin: var(--bui-space-4); + } + + .xl\:bui-m-5 { + margin: var(--bui-space-5); + } + + .xl\:bui-m-6 { + margin: var(--bui-space-6); + } + + .xl\:bui-m-7 { + margin: var(--bui-space-7); + } + + .xl\:bui-m-8 { + margin: var(--bui-space-8); + } + + .xl\:bui-m-9 { + margin: var(--bui-space-9); + } + + .xl\:bui-m-10 { + margin: var(--bui-space-10); + } + + .xl\:bui-m-11 { + margin: var(--bui-space-11); + } + + .xl\:bui-m-12 { + margin: var(--bui-space-12); + } + + .xl\:bui-m-13 { + margin: var(--bui-space-13); + } + + .xl\:bui-m-14 { + margin: var(--bui-space-14); + } +} diff --git a/packages/ui/src/css/utilities/mb.css b/packages/ui/src/css/utilities/mb.css new file mode 100644 index 0000000000..576d4d488e --- /dev/null +++ b/packages/ui/src/css/utilities/mb.css @@ -0,0 +1,422 @@ +.bui-mb { + margin-bottom: var(--mb); +} + +.bui-mb-0\.5 { + margin-bottom: var(--bui-space-0_5); +} + +.bui-mb-1 { + margin-bottom: var(--bui-space-1); +} + +.bui-mb-1\.5 { + margin-bottom: var(--bui-space-1_5); +} + +.bui-mb-2 { + margin-bottom: var(--bui-space-2); +} + +.bui-mb-3 { + margin-bottom: var(--bui-space-3); +} + +.bui-mb-4 { + margin-bottom: var(--bui-space-4); +} + +.bui-mb-5 { + margin-bottom: var(--bui-space-5); +} + +.bui-mb-6 { + margin-bottom: var(--bui-space-6); +} + +.bui-mb-7 { + margin-bottom: var(--bui-space-7); +} + +.bui-mb-8 { + margin-bottom: var(--bui-space-8); +} + +.bui-mb-9 { + margin-bottom: var(--bui-space-9); +} + +.bui-mb-10 { + margin-bottom: var(--bui-space-10); +} + +.bui-mb-11 { + margin-bottom: var(--bui-space-11); +} + +.bui-mb-12 { + margin-bottom: var(--bui-space-12); +} + +.bui-mb-13 { + margin-bottom: var(--bui-space-13); +} + +.bui-mb-14 { + margin-bottom: var(--bui-space-14); +} + +/* Breakpoint xs */ +@media (min-width: 640px) { + .xs\:bui-mb { + margin-bottom: var(--pb-xs); + } + + .xs\:bui-mb-0\.5 { + margin-bottom: var(--bui-space-0_5); + } + + .xs\:bui-mb-1 { + margin-bottom: var(--bui-space-1); + } + + .xs\:bui-mb-1\.5 { + margin-bottom: var(--bui-space-1_5); + } + + .xs\:bui-mb-2 { + margin-bottom: var(--bui-space-2); + } + + .xs\:bui-mb-3 { + margin-bottom: var(--bui-space-3); + } + + .xs\:bui-mb-4 { + margin-bottom: var(--bui-space-4); + } + + .xs\:bui-mb-5 { + margin-bottom: var(--bui-space-5); + } + + .xs\:bui-mb-6 { + margin-bottom: var(--bui-space-6); + } + + .xs\:bui-mb-7 { + margin-bottom: var(--bui-space-7); + } + + .xs\:bui-mb-8 { + margin-bottom: var(--bui-space-8); + } + + .xs\:bui-mb-9 { + margin-bottom: var(--bui-space-9); + } + + .xs\:bui-mb-10 { + margin-bottom: var(--bui-space-10); + } + + .xs\:bui-mb-11 { + margin-bottom: var(--bui-space-11); + } + + .xs\:bui-mb-12 { + margin-bottom: var(--bui-space-12); + } + + .xs\:bui-mb-13 { + margin-bottom: var(--bui-space-13); + } + + .xs\:bui-mb-14 { + margin-bottom: var(--bui-space-14); + } +} + +/* Breakpoint sm */ +@media (min-width: 768px) { + .sm\:bui-mb { + margin-bottom: var(--pb-sm); + } + + .sm\:bui-mb-0\.5 { + margin-bottom: var(--bui-space-0_5); + } + + .sm\:bui-mb-1 { + margin-bottom: var(--bui-space-1); + } + + .sm\:bui-mb-1\.5 { + margin-bottom: var(--bui-space-1_5); + } + + .sm\:bui-mb-2 { + margin-bottom: var(--bui-space-2); + } + + .sm\:bui-mb-3 { + margin-bottom: var(--bui-space-3); + } + + .sm\:bui-mb-4 { + margin-bottom: var(--bui-space-4); + } + + .sm\:bui-mb-5 { + margin-bottom: var(--bui-space-5); + } + + .sm\:bui-mb-6 { + margin-bottom: var(--bui-space-6); + } + + .sm\:bui-mb-7 { + margin-bottom: var(--bui-space-7); + } + + .sm\:bui-mb-8 { + margin-bottom: var(--bui-space-8); + } + + .sm\:bui-mb-9 { + margin-bottom: var(--bui-space-9); + } + + .sm\:bui-mb-10 { + margin-bottom: var(--bui-space-10); + } + + .sm\:bui-mb-11 { + margin-bottom: var(--bui-space-11); + } + + .sm\:bui-mb-12 { + margin-bottom: var(--bui-space-12); + } + + .sm\:bui-mb-13 { + margin-bottom: var(--bui-space-13); + } + + .sm\:bui-mb-14 { + margin-bottom: var(--bui-space-14); + } +} + +/* Breakpoint md */ +@media (min-width: 1024px) { + .md\:bui-mb { + margin-bottom: var(--pb-md); + } + + .md\:bui-mb-0\.5 { + margin-bottom: var(--bui-space-0_5); + } + + .md\:bui-mb-1 { + margin-bottom: var(--bui-space-1); + } + + .md\:bui-mb-1\.5 { + margin-bottom: var(--bui-space-1_5); + } + + .md\:bui-mb-2 { + margin-bottom: var(--bui-space-2); + } + + .md\:bui-mb-3 { + margin-bottom: var(--bui-space-3); + } + + .md\:bui-mb-4 { + margin-bottom: var(--bui-space-4); + } + + .md\:bui-mb-5 { + margin-bottom: var(--bui-space-5); + } + + .md\:bui-mb-6 { + margin-bottom: var(--bui-space-6); + } + + .md\:bui-mb-7 { + margin-bottom: var(--bui-space-7); + } + + .md\:bui-mb-8 { + margin-bottom: var(--bui-space-8); + } + + .md\:bui-mb-9 { + margin-bottom: var(--bui-space-9); + } + + .md\:bui-mb-10 { + margin-bottom: var(--bui-space-10); + } + + .md\:bui-mb-11 { + margin-bottom: var(--bui-space-11); + } + + .md\:bui-mb-12 { + margin-bottom: var(--bui-space-12); + } + + .md\:bui-mb-13 { + margin-bottom: var(--bui-space-13); + } + + .md\:bui-mb-14 { + margin-bottom: var(--bui-space-14); + } +} + +/* Breakpoint lg */ +@media (min-width: 1280px) { + .lg\:bui-mb { + margin-bottom: var(--pb-lg); + } + + .lg\:bui-mb-0\.5 { + margin-bottom: var(--bui-space-0_5); + } + + .lg\:bui-mb-1 { + margin-bottom: var(--bui-space-1); + } + + .lg\:bui-mb-1\.5 { + margin-bottom: var(--bui-space-1_5); + } + + .lg\:bui-mb-2 { + margin-bottom: var(--bui-space-2); + } + + .lg\:bui-mb-3 { + margin-bottom: var(--bui-space-3); + } + + .lg\:bui-mb-4 { + margin-bottom: var(--bui-space-4); + } + + .lg\:bui-mb-5 { + margin-bottom: var(--bui-space-5); + } + + .lg\:bui-mb-6 { + margin-bottom: var(--bui-space-6); + } + + .lg\:bui-mb-7 { + margin-bottom: var(--bui-space-7); + } + + .lg\:bui-mb-8 { + margin-bottom: var(--bui-space-8); + } + + .lg\:bui-mb-9 { + margin-bottom: var(--bui-space-9); + } + + .lg\:bui-mb-10 { + margin-bottom: var(--bui-space-10); + } + + .lg\:bui-mb-11 { + margin-bottom: var(--bui-space-11); + } + + .lg\:bui-mb-12 { + margin-bottom: var(--bui-space-12); + } + + .lg\:bui-mb-13 { + margin-bottom: var(--bui-space-13); + } + + .lg\:bui-mb-14 { + margin-bottom: var(--bui-space-14); + } +} + +/* Breakpoint xl */ +@media (min-width: 1536px) { + .xl\:bui-mb { + margin-bottom: var(--pb-xl); + } + + .xl\:bui-mb-0\.5 { + margin-bottom: var(--bui-space-0_5); + } + + .xl\:bui-mb-1 { + margin-bottom: var(--bui-space-1); + } + + .xl\:bui-mb-1\.5 { + margin-bottom: var(--bui-space-1_5); + } + + .xl\:bui-mb-2 { + margin-bottom: var(--bui-space-2); + } + + .xl\:bui-mb-3 { + margin-bottom: var(--bui-space-3); + } + + .xl\:bui-mb-4 { + margin-bottom: var(--bui-space-4); + } + + .xl\:bui-mb-5 { + margin-bottom: var(--bui-space-5); + } + + .xl\:bui-mb-6 { + margin-bottom: var(--bui-space-6); + } + + .xl\:bui-mb-7 { + margin-bottom: var(--bui-space-7); + } + + .xl\:bui-mb-8 { + margin-bottom: var(--bui-space-8); + } + + .xl\:bui-mb-9 { + margin-bottom: var(--bui-space-9); + } + + .xl\:bui-mb-10 { + margin-bottom: var(--bui-space-10); + } + + .xl\:bui-mb-11 { + margin-bottom: var(--bui-space-11); + } + + .xl\:bui-mb-12 { + margin-bottom: var(--bui-space-12); + } + + .xl\:bui-mb-13 { + margin-bottom: var(--bui-space-13); + } + + .xl\:bui-mb-14 { + margin-bottom: var(--bui-space-14); + } +} diff --git a/packages/ui/src/css/utilities/ml.css b/packages/ui/src/css/utilities/ml.css new file mode 100644 index 0000000000..b11fe779fb --- /dev/null +++ b/packages/ui/src/css/utilities/ml.css @@ -0,0 +1,422 @@ +.bui-ml { + margin-left: var(--ml); +} + +.bui-ml-0\.5 { + margin-left: var(--bui-space-0_5); +} + +.bui-ml-1 { + margin-left: var(--bui-space-1); +} + +.bui-ml-1\.5 { + margin-left: var(--bui-space-1_5); +} + +.bui-ml-2 { + margin-left: var(--bui-space-2); +} + +.bui-ml-3 { + margin-left: var(--bui-space-3); +} + +.bui-ml-4 { + margin-left: var(--bui-space-4); +} + +.bui-ml-5 { + margin-left: var(--bui-space-5); +} + +.bui-ml-6 { + margin-left: var(--bui-space-6); +} + +.bui-ml-7 { + margin-left: var(--bui-space-7); +} + +.bui-ml-8 { + margin-left: var(--bui-space-8); +} + +.bui-ml-9 { + margin-left: var(--bui-space-9); +} + +.bui-ml-10 { + margin-left: var(--bui-space-10); +} + +.bui-ml-11 { + margin-left: var(--bui-space-11); +} + +.bui-ml-12 { + margin-left: var(--bui-space-12); +} + +.bui-ml-13 { + margin-left: var(--bui-space-13); +} + +.bui-ml-14 { + margin-left: var(--bui-space-14); +} + +/* Breakpoint xs */ +@media (min-width: 640px) { + .xs\:bui-ml { + margin-left: var(--pl-xs); + } + + .xs\:bui-ml-0\.5 { + margin-left: var(--bui-space-0_5); + } + + .xs\:bui-ml-1 { + margin-left: var(--bui-space-1); + } + + .xs\:bui-ml-1\.5 { + margin-left: var(--bui-space-1_5); + } + + .xs\:bui-ml-2 { + margin-left: var(--bui-space-2); + } + + .xs\:bui-ml-3 { + margin-left: var(--bui-space-3); + } + + .xs\:bui-ml-4 { + margin-left: var(--bui-space-4); + } + + .xs\:bui-ml-5 { + margin-left: var(--bui-space-5); + } + + .xs\:bui-ml-6 { + margin-left: var(--bui-space-6); + } + + .xs\:bui-ml-7 { + margin-left: var(--bui-space-7); + } + + .xs\:bui-ml-8 { + margin-left: var(--bui-space-8); + } + + .xs\:bui-ml-9 { + margin-left: var(--bui-space-9); + } + + .xs\:bui-ml-10 { + margin-left: var(--bui-space-10); + } + + .xs\:bui-ml-11 { + margin-left: var(--bui-space-11); + } + + .xs\:bui-ml-12 { + margin-left: var(--bui-space-12); + } + + .xs\:bui-ml-13 { + margin-left: var(--bui-space-13); + } + + .xs\:bui-ml-14 { + margin-left: var(--bui-space-14); + } +} + +/* Breakpoint sm */ +@media (min-width: 768px) { + .sm\:bui-ml { + margin-left: var(--pl-sm); + } + + .sm\:bui-ml-0\.5 { + margin-left: var(--bui-space-0_5); + } + + .sm\:bui-ml-1 { + margin-left: var(--bui-space-1); + } + + .sm\:bui-ml-1\.5 { + margin-left: var(--bui-space-1_5); + } + + .sm\:bui-ml-2 { + margin-left: var(--bui-space-2); + } + + .sm\:bui-ml-3 { + margin-left: var(--bui-space-3); + } + + .sm\:bui-ml-4 { + margin-left: var(--bui-space-4); + } + + .sm\:bui-ml-5 { + margin-left: var(--bui-space-5); + } + + .sm\:bui-ml-6 { + margin-left: var(--bui-space-6); + } + + .sm\:bui-ml-7 { + margin-left: var(--bui-space-7); + } + + .sm\:bui-ml-8 { + margin-left: var(--bui-space-8); + } + + .sm\:bui-ml-9 { + margin-left: var(--bui-space-9); + } + + .sm\:bui-ml-10 { + margin-left: var(--bui-space-10); + } + + .sm\:bui-ml-11 { + margin-left: var(--bui-space-11); + } + + .sm\:bui-ml-12 { + margin-left: var(--bui-space-12); + } + + .sm\:bui-ml-13 { + margin-left: var(--bui-space-13); + } + + .sm\:bui-ml-14 { + margin-left: var(--bui-space-14); + } +} + +/* Breakpoint md */ +@media (min-width: 1024px) { + .md\:bui-ml { + margin-left: var(--pl-md); + } + + .md\:bui-ml-0\.5 { + margin-left: var(--bui-space-0_5); + } + + .md\:bui-ml-1 { + margin-left: var(--bui-space-1); + } + + .md\:bui-ml-1\.5 { + margin-left: var(--bui-space-1_5); + } + + .md\:bui-ml-2 { + margin-left: var(--bui-space-2); + } + + .md\:bui-ml-3 { + margin-left: var(--bui-space-3); + } + + .md\:bui-ml-4 { + margin-left: var(--bui-space-4); + } + + .md\:bui-ml-5 { + margin-left: var(--bui-space-5); + } + + .md\:bui-ml-6 { + margin-left: var(--bui-space-6); + } + + .md\:bui-ml-7 { + margin-left: var(--bui-space-7); + } + + .md\:bui-ml-8 { + margin-left: var(--bui-space-8); + } + + .md\:bui-ml-9 { + margin-left: var(--bui-space-9); + } + + .md\:bui-ml-10 { + margin-left: var(--bui-space-10); + } + + .md\:bui-ml-11 { + margin-left: var(--bui-space-11); + } + + .md\:bui-ml-12 { + margin-left: var(--bui-space-12); + } + + .md\:bui-ml-13 { + margin-left: var(--bui-space-13); + } + + .md\:bui-ml-14 { + margin-left: var(--bui-space-14); + } +} + +/* Breakpoint lg */ +@media (min-width: 1280px) { + .lg\:bui-ml { + margin-left: var(--pl-lg); + } + + .lg\:bui-ml-0\.5 { + margin-left: var(--bui-space-0_5); + } + + .lg\:bui-ml-1 { + margin-left: var(--bui-space-1); + } + + .lg\:bui-ml-1\.5 { + margin-left: var(--bui-space-1_5); + } + + .lg\:bui-ml-2 { + margin-left: var(--bui-space-2); + } + + .lg\:bui-ml-3 { + margin-left: var(--bui-space-3); + } + + .lg\:bui-ml-4 { + margin-left: var(--bui-space-4); + } + + .lg\:bui-ml-5 { + margin-left: var(--bui-space-5); + } + + .lg\:bui-ml-6 { + margin-left: var(--bui-space-6); + } + + .lg\:bui-ml-7 { + margin-left: var(--bui-space-7); + } + + .lg\:bui-ml-8 { + margin-left: var(--bui-space-8); + } + + .lg\:bui-ml-9 { + margin-left: var(--bui-space-9); + } + + .lg\:bui-ml-10 { + margin-left: var(--bui-space-10); + } + + .lg\:bui-ml-11 { + margin-left: var(--bui-space-11); + } + + .lg\:bui-ml-12 { + margin-left: var(--bui-space-12); + } + + .lg\:bui-ml-13 { + margin-left: var(--bui-space-13); + } + + .lg\:bui-ml-14 { + margin-left: var(--bui-space-14); + } +} + +/* Breakpoint xl */ +@media (min-width: 1536px) { + .xl\:bui-ml { + margin-left: var(--pl-xl); + } + + .xl\:bui-ml-0\.5 { + margin-left: var(--bui-space-0_5); + } + + .xl\:bui-ml-1 { + margin-left: var(--bui-space-1); + } + + .xl\:bui-ml-1\.5 { + margin-left: var(--bui-space-1_5); + } + + .xl\:bui-ml-2 { + margin-left: var(--bui-space-2); + } + + .xl\:bui-ml-3 { + margin-left: var(--bui-space-3); + } + + .xl\:bui-ml-4 { + margin-left: var(--bui-space-4); + } + + .xl\:bui-ml-5 { + margin-left: var(--bui-space-5); + } + + .xl\:bui-ml-6 { + margin-left: var(--bui-space-6); + } + + .xl\:bui-ml-7 { + margin-left: var(--bui-space-7); + } + + .xl\:bui-ml-8 { + margin-left: var(--bui-space-8); + } + + .xl\:bui-ml-9 { + margin-left: var(--bui-space-9); + } + + .xl\:bui-ml-10 { + margin-left: var(--bui-space-10); + } + + .xl\:bui-ml-11 { + margin-left: var(--bui-space-11); + } + + .xl\:bui-ml-12 { + margin-left: var(--bui-space-12); + } + + .xl\:bui-ml-13 { + margin-left: var(--bui-space-13); + } + + .xl\:bui-ml-14 { + margin-left: var(--bui-space-14); + } +} diff --git a/packages/ui/src/css/utilities/mr.css b/packages/ui/src/css/utilities/mr.css new file mode 100644 index 0000000000..215a9dc88d --- /dev/null +++ b/packages/ui/src/css/utilities/mr.css @@ -0,0 +1,422 @@ +.bui-mr { + margin-right: var(--mr); +} + +.bui-mr-0\.5 { + margin-right: var(--bui-space-0_5); +} + +.bui-mr-1 { + margin-right: var(--bui-space-1); +} + +.bui-mr-1\.5 { + margin-right: var(--bui-space-1_5); +} + +.bui-mr-2 { + margin-right: var(--bui-space-2); +} + +.bui-mr-3 { + margin-right: var(--bui-space-3); +} + +.bui-mr-4 { + margin-right: var(--bui-space-4); +} + +.bui-mr-5 { + margin-right: var(--bui-space-5); +} + +.bui-mr-6 { + margin-right: var(--bui-space-6); +} + +.bui-mr-7 { + margin-right: var(--bui-space-7); +} + +.bui-mr-8 { + margin-right: var(--bui-space-8); +} + +.bui-mr-9 { + margin-right: var(--bui-space-9); +} + +.bui-mr-10 { + margin-right: var(--bui-space-10); +} + +.bui-mr-11 { + margin-right: var(--bui-space-11); +} + +.bui-mr-12 { + margin-right: var(--bui-space-12); +} + +.bui-mr-13 { + margin-right: var(--bui-space-13); +} + +.bui-mr-14 { + margin-right: var(--bui-space-14); +} + +/* Breakpoint xs */ +@media (min-width: 640px) { + .xs\:bui-mr { + margin-right: var(--pr-xs); + } + + .xs\:bui-mr-0\.5 { + margin-right: var(--bui-space-0_5); + } + + .xs\:bui-mr-1 { + margin-right: var(--bui-space-1); + } + + .xs\:bui-mr-1\.5 { + margin-right: var(--bui-space-1_5); + } + + .xs\:bui-mr-2 { + margin-right: var(--bui-space-2); + } + + .xs\:bui-mr-3 { + margin-right: var(--bui-space-3); + } + + .xs\:bui-mr-4 { + margin-right: var(--bui-space-4); + } + + .xs\:bui-mr-5 { + margin-right: var(--bui-space-5); + } + + .xs\:bui-mr-6 { + margin-right: var(--bui-space-6); + } + + .xs\:bui-mr-7 { + margin-right: var(--bui-space-7); + } + + .xs\:bui-mr-8 { + margin-right: var(--bui-space-8); + } + + .xs\:bui-mr-9 { + margin-right: var(--bui-space-9); + } + + .xs\:bui-mr-10 { + margin-right: var(--bui-space-10); + } + + .xs\:bui-mr-11 { + margin-right: var(--bui-space-11); + } + + .xs\:bui-mr-12 { + margin-right: var(--bui-space-12); + } + + .xs\:bui-mr-13 { + margin-right: var(--bui-space-13); + } + + .xs\:bui-mr-14 { + margin-right: var(--bui-space-14); + } +} + +/* Breakpoint sm */ +@media (min-width: 768px) { + .sm\:bui-mr { + margin-right: var(--pr-sm); + } + + .sm\:bui-mr-0\.5 { + margin-right: var(--bui-space-0_5); + } + + .sm\:bui-mr-1 { + margin-right: var(--bui-space-1); + } + + .sm\:bui-mr-1\.5 { + margin-right: var(--bui-space-1_5); + } + + .sm\:bui-mr-2 { + margin-right: var(--bui-space-2); + } + + .sm\:bui-mr-3 { + margin-right: var(--bui-space-3); + } + + .sm\:bui-mr-4 { + margin-right: var(--bui-space-4); + } + + .sm\:bui-mr-5 { + margin-right: var(--bui-space-5); + } + + .sm\:bui-mr-6 { + margin-right: var(--bui-space-6); + } + + .sm\:bui-mr-7 { + margin-right: var(--bui-space-7); + } + + .sm\:bui-mr-8 { + margin-right: var(--bui-space-8); + } + + .sm\:bui-mr-9 { + margin-right: var(--bui-space-9); + } + + .sm\:bui-mr-10 { + margin-right: var(--bui-space-10); + } + + .sm\:bui-mr-11 { + margin-right: var(--bui-space-11); + } + + .sm\:bui-mr-12 { + margin-right: var(--bui-space-12); + } + + .sm\:bui-mr-13 { + margin-right: var(--bui-space-13); + } + + .sm\:bui-mr-14 { + margin-right: var(--bui-space-14); + } +} + +/* Breakpoint md */ +@media (min-width: 1024px) { + .md\:bui-mr { + margin-right: var(--pr-md); + } + + .md\:bui-mr-0\.5 { + margin-right: var(--bui-space-0_5); + } + + .md\:bui-mr-1 { + margin-right: var(--bui-space-1); + } + + .md\:bui-mr-1\.5 { + margin-right: var(--bui-space-1_5); + } + + .md\:bui-mr-2 { + margin-right: var(--bui-space-2); + } + + .md\:bui-mr-3 { + margin-right: var(--bui-space-3); + } + + .md\:bui-mr-4 { + margin-right: var(--bui-space-4); + } + + .md\:bui-mr-5 { + margin-right: var(--bui-space-5); + } + + .md\:bui-mr-6 { + margin-right: var(--bui-space-6); + } + + .md\:bui-mr-7 { + margin-right: var(--bui-space-7); + } + + .md\:bui-mr-8 { + margin-right: var(--bui-space-8); + } + + .md\:bui-mr-9 { + margin-right: var(--bui-space-9); + } + + .md\:bui-mr-10 { + margin-right: var(--bui-space-10); + } + + .md\:bui-mr-11 { + margin-right: var(--bui-space-11); + } + + .md\:bui-mr-12 { + margin-right: var(--bui-space-12); + } + + .md\:bui-mr-13 { + margin-right: var(--bui-space-13); + } + + .md\:bui-mr-14 { + margin-right: var(--bui-space-14); + } +} + +/* Breakpoint lg */ +@media (min-width: 1280px) { + .lg\:bui-mr { + margin-right: var(--pr-lg); + } + + .lg\:bui-mr-0\.5 { + margin-right: var(--bui-space-0_5); + } + + .lg\:bui-mr-1 { + margin-right: var(--bui-space-1); + } + + .lg\:bui-mr-1\.5 { + margin-right: var(--bui-space-1_5); + } + + .lg\:bui-mr-2 { + margin-right: var(--bui-space-2); + } + + .lg\:bui-mr-3 { + margin-right: var(--bui-space-3); + } + + .lg\:bui-mr-4 { + margin-right: var(--bui-space-4); + } + + .lg\:bui-mr-5 { + margin-right: var(--bui-space-5); + } + + .lg\:bui-mr-6 { + margin-right: var(--bui-space-6); + } + + .lg\:bui-mr-7 { + margin-right: var(--bui-space-7); + } + + .lg\:bui-mr-8 { + margin-right: var(--bui-space-8); + } + + .lg\:bui-mr-9 { + margin-right: var(--bui-space-9); + } + + .lg\:bui-mr-10 { + margin-right: var(--bui-space-10); + } + + .lg\:bui-mr-11 { + margin-right: var(--bui-space-11); + } + + .lg\:bui-mr-12 { + margin-right: var(--bui-space-12); + } + + .lg\:bui-mr-13 { + margin-right: var(--bui-space-13); + } + + .lg\:bui-mr-14 { + margin-right: var(--bui-space-14); + } +} + +/* Breakpoint xl */ +@media (min-width: 1536px) { + .xl\:bui-mr { + margin-right: var(--pr-xl); + } + + .xl\:bui-mr-0\.5 { + margin-right: var(--bui-space-0_5); + } + + .xl\:bui-mr-1 { + margin-right: var(--bui-space-1); + } + + .xl\:bui-mr-1\.5 { + margin-right: var(--bui-space-1_5); + } + + .xl\:bui-mr-2 { + margin-right: var(--bui-space-2); + } + + .xl\:bui-mr-3 { + margin-right: var(--bui-space-3); + } + + .xl\:bui-mr-4 { + margin-right: var(--bui-space-4); + } + + .xl\:bui-mr-5 { + margin-right: var(--bui-space-5); + } + + .xl\:bui-mr-6 { + margin-right: var(--bui-space-6); + } + + .xl\:bui-mr-7 { + margin-right: var(--bui-space-7); + } + + .xl\:bui-mr-8 { + margin-right: var(--bui-space-8); + } + + .xl\:bui-mr-9 { + margin-right: var(--bui-space-9); + } + + .xl\:bui-mr-10 { + margin-right: var(--bui-space-10); + } + + .xl\:bui-mr-11 { + margin-right: var(--bui-space-11); + } + + .xl\:bui-mr-12 { + margin-right: var(--bui-space-12); + } + + .xl\:bui-mr-13 { + margin-right: var(--bui-space-13); + } + + .xl\:bui-mr-14 { + margin-right: var(--bui-space-14); + } +} diff --git a/packages/ui/src/css/utilities/mt.css b/packages/ui/src/css/utilities/mt.css new file mode 100644 index 0000000000..30035bf8bd --- /dev/null +++ b/packages/ui/src/css/utilities/mt.css @@ -0,0 +1,422 @@ +.bui-mt { + margin-top: var(--mt); +} + +.bui-mt-0\.5 { + margin-top: var(--bui-space-0_5); +} + +.bui-mt-1 { + margin-top: var(--bui-space-1); +} + +.bui-mt-1\.5 { + margin-top: var(--bui-space-1_5); +} + +.bui-mt-2 { + margin-top: var(--bui-space-2); +} + +.bui-mt-3 { + margin-top: var(--bui-space-3); +} + +.bui-mt-4 { + margin-top: var(--bui-space-4); +} + +.bui-mt-5 { + margin-top: var(--bui-space-5); +} + +.bui-mt-6 { + margin-top: var(--bui-space-6); +} + +.bui-mt-7 { + margin-top: var(--bui-space-7); +} + +.bui-mt-8 { + margin-top: var(--bui-space-8); +} + +.bui-mt-9 { + margin-top: var(--bui-space-9); +} + +.bui-mt-10 { + margin-top: var(--bui-space-10); +} + +.bui-mt-11 { + margin-top: var(--bui-space-11); +} + +.bui-mt-12 { + margin-top: var(--bui-space-12); +} + +.bui-mt-13 { + margin-top: var(--bui-space-13); +} + +.bui-mt-14 { + margin-top: var(--bui-space-14); +} + +/* Breakpoint xs */ +@media (min-width: 640px) { + .xs\:bui-mt { + margin-top: var(--pt-xs); + } + + .xs\:bui-mt-0\.5 { + margin-top: var(--bui-space-0_5); + } + + .xs\:bui-mt-1 { + margin-top: var(--bui-space-1); + } + + .xs\:bui-mt-1\.5 { + margin-top: var(--bui-space-1_5); + } + + .xs\:bui-mt-2 { + margin-top: var(--bui-space-2); + } + + .xs\:bui-mt-3 { + margin-top: var(--bui-space-3); + } + + .xs\:bui-mt-4 { + margin-top: var(--bui-space-4); + } + + .xs\:bui-mt-5 { + margin-top: var(--bui-space-5); + } + + .xs\:bui-mt-6 { + margin-top: var(--bui-space-6); + } + + .xs\:bui-mt-7 { + margin-top: var(--bui-space-7); + } + + .xs\:bui-mt-8 { + margin-top: var(--bui-space-8); + } + + .xs\:bui-mt-9 { + margin-top: var(--bui-space-9); + } + + .xs\:bui-mt-10 { + margin-top: var(--bui-space-10); + } + + .xs\:bui-mt-11 { + margin-top: var(--bui-space-11); + } + + .xs\:bui-mt-12 { + margin-top: var(--bui-space-12); + } + + .xs\:bui-mt-13 { + margin-top: var(--bui-space-13); + } + + .xs\:bui-mt-14 { + margin-top: var(--bui-space-14); + } +} + +/* Breakpoint sm */ +@media (min-width: 768px) { + .sm\:bui-mt { + margin-top: var(--pt-sm); + } + + .sm\:bui-mt-0\.5 { + margin-top: var(--bui-space-0_5); + } + + .sm\:bui-mt-1 { + margin-top: var(--bui-space-1); + } + + .sm\:bui-mt-1\.5 { + margin-top: var(--bui-space-1_5); + } + + .sm\:bui-mt-2 { + margin-top: var(--bui-space-2); + } + + .sm\:bui-mt-3 { + margin-top: var(--bui-space-3); + } + + .sm\:bui-mt-4 { + margin-top: var(--bui-space-4); + } + + .sm\:bui-mt-5 { + margin-top: var(--bui-space-5); + } + + .sm\:bui-mt-6 { + margin-top: var(--bui-space-6); + } + + .sm\:bui-mt-7 { + margin-top: var(--bui-space-7); + } + + .sm\:bui-mt-8 { + margin-top: var(--bui-space-8); + } + + .sm\:bui-mt-9 { + margin-top: var(--bui-space-9); + } + + .sm\:bui-mt-10 { + margin-top: var(--bui-space-10); + } + + .sm\:bui-mt-11 { + margin-top: var(--bui-space-11); + } + + .sm\:bui-mt-12 { + margin-top: var(--bui-space-12); + } + + .sm\:bui-mt-13 { + margin-top: var(--bui-space-13); + } + + .sm\:bui-mt-14 { + margin-top: var(--bui-space-14); + } +} + +/* Breakpoint md */ +@media (min-width: 1024px) { + .md\:bui-mt { + margin-top: var(--pt-md); + } + + .md\:bui-mt-0\.5 { + margin-top: var(--bui-space-0_5); + } + + .md\:bui-mt-1 { + margin-top: var(--bui-space-1); + } + + .md\:bui-mt-1\.5 { + margin-top: var(--bui-space-1_5); + } + + .md\:bui-mt-2 { + margin-top: var(--bui-space-2); + } + + .md\:bui-mt-3 { + margin-top: var(--bui-space-3); + } + + .md\:bui-mt-4 { + margin-top: var(--bui-space-4); + } + + .md\:bui-mt-5 { + margin-top: var(--bui-space-5); + } + + .md\:bui-mt-6 { + margin-top: var(--bui-space-6); + } + + .md\:bui-mt-7 { + margin-top: var(--bui-space-7); + } + + .md\:bui-mt-8 { + margin-top: var(--bui-space-8); + } + + .md\:bui-mt-9 { + margin-top: var(--bui-space-9); + } + + .md\:bui-mt-10 { + margin-top: var(--bui-space-10); + } + + .md\:bui-mt-11 { + margin-top: var(--bui-space-11); + } + + .md\:bui-mt-12 { + margin-top: var(--bui-space-12); + } + + .md\:bui-mt-13 { + margin-top: var(--bui-space-13); + } + + .md\:bui-mt-14 { + margin-top: var(--bui-space-14); + } +} + +/* Breakpoint lg */ +@media (min-width: 1280px) { + .lg\:bui-mt { + margin-top: var(--pt-lg); + } + + .lg\:bui-mt-0\.5 { + margin-top: var(--bui-space-0_5); + } + + .lg\:bui-mt-1 { + margin-top: var(--bui-space-1); + } + + .lg\:bui-mt-1\.5 { + margin-top: var(--bui-space-1_5); + } + + .lg\:bui-mt-2 { + margin-top: var(--bui-space-2); + } + + .lg\:bui-mt-3 { + margin-top: var(--bui-space-3); + } + + .lg\:bui-mt-4 { + margin-top: var(--bui-space-4); + } + + .lg\:bui-mt-5 { + margin-top: var(--bui-space-5); + } + + .lg\:bui-mt-6 { + margin-top: var(--bui-space-6); + } + + .lg\:bui-mt-7 { + margin-top: var(--bui-space-7); + } + + .lg\:bui-mt-8 { + margin-top: var(--bui-space-8); + } + + .lg\:bui-mt-9 { + margin-top: var(--bui-space-9); + } + + .lg\:bui-mt-10 { + margin-top: var(--bui-space-10); + } + + .lg\:bui-mt-11 { + margin-top: var(--bui-space-11); + } + + .lg\:bui-mt-12 { + margin-top: var(--bui-space-12); + } + + .lg\:bui-mt-13 { + margin-top: var(--bui-space-13); + } + + .lg\:bui-mt-14 { + margin-top: var(--bui-space-14); + } +} + +/* Breakpoint xl */ +@media (min-width: 1536px) { + .xl\:bui-mt { + margin-top: var(--pt-xl); + } + + .xl\:bui-mt-0\.5 { + margin-top: var(--bui-space-0_5); + } + + .xl\:bui-mt-1 { + margin-top: var(--bui-space-1); + } + + .xl\:bui-mt-1\.5 { + margin-top: var(--bui-space-1_5); + } + + .xl\:bui-mt-2 { + margin-top: var(--bui-space-2); + } + + .xl\:bui-mt-3 { + margin-top: var(--bui-space-3); + } + + .xl\:bui-mt-4 { + margin-top: var(--bui-space-4); + } + + .xl\:bui-mt-5 { + margin-top: var(--bui-space-5); + } + + .xl\:bui-mt-6 { + margin-top: var(--bui-space-6); + } + + .xl\:bui-mt-7 { + margin-top: var(--bui-space-7); + } + + .xl\:bui-mt-8 { + margin-top: var(--bui-space-8); + } + + .xl\:bui-mt-9 { + margin-top: var(--bui-space-9); + } + + .xl\:bui-mt-10 { + margin-top: var(--bui-space-10); + } + + .xl\:bui-mt-11 { + margin-top: var(--bui-space-11); + } + + .xl\:bui-mt-12 { + margin-top: var(--bui-space-12); + } + + .xl\:bui-mt-13 { + margin-top: var(--bui-space-13); + } + + .xl\:bui-mt-14 { + margin-top: var(--bui-space-14); + } +} diff --git a/packages/ui/src/css/utilities/mx.css b/packages/ui/src/css/utilities/mx.css new file mode 100644 index 0000000000..36bdf32796 --- /dev/null +++ b/packages/ui/src/css/utilities/mx.css @@ -0,0 +1,524 @@ +.bui-mx { + margin-left: var(--mx); + margin-right: var(--mx); +} + +.bui-mx-0\.5 { + margin-left: var(--bui-space-0_5); + margin-right: var(--bui-space-0_5); +} + +.bui-mx-1 { + margin-left: var(--bui-space-1); + margin-right: var(--bui-space-1); +} + +.bui-mx-1\.5 { + margin-left: var(--bui-space-1_5); + margin-right: var(--bui-space-1_5); +} + +.bui-mx-2 { + margin-left: var(--bui-space-2); + margin-right: var(--bui-space-2); +} + +.bui-mx-3 { + margin-left: var(--bui-space-3); + margin-right: var(--bui-space-3); +} + +.bui-mx-4 { + margin-left: var(--bui-space-4); + margin-right: var(--bui-space-4); +} + +.bui-mx-5 { + margin-left: var(--bui-space-5); + margin-right: var(--bui-space-5); +} + +.bui-mx-6 { + margin-left: var(--bui-space-6); + margin-right: var(--bui-space-6); +} + +.bui-mx-7 { + margin-left: var(--bui-space-7); + margin-right: var(--bui-space-7); +} + +.bui-mx-8 { + margin-left: var(--bui-space-8); + margin-right: var(--bui-space-8); +} + +.bui-mx-9 { + margin-left: var(--bui-space-9); + margin-right: var(--bui-space-9); +} + +.bui-mx-10 { + margin-left: var(--bui-space-10); + margin-right: var(--bui-space-10); +} + +.bui-mx-11 { + margin-left: var(--bui-space-11); + margin-right: var(--bui-space-11); +} + +.bui-mx-12 { + margin-left: var(--bui-space-12); + margin-right: var(--bui-space-12); +} + +.bui-mx-13 { + margin-left: var(--bui-space-13); + margin-right: var(--bui-space-13); +} + +.bui-mx-14 { + margin-left: var(--bui-space-14); + margin-right: var(--bui-space-14); +} + +/* Breakpoint xs */ +@media (min-width: 640px) { + .xs\:bui-mx { + margin-left: var(--px-xs); + margin-right: var(--px-xs); + } + + .xs\:bui-mx-0\.5 { + margin-left: var(--bui-space-0_5); + margin-right: var(--bui-space-0_5); + } + + .xs\:bui-mx-1 { + margin-left: var(--bui-space-1); + margin-right: var(--bui-space-1); + } + + .xs\:bui-mx-1\.5 { + margin-left: var(--bui-space-1_5); + margin-right: var(--bui-space-1_5); + } + + .xs\:bui-mx-2 { + margin-left: var(--bui-space-2); + margin-right: var(--bui-space-2); + } + + .xs\:bui-mx-3 { + margin-left: var(--bui-space-3); + margin-right: var(--bui-space-3); + } + + .xs\:bui-mx-4 { + margin-left: var(--bui-space-4); + margin-right: var(--bui-space-4); + } + + .xs\:bui-mx-5 { + margin-left: var(--bui-space-5); + margin-right: var(--bui-space-5); + } + + .xs\:bui-mx-6 { + margin-left: var(--bui-space-6); + margin-right: var(--bui-space-6); + } + + .xs\:bui-mx-7 { + margin-left: var(--bui-space-7); + margin-right: var(--bui-space-7); + } + + .xs\:bui-mx-8 { + margin-left: var(--bui-space-8); + margin-right: var(--bui-space-8); + } + + .xs\:bui-mx-9 { + margin-left: var(--bui-space-9); + margin-right: var(--bui-space-9); + } + + .xs\:bui-mx-10 { + margin-left: var(--bui-space-10); + margin-right: var(--bui-space-10); + } + + .xs\:bui-mx-11 { + margin-left: var(--bui-space-11); + margin-right: var(--bui-space-11); + } + + .xs\:bui-mx-12 { + margin-left: var(--bui-space-12); + margin-right: var(--bui-space-12); + } + + .xs\:bui-mx-13 { + margin-left: var(--bui-space-13); + margin-right: var(--bui-space-13); + } + + .xs\:bui-mx-14 { + margin-left: var(--bui-space-14); + margin-right: var(--bui-space-14); + } +} + +/* Breakpoint sm */ +@media (min-width: 768px) { + .sm\:bui-mx { + margin-left: var(--px-sm); + margin-right: var(--px-sm); + } + + .sm\:bui-mx-0\.5 { + margin-left: var(--bui-space-0_5); + margin-right: var(--bui-space-0_5); + } + + .sm\:bui-mx-1 { + margin-left: var(--bui-space-1); + margin-right: var(--bui-space-1); + } + + .sm\:bui-mx-1\.5 { + margin-left: var(--bui-space-1_5); + margin-right: var(--bui-space-1_5); + } + + .sm\:bui-mx-2 { + margin-left: var(--bui-space-2); + margin-right: var(--bui-space-2); + } + + .sm\:bui-mx-3 { + margin-left: var(--bui-space-3); + margin-right: var(--bui-space-3); + } + + .sm\:bui-mx-4 { + margin-left: var(--bui-space-4); + margin-right: var(--bui-space-4); + } + + .sm\:bui-mx-5 { + margin-left: var(--bui-space-5); + margin-right: var(--bui-space-5); + } + + .sm\:bui-mx-6 { + margin-left: var(--bui-space-6); + margin-right: var(--bui-space-6); + } + + .sm\:bui-mx-7 { + margin-left: var(--bui-space-7); + margin-right: var(--bui-space-7); + } + + .sm\:bui-mx-8 { + margin-left: var(--bui-space-8); + margin-right: var(--bui-space-8); + } + + .sm\:bui-mx-9 { + margin-left: var(--bui-space-9); + margin-right: var(--bui-space-9); + } + + .sm\:bui-mx-10 { + margin-left: var(--bui-space-10); + margin-right: var(--bui-space-10); + } + + .sm\:bui-mx-11 { + margin-left: var(--bui-space-11); + margin-right: var(--bui-space-11); + } + + .sm\:bui-mx-12 { + margin-left: var(--bui-space-12); + margin-right: var(--bui-space-12); + } + + .sm\:bui-mx-13 { + margin-left: var(--bui-space-13); + margin-right: var(--bui-space-13); + } + + .sm\:bui-mx-14 { + margin-left: var(--bui-space-14); + margin-right: var(--bui-space-14); + } +} + +/* Breakpoint md */ +@media (min-width: 1024px) { + .md\:bui-mx { + margin-left: var(--px-md); + margin-right: var(--px-md); + } + + .md\:bui-mx-0\.5 { + margin-left: var(--bui-space-0_5); + margin-right: var(--bui-space-0_5); + } + + .md\:bui-mx-1 { + margin-left: var(--bui-space-1); + margin-right: var(--bui-space-1); + } + + .md\:bui-mx-1\.5 { + margin-left: var(--bui-space-1_5); + margin-right: var(--bui-space-1_5); + } + + .md\:bui-mx-2 { + margin-left: var(--bui-space-2); + margin-right: var(--bui-space-2); + } + + .md\:bui-mx-3 { + margin-left: var(--bui-space-3); + margin-right: var(--bui-space-3); + } + + .md\:bui-mx-4 { + margin-left: var(--bui-space-4); + margin-right: var(--bui-space-4); + } + + .md\:bui-mx-5 { + margin-left: var(--bui-space-5); + margin-right: var(--bui-space-5); + } + + .md\:bui-mx-6 { + margin-left: var(--bui-space-6); + margin-right: var(--bui-space-6); + } + + .md\:bui-mx-7 { + margin-left: var(--bui-space-7); + margin-right: var(--bui-space-7); + } + + .md\:bui-mx-8 { + margin-left: var(--bui-space-8); + margin-right: var(--bui-space-8); + } + + .md\:bui-mx-9 { + margin-left: var(--bui-space-9); + margin-right: var(--bui-space-9); + } + + .md\:bui-mx-10 { + margin-left: var(--bui-space-10); + margin-right: var(--bui-space-10); + } + + .md\:bui-mx-11 { + margin-left: var(--bui-space-11); + margin-right: var(--bui-space-11); + } + + .md\:bui-mx-12 { + margin-left: var(--bui-space-12); + margin-right: var(--bui-space-12); + } + + .md\:bui-mx-13 { + margin-left: var(--bui-space-13); + margin-right: var(--bui-space-13); + } + + .md\:bui-mx-14 { + margin-left: var(--bui-space-14); + margin-right: var(--bui-space-14); + } +} + +/* Breakpoint lg */ +@media (min-width: 1280px) { + .lg\:bui-mx { + margin-left: var(--px-lg); + margin-right: var(--px-lg); + } + + .lg\:bui-mx-0\.5 { + margin-left: var(--bui-space-0_5); + margin-right: var(--bui-space-0_5); + } + + .lg\:bui-mx-1 { + margin-left: var(--bui-space-1); + margin-right: var(--bui-space-1); + } + + .lg\:bui-mx-1\.5 { + margin-left: var(--bui-space-1_5); + margin-right: var(--bui-space-1_5); + } + + .lg\:bui-mx-2 { + margin-left: var(--bui-space-2); + margin-right: var(--bui-space-2); + } + + .lg\:bui-mx-3 { + margin-left: var(--bui-space-3); + margin-right: var(--bui-space-3); + } + + .lg\:bui-mx-4 { + margin-left: var(--bui-space-4); + margin-right: var(--bui-space-4); + } + + .lg\:bui-mx-5 { + margin-left: var(--bui-space-5); + margin-right: var(--bui-space-5); + } + + .lg\:bui-mx-6 { + margin-left: var(--bui-space-6); + margin-right: var(--bui-space-6); + } + + .lg\:bui-mx-7 { + margin-left: var(--bui-space-7); + margin-right: var(--bui-space-7); + } + + .lg\:bui-mx-8 { + margin-left: var(--bui-space-8); + margin-right: var(--bui-space-8); + } + + .lg\:bui-mx-9 { + margin-left: var(--bui-space-9); + margin-right: var(--bui-space-9); + } + + .lg\:bui-mx-10 { + margin-left: var(--bui-space-10); + margin-right: var(--bui-space-10); + } + + .lg\:bui-mx-11 { + margin-left: var(--bui-space-11); + margin-right: var(--bui-space-11); + } + + .lg\:bui-mx-12 { + margin-left: var(--bui-space-12); + margin-right: var(--bui-space-12); + } + + .lg\:bui-mx-13 { + margin-left: var(--bui-space-13); + margin-right: var(--bui-space-13); + } + + .lg\:bui-mx-14 { + margin-left: var(--bui-space-14); + margin-right: var(--bui-space-14); + } +} + +/* Breakpoint xl */ +@media (min-width: 1536px) { + .xl\:bui-mx { + margin-left: var(--px-xl); + margin-right: var(--px-xl); + } + + .xl\:bui-mx-0\.5 { + margin-left: var(--bui-space-0_5); + margin-right: var(--bui-space-0_5); + } + + .xl\:bui-mx-1 { + margin-left: var(--bui-space-1); + margin-right: var(--bui-space-1); + } + + .xl\:bui-mx-1\.5 { + margin-left: var(--bui-space-1_5); + margin-right: var(--bui-space-1_5); + } + + .xl\:bui-mx-2 { + margin-left: var(--bui-space-2); + margin-right: var(--bui-space-2); + } + + .xl\:bui-mx-3 { + margin-left: var(--bui-space-3); + margin-right: var(--bui-space-3); + } + + .xl\:bui-mx-4 { + margin-left: var(--bui-space-4); + margin-right: var(--bui-space-4); + } + + .xl\:bui-mx-5 { + margin-left: var(--bui-space-5); + margin-right: var(--bui-space-5); + } + + .xl\:bui-mx-6 { + margin-left: var(--bui-space-6); + margin-right: var(--bui-space-6); + } + + .xl\:bui-mx-7 { + margin-left: var(--bui-space-7); + margin-right: var(--bui-space-7); + } + + .xl\:bui-mx-8 { + margin-left: var(--bui-space-8); + margin-right: var(--bui-space-8); + } + + .xl\:bui-mx-9 { + margin-left: var(--bui-space-9); + margin-right: var(--bui-space-9); + } + + .xl\:bui-mx-10 { + margin-left: var(--bui-space-10); + margin-right: var(--bui-space-10); + } + + .xl\:bui-mx-11 { + margin-left: var(--bui-space-11); + margin-right: var(--bui-space-11); + } + + .xl\:bui-mx-12 { + margin-left: var(--bui-space-12); + margin-right: var(--bui-space-12); + } + + .xl\:bui-mx-13 { + margin-left: var(--bui-space-13); + margin-right: var(--bui-space-13); + } + + .xl\:bui-mx-14 { + margin-left: var(--bui-space-14); + margin-right: var(--bui-space-14); + } +} diff --git a/packages/ui/src/css/utilities/my.css b/packages/ui/src/css/utilities/my.css new file mode 100644 index 0000000000..ab3aadf77e --- /dev/null +++ b/packages/ui/src/css/utilities/my.css @@ -0,0 +1,524 @@ +.bui-my { + margin-top: var(--my); + margin-bottom: var(--my); +} + +.bui-my-0\.5 { + margin-top: var(--bui-space-0_5); + margin-bottom: var(--bui-space-0_5); +} + +.bui-my-1 { + margin-top: var(--bui-space-1); + margin-bottom: var(--bui-space-1); +} + +.bui-my-1\.5 { + margin-top: var(--bui-space-1_5); + margin-bottom: var(--bui-space-1_5); +} + +.bui-my-2 { + margin-top: var(--bui-space-2); + margin-bottom: var(--bui-space-2); +} + +.bui-my-3 { + margin-top: var(--bui-space-3); + margin-bottom: var(--bui-space-3); +} + +.bui-my-4 { + margin-top: var(--bui-space-4); + margin-bottom: var(--bui-space-4); +} + +.bui-my-5 { + margin-top: var(--bui-space-5); + margin-bottom: var(--bui-space-5); +} + +.bui-my-6 { + margin-top: var(--bui-space-6); + margin-bottom: var(--bui-space-6); +} + +.bui-my-7 { + margin-top: var(--bui-space-7); + margin-bottom: var(--bui-space-7); +} + +.bui-my-8 { + margin-top: var(--bui-space-8); + margin-bottom: var(--bui-space-8); +} + +.bui-my-9 { + margin-top: var(--bui-space-9); + margin-bottom: var(--bui-space-9); +} + +.bui-my-10 { + margin-top: var(--bui-space-10); + margin-bottom: var(--bui-space-10); +} + +.bui-my-11 { + margin-top: var(--bui-space-11); + margin-bottom: var(--bui-space-11); +} + +.bui-my-12 { + margin-top: var(--bui-space-12); + margin-bottom: var(--bui-space-12); +} + +.bui-my-13 { + margin-top: var(--bui-space-13); + margin-bottom: var(--bui-space-13); +} + +.bui-my-14 { + margin-top: var(--bui-space-14); + margin-bottom: var(--bui-space-14); +} + +/* Breakpoint xs */ +@media (min-width: 640px) { + .xs\:bui-my { + margin-top: var(--py-xs); + margin-bottom: var(--py-xs); + } + + .xs\:bui-my-0\.5 { + margin-top: var(--bui-space-0_5); + margin-bottom: var(--bui-space-0_5); + } + + .xs\:bui-my-1 { + margin-top: var(--bui-space-1); + margin-bottom: var(--bui-space-1); + } + + .xs\:bui-my-1\.5 { + margin-top: var(--bui-space-1_5); + margin-bottom: var(--bui-space-1_5); + } + + .xs\:bui-my-2 { + margin-top: var(--bui-space-2); + margin-bottom: var(--bui-space-2); + } + + .xs\:bui-my-3 { + margin-top: var(--bui-space-3); + margin-bottom: var(--bui-space-3); + } + + .xs\:bui-my-4 { + margin-top: var(--bui-space-4); + margin-bottom: var(--bui-space-4); + } + + .xs\:bui-my-5 { + margin-top: var(--bui-space-5); + margin-bottom: var(--bui-space-5); + } + + .xs\:bui-my-6 { + margin-top: var(--bui-space-6); + margin-bottom: var(--bui-space-6); + } + + .xs\:bui-my-7 { + margin-top: var(--bui-space-7); + margin-bottom: var(--bui-space-7); + } + + .xs\:bui-my-8 { + margin-top: var(--bui-space-8); + margin-bottom: var(--bui-space-8); + } + + .xs\:bui-my-9 { + margin-top: var(--bui-space-9); + margin-bottom: var(--bui-space-9); + } + + .xs\:bui-my-10 { + margin-top: var(--bui-space-10); + margin-bottom: var(--bui-space-10); + } + + .xs\:bui-my-11 { + margin-top: var(--bui-space-11); + margin-bottom: var(--bui-space-11); + } + + .xs\:bui-my-12 { + margin-top: var(--bui-space-12); + margin-bottom: var(--bui-space-12); + } + + .xs\:bui-my-13 { + margin-top: var(--bui-space-13); + margin-bottom: var(--bui-space-13); + } + + .xs\:bui-my-14 { + margin-top: var(--bui-space-14); + margin-bottom: var(--bui-space-14); + } +} + +/* Breakpoint sm */ +@media (min-width: 768px) { + .sm\:bui-my { + margin-top: var(--py-sm); + margin-bottom: var(--py-sm); + } + + .sm\:bui-my-0\.5 { + margin-top: var(--bui-space-0_5); + margin-bottom: var(--bui-space-0_5); + } + + .sm\:bui-my-1 { + margin-top: var(--bui-space-1); + margin-bottom: var(--bui-space-1); + } + + .sm\:bui-my-1\.5 { + margin-top: var(--bui-space-1_5); + margin-bottom: var(--bui-space-1_5); + } + + .sm\:bui-my-2 { + margin-top: var(--bui-space-2); + margin-bottom: var(--bui-space-2); + } + + .sm\:bui-my-3 { + margin-top: var(--bui-space-3); + margin-bottom: var(--bui-space-3); + } + + .sm\:bui-my-4 { + margin-top: var(--bui-space-4); + margin-bottom: var(--bui-space-4); + } + + .sm\:bui-my-5 { + margin-top: var(--bui-space-5); + margin-bottom: var(--bui-space-5); + } + + .sm\:bui-my-6 { + margin-top: var(--bui-space-6); + margin-bottom: var(--bui-space-6); + } + + .sm\:bui-my-7 { + margin-top: var(--bui-space-7); + margin-bottom: var(--bui-space-7); + } + + .sm\:bui-my-8 { + margin-top: var(--bui-space-8); + margin-bottom: var(--bui-space-8); + } + + .sm\:bui-my-9 { + margin-top: var(--bui-space-9); + margin-bottom: var(--bui-space-9); + } + + .sm\:bui-my-10 { + margin-top: var(--bui-space-10); + margin-bottom: var(--bui-space-10); + } + + .sm\:bui-my-11 { + margin-top: var(--bui-space-11); + margin-bottom: var(--bui-space-11); + } + + .sm\:bui-my-12 { + margin-top: var(--bui-space-12); + margin-bottom: var(--bui-space-12); + } + + .sm\:bui-my-13 { + margin-top: var(--bui-space-13); + margin-bottom: var(--bui-space-13); + } + + .sm\:bui-my-14 { + margin-top: var(--bui-space-14); + margin-bottom: var(--bui-space-14); + } +} + +/* Breakpoint md */ +@media (min-width: 1024px) { + .md\:bui-my { + margin-top: var(--py-md); + margin-bottom: var(--py-md); + } + + .md\:bui-my-0\.5 { + margin-top: var(--bui-space-0_5); + margin-bottom: var(--bui-space-0_5); + } + + .md\:bui-my-1 { + margin-top: var(--bui-space-1); + margin-bottom: var(--bui-space-1); + } + + .md\:bui-my-1\.5 { + margin-top: var(--bui-space-1_5); + margin-bottom: var(--bui-space-1_5); + } + + .md\:bui-my-2 { + margin-top: var(--bui-space-2); + margin-bottom: var(--bui-space-2); + } + + .md\:bui-my-3 { + margin-top: var(--bui-space-3); + margin-bottom: var(--bui-space-3); + } + + .md\:bui-my-4 { + margin-top: var(--bui-space-4); + margin-bottom: var(--bui-space-4); + } + + .md\:bui-my-5 { + margin-top: var(--bui-space-5); + margin-bottom: var(--bui-space-5); + } + + .md\:bui-my-6 { + margin-top: var(--bui-space-6); + margin-bottom: var(--bui-space-6); + } + + .md\:bui-my-7 { + margin-top: var(--bui-space-7); + margin-bottom: var(--bui-space-7); + } + + .md\:bui-my-8 { + margin-top: var(--bui-space-8); + margin-bottom: var(--bui-space-8); + } + + .md\:bui-my-9 { + margin-top: var(--bui-space-9); + margin-bottom: var(--bui-space-9); + } + + .md\:bui-my-10 { + margin-top: var(--bui-space-10); + margin-bottom: var(--bui-space-10); + } + + .md\:bui-my-11 { + margin-top: var(--bui-space-11); + margin-bottom: var(--bui-space-11); + } + + .md\:bui-my-12 { + margin-top: var(--bui-space-12); + margin-bottom: var(--bui-space-12); + } + + .md\:bui-my-13 { + margin-top: var(--bui-space-13); + margin-bottom: var(--bui-space-13); + } + + .md\:bui-my-14 { + margin-top: var(--bui-space-14); + margin-bottom: var(--bui-space-14); + } +} + +/* Breakpoint lg */ +@media (min-width: 1280px) { + .lg\:bui-my { + margin-top: var(--py-lg); + margin-bottom: var(--py-lg); + } + + .lg\:bui-my-0\.5 { + margin-top: var(--bui-space-0_5); + margin-bottom: var(--bui-space-0_5); + } + + .lg\:bui-my-1 { + margin-top: var(--bui-space-1); + margin-bottom: var(--bui-space-1); + } + + .lg\:bui-my-1\.5 { + margin-top: var(--bui-space-1_5); + margin-bottom: var(--bui-space-1_5); + } + + .lg\:bui-my-2 { + margin-top: var(--bui-space-2); + margin-bottom: var(--bui-space-2); + } + + .lg\:bui-my-3 { + margin-top: var(--bui-space-3); + margin-bottom: var(--bui-space-3); + } + + .lg\:bui-my-4 { + margin-top: var(--bui-space-4); + margin-bottom: var(--bui-space-4); + } + + .lg\:bui-my-5 { + margin-top: var(--bui-space-5); + margin-bottom: var(--bui-space-5); + } + + .lg\:bui-my-6 { + margin-top: var(--bui-space-6); + margin-bottom: var(--bui-space-6); + } + + .lg\:bui-my-7 { + margin-top: var(--bui-space-7); + margin-bottom: var(--bui-space-7); + } + + .lg\:bui-my-8 { + margin-top: var(--bui-space-8); + margin-bottom: var(--bui-space-8); + } + + .lg\:bui-my-9 { + margin-top: var(--bui-space-9); + margin-bottom: var(--bui-space-9); + } + + .lg\:bui-my-10 { + margin-top: var(--bui-space-10); + margin-bottom: var(--bui-space-10); + } + + .lg\:bui-my-11 { + margin-top: var(--bui-space-11); + margin-bottom: var(--bui-space-11); + } + + .lg\:bui-my-12 { + margin-top: var(--bui-space-12); + margin-bottom: var(--bui-space-12); + } + + .lg\:bui-my-13 { + margin-top: var(--bui-space-13); + margin-bottom: var(--bui-space-13); + } + + .lg\:bui-my-14 { + margin-top: var(--bui-space-14); + margin-bottom: var(--bui-space-14); + } +} + +/* Breakpoint xl */ +@media (min-width: 1536px) { + .xl\:bui-my { + margin-top: var(--py-xl); + margin-bottom: var(--py-xl); + } + + .xl\:bui-my-0\.5 { + margin-top: var(--bui-space-0_5); + margin-bottom: var(--bui-space-0_5); + } + + .xl\:bui-my-1 { + margin-top: var(--bui-space-1); + margin-bottom: var(--bui-space-1); + } + + .xl\:bui-my-1\.5 { + margin-top: var(--bui-space-1_5); + margin-bottom: var(--bui-space-1_5); + } + + .xl\:bui-my-2 { + margin-top: var(--bui-space-2); + margin-bottom: var(--bui-space-2); + } + + .xl\:bui-my-3 { + margin-top: var(--bui-space-3); + margin-bottom: var(--bui-space-3); + } + + .xl\:bui-my-4 { + margin-top: var(--bui-space-4); + margin-bottom: var(--bui-space-4); + } + + .xl\:bui-my-5 { + margin-top: var(--bui-space-5); + margin-bottom: var(--bui-space-5); + } + + .xl\:bui-my-6 { + margin-top: var(--bui-space-6); + margin-bottom: var(--bui-space-6); + } + + .xl\:bui-my-7 { + margin-top: var(--bui-space-7); + margin-bottom: var(--bui-space-7); + } + + .xl\:bui-my-8 { + margin-top: var(--bui-space-8); + margin-bottom: var(--bui-space-8); + } + + .xl\:bui-my-9 { + margin-top: var(--bui-space-9); + margin-bottom: var(--bui-space-9); + } + + .xl\:bui-my-10 { + margin-top: var(--bui-space-10); + margin-bottom: var(--bui-space-10); + } + + .xl\:bui-my-11 { + margin-top: var(--bui-space-11); + margin-bottom: var(--bui-space-11); + } + + .xl\:bui-my-12 { + margin-top: var(--bui-space-12); + margin-bottom: var(--bui-space-12); + } + + .xl\:bui-my-13 { + margin-top: var(--bui-space-13); + margin-bottom: var(--bui-space-13); + } + + .xl\:bui-my-14 { + margin-top: var(--bui-space-14); + margin-bottom: var(--bui-space-14); + } +} diff --git a/packages/ui/src/css/utilities/p.css b/packages/ui/src/css/utilities/p.css new file mode 100644 index 0000000000..113f10777e --- /dev/null +++ b/packages/ui/src/css/utilities/p.css @@ -0,0 +1,422 @@ +.bui-p { + padding: var(--p); +} + +.bui-p-0\.5 { + padding: var(--bui-space-0_5); +} + +.bui-p-1 { + padding: var(--bui-space-1); +} + +.bui-p-1\.5 { + padding: var(--bui-space-1_5); +} + +.bui-p-2 { + padding: var(--bui-space-2); +} + +.bui-p-3 { + padding: var(--bui-space-3); +} + +.bui-p-4 { + padding: var(--bui-space-4); +} + +.bui-p-5 { + padding: var(--bui-space-5); +} + +.bui-p-6 { + padding: var(--bui-space-6); +} + +.bui-p-7 { + padding: var(--bui-space-7); +} + +.bui-p-8 { + padding: var(--bui-space-8); +} + +.bui-p-9 { + padding: var(--bui-space-9); +} + +.bui-p-10 { + padding: var(--bui-space-10); +} + +.bui-p-11 { + padding: var(--bui-space-11); +} + +.bui-p-12 { + padding: var(--bui-space-12); +} + +.bui-p-13 { + padding: var(--bui-space-13); +} + +.bui-p-14 { + padding: var(--bui-space-14); +} + +/* Breakpoint xs */ +@media (min-width: 640px) { + .xs\:bui-p { + padding: var(--p-xs); + } + + .xs\:bui-p-0\.5 { + padding: var(--bui-space-0_5); + } + + .xs\:bui-p-1 { + padding: var(--bui-space-1); + } + + .xs\:bui-p-1\.5 { + padding: var(--bui-space-1_5); + } + + .xs\:bui-p-2 { + padding: var(--bui-space-2); + } + + .xs\:bui-p-3 { + padding: var(--bui-space-3); + } + + .xs\:bui-p-4 { + padding: var(--bui-space-4); + } + + .xs\:bui-p-5 { + padding: var(--bui-space-5); + } + + .xs\:bui-p-6 { + padding: var(--bui-space-6); + } + + .xs\:bui-p-7 { + padding: var(--bui-space-7); + } + + .xs\:bui-p-8 { + padding: var(--bui-space-8); + } + + .xs\:bui-p-9 { + padding: var(--bui-space-9); + } + + .xs\:bui-p-10 { + padding: var(--bui-space-10); + } + + .xs\:bui-p-11 { + padding: var(--bui-space-11); + } + + .xs\:bui-p-12 { + padding: var(--bui-space-12); + } + + .xs\:bui-p-13 { + padding: var(--bui-space-13); + } + + .xs\:bui-p-14 { + padding: var(--bui-space-14); + } +} + +/* Breakpoint sm */ +@media (min-width: 768px) { + .sm\:bui-p { + padding: var(--p-sm); + } + + .sm\:bui-p-0\.5 { + padding: var(--bui-space-0_5); + } + + .sm\:bui-p-1 { + padding: var(--bui-space-1); + } + + .sm\:bui-p-1\.5 { + padding: var(--bui-space-1_5); + } + + .sm\:bui-p-2 { + padding: var(--bui-space-2); + } + + .sm\:bui-p-3 { + padding: var(--bui-space-3); + } + + .sm\:bui-p-4 { + padding: var(--bui-space-4); + } + + .sm\:bui-p-5 { + padding: var(--bui-space-5); + } + + .sm\:bui-p-6 { + padding: var(--bui-space-6); + } + + .sm\:bui-p-7 { + padding: var(--bui-space-7); + } + + .sm\:bui-p-8 { + padding: var(--bui-space-8); + } + + .sm\:bui-p-9 { + padding: var(--bui-space-9); + } + + .sm\:bui-p-10 { + padding: var(--bui-space-10); + } + + .sm\:bui-p-11 { + padding: var(--bui-space-11); + } + + .sm\:bui-p-12 { + padding: var(--bui-space-12); + } + + .sm\:bui-p-13 { + padding: var(--bui-space-13); + } + + .sm\:bui-p-14 { + padding: var(--bui-space-14); + } +} + +/* Breakpoint md */ +@media (min-width: 1024px) { + .md\:bui-p { + padding: var(--p-md); + } + + .md\:bui-p-0\.5 { + padding: var(--bui-space-0_5); + } + + .md\:bui-p-1 { + padding: var(--bui-space-1); + } + + .md\:bui-p-1\.5 { + padding: var(--bui-space-1_5); + } + + .md\:bui-p-2 { + padding: var(--bui-space-2); + } + + .md\:bui-p-3 { + padding: var(--bui-space-3); + } + + .md\:bui-p-4 { + padding: var(--bui-space-4); + } + + .md\:bui-p-5 { + padding: var(--bui-space-5); + } + + .md\:bui-p-6 { + padding: var(--bui-space-6); + } + + .md\:bui-p-7 { + padding: var(--bui-space-7); + } + + .md\:bui-p-8 { + padding: var(--bui-space-8); + } + + .md\:bui-p-9 { + padding: var(--bui-space-9); + } + + .md\:bui-p-10 { + padding: var(--bui-space-10); + } + + .md\:bui-p-11 { + padding: var(--bui-space-11); + } + + .md\:bui-p-12 { + padding: var(--bui-space-12); + } + + .md\:bui-p-13 { + padding: var(--bui-space-13); + } + + .md\:bui-p-14 { + padding: var(--bui-space-14); + } +} + +/* Breakpoint lg */ +@media (min-width: 1280px) { + .lg\:bui-p { + padding: var(--p-lg); + } + + .lg\:bui-p-0\.5 { + padding: var(--bui-space-0_5); + } + + .lg\:bui-p-1 { + padding: var(--bui-space-1); + } + + .lg\:bui-p-1\.5 { + padding: var(--bui-space-1_5); + } + + .lg\:bui-p-2 { + padding: var(--bui-space-2); + } + + .lg\:bui-p-3 { + padding: var(--bui-space-3); + } + + .lg\:bui-p-4 { + padding: var(--bui-space-4); + } + + .lg\:bui-p-5 { + padding: var(--bui-space-5); + } + + .lg\:bui-p-6 { + padding: var(--bui-space-6); + } + + .lg\:bui-p-7 { + padding: var(--bui-space-7); + } + + .lg\:bui-p-8 { + padding: var(--bui-space-8); + } + + .lg\:bui-p-9 { + padding: var(--bui-space-9); + } + + .lg\:bui-p-10 { + padding: var(--bui-space-10); + } + + .lg\:bui-p-11 { + padding: var(--bui-space-11); + } + + .lg\:bui-p-12 { + padding: var(--bui-space-12); + } + + .lg\:bui-p-13 { + padding: var(--bui-space-13); + } + + .lg\:bui-p-14 { + padding: var(--bui-space-14); + } +} + +/* Breakpoint xl */ +@media (min-width: 1536px) { + .xl\:bui-p { + padding: var(--p-xl); + } + + .xl\:bui-p-0\.5 { + padding: var(--bui-space-0_5); + } + + .xl\:bui-p-1 { + padding: var(--bui-space-1); + } + + .xl\:bui-p-1\.5 { + padding: var(--bui-space-1_5); + } + + .xl\:bui-p-2 { + padding: var(--bui-space-2); + } + + .xl\:bui-p-3 { + padding: var(--bui-space-3); + } + + .xl\:bui-p-4 { + padding: var(--bui-space-4); + } + + .xl\:bui-p-5 { + padding: var(--bui-space-5); + } + + .xl\:bui-p-6 { + padding: var(--bui-space-6); + } + + .xl\:bui-p-7 { + padding: var(--bui-space-7); + } + + .xl\:bui-p-8 { + padding: var(--bui-space-8); + } + + .xl\:bui-p-9 { + padding: var(--bui-space-9); + } + + .xl\:bui-p-10 { + padding: var(--bui-space-10); + } + + .xl\:bui-p-11 { + padding: var(--bui-space-11); + } + + .xl\:bui-p-12 { + padding: var(--bui-space-12); + } + + .xl\:bui-p-13 { + padding: var(--bui-space-13); + } + + .xl\:bui-p-14 { + padding: var(--bui-space-14); + } +} diff --git a/packages/ui/src/css/utilities/pb.css b/packages/ui/src/css/utilities/pb.css new file mode 100644 index 0000000000..bdd3881f86 --- /dev/null +++ b/packages/ui/src/css/utilities/pb.css @@ -0,0 +1,422 @@ +.bui-pb { + padding-bottom: var(--pb); +} + +.bui-pb-0\.5 { + padding-bottom: var(--bui-space-0_5); +} + +.bui-pb-1 { + padding-bottom: var(--bui-space-1); +} + +.bui-pb-1\.5 { + padding-bottom: var(--bui-space-1_5); +} + +.bui-pb-2 { + padding-bottom: var(--bui-space-2); +} + +.bui-pb-3 { + padding-bottom: var(--bui-space-3); +} + +.bui-pb-4 { + padding-bottom: var(--bui-space-4); +} + +.bui-pb-5 { + padding-bottom: var(--bui-space-5); +} + +.bui-pb-6 { + padding-bottom: var(--bui-space-6); +} + +.bui-pb-7 { + padding-bottom: var(--bui-space-7); +} + +.bui-pb-8 { + padding-bottom: var(--bui-space-8); +} + +.bui-pb-9 { + padding-bottom: var(--bui-space-9); +} + +.bui-pb-10 { + padding-bottom: var(--bui-space-10); +} + +.bui-pb-11 { + padding-bottom: var(--bui-space-11); +} + +.bui-pb-12 { + padding-bottom: var(--bui-space-12); +} + +.bui-pb-13 { + padding-bottom: var(--bui-space-13); +} + +.bui-pb-14 { + padding-bottom: var(--bui-space-14); +} + +/* Breakpoint xs */ +@media (min-width: 640px) { + .xs\:bui-pb { + padding-bottom: var(--pb-xs); + } + + .xs\:bui-pb-0\.5 { + padding-bottom: var(--bui-space-0_5); + } + + .xs\:bui-pb-1 { + padding-bottom: var(--bui-space-1); + } + + .xs\:bui-pb-1\.5 { + padding-bottom: var(--bui-space-1_5); + } + + .xs\:bui-pb-2 { + padding-bottom: var(--bui-space-2); + } + + .xs\:bui-pb-3 { + padding-bottom: var(--bui-space-3); + } + + .xs\:bui-pb-4 { + padding-bottom: var(--bui-space-4); + } + + .xs\:bui-pb-5 { + padding-bottom: var(--bui-space-5); + } + + .xs\:bui-pb-6 { + padding-bottom: var(--bui-space-6); + } + + .xs\:bui-pb-7 { + padding-bottom: var(--bui-space-7); + } + + .xs\:bui-pb-8 { + padding-bottom: var(--bui-space-8); + } + + .xs\:bui-pb-9 { + padding-bottom: var(--bui-space-9); + } + + .xs\:bui-pb-10 { + padding-bottom: var(--bui-space-10); + } + + .xs\:bui-pb-11 { + padding-bottom: var(--bui-space-11); + } + + .xs\:bui-pb-12 { + padding-bottom: var(--bui-space-12); + } + + .xs\:bui-pb-13 { + padding-bottom: var(--bui-space-13); + } + + .xs\:bui-pb-14 { + padding-bottom: var(--bui-space-14); + } +} + +/* Breakpoint sm */ +@media (min-width: 768px) { + .sm\:bui-pb { + padding-bottom: var(--pb-sm); + } + + .sm\:bui-pb-0\.5 { + padding-bottom: var(--bui-space-0_5); + } + + .sm\:bui-pb-1 { + padding-bottom: var(--bui-space-1); + } + + .sm\:bui-pb-1\.5 { + padding-bottom: var(--bui-space-1_5); + } + + .sm\:bui-pb-2 { + padding-bottom: var(--bui-space-2); + } + + .sm\:bui-pb-3 { + padding-bottom: var(--bui-space-3); + } + + .sm\:bui-pb-4 { + padding-bottom: var(--bui-space-4); + } + + .sm\:bui-pb-5 { + padding-bottom: var(--bui-space-5); + } + + .sm\:bui-pb-6 { + padding-bottom: var(--bui-space-6); + } + + .sm\:bui-pb-7 { + padding-bottom: var(--bui-space-7); + } + + .sm\:bui-pb-8 { + padding-bottom: var(--bui-space-8); + } + + .sm\:bui-pb-9 { + padding-bottom: var(--bui-space-9); + } + + .sm\:bui-pb-10 { + padding-bottom: var(--bui-space-10); + } + + .sm\:bui-pb-11 { + padding-bottom: var(--bui-space-11); + } + + .sm\:bui-pb-12 { + padding-bottom: var(--bui-space-12); + } + + .sm\:bui-pb-13 { + padding-bottom: var(--bui-space-13); + } + + .sm\:bui-pb-14 { + padding-bottom: var(--bui-space-14); + } +} + +/* Breakpoint md */ +@media (min-width: 1024px) { + .md\:bui-pb { + padding-bottom: var(--pb-md); + } + + .md\:bui-pb-0\.5 { + padding-bottom: var(--bui-space-0_5); + } + + .md\:bui-pb-1 { + padding-bottom: var(--bui-space-1); + } + + .md\:bui-pb-1\.5 { + padding-bottom: var(--bui-space-1_5); + } + + .md\:bui-pb-2 { + padding-bottom: var(--bui-space-2); + } + + .md\:bui-pb-3 { + padding-bottom: var(--bui-space-3); + } + + .md\:bui-pb-4 { + padding-bottom: var(--bui-space-4); + } + + .md\:bui-pb-5 { + padding-bottom: var(--bui-space-5); + } + + .md\:bui-pb-6 { + padding-bottom: var(--bui-space-6); + } + + .md\:bui-pb-7 { + padding-bottom: var(--bui-space-7); + } + + .md\:bui-pb-8 { + padding-bottom: var(--bui-space-8); + } + + .md\:bui-pb-9 { + padding-bottom: var(--bui-space-9); + } + + .md\:bui-pb-10 { + padding-bottom: var(--bui-space-10); + } + + .md\:bui-pb-11 { + padding-bottom: var(--bui-space-11); + } + + .md\:bui-pb-12 { + padding-bottom: var(--bui-space-12); + } + + .md\:bui-pb-13 { + padding-bottom: var(--bui-space-13); + } + + .md\:bui-pb-14 { + padding-bottom: var(--bui-space-14); + } +} + +/* Breakpoint lg */ +@media (min-width: 1280px) { + .lg\:bui-pb { + padding-bottom: var(--pb-lg); + } + + .lg\:bui-pb-0\.5 { + padding-bottom: var(--bui-space-0_5); + } + + .lg\:bui-pb-1 { + padding-bottom: var(--bui-space-1); + } + + .lg\:bui-pb-1\.5 { + padding-bottom: var(--bui-space-1_5); + } + + .lg\:bui-pb-2 { + padding-bottom: var(--bui-space-2); + } + + .lg\:bui-pb-3 { + padding-bottom: var(--bui-space-3); + } + + .lg\:bui-pb-4 { + padding-bottom: var(--bui-space-4); + } + + .lg\:bui-pb-5 { + padding-bottom: var(--bui-space-5); + } + + .lg\:bui-pb-6 { + padding-bottom: var(--bui-space-6); + } + + .lg\:bui-pb-7 { + padding-bottom: var(--bui-space-7); + } + + .lg\:bui-pb-8 { + padding-bottom: var(--bui-space-8); + } + + .lg\:bui-pb-9 { + padding-bottom: var(--bui-space-9); + } + + .lg\:bui-pb-10 { + padding-bottom: var(--bui-space-10); + } + + .lg\:bui-pb-11 { + padding-bottom: var(--bui-space-11); + } + + .lg\:bui-pb-12 { + padding-bottom: var(--bui-space-12); + } + + .lg\:bui-pb-13 { + padding-bottom: var(--bui-space-13); + } + + .lg\:bui-pb-14 { + padding-bottom: var(--bui-space-14); + } +} + +/* Breakpoint xl */ +@media (min-width: 1536px) { + .xl\:bui-pb { + padding-bottom: var(--pb-xl); + } + + .xl\:bui-pb-0\.5 { + padding-bottom: var(--bui-space-0_5); + } + + .xl\:bui-pb-1 { + padding-bottom: var(--bui-space-1); + } + + .xl\:bui-pb-1\.5 { + padding-bottom: var(--bui-space-1_5); + } + + .xl\:bui-pb-2 { + padding-bottom: var(--bui-space-2); + } + + .xl\:bui-pb-3 { + padding-bottom: var(--bui-space-3); + } + + .xl\:bui-pb-4 { + padding-bottom: var(--bui-space-4); + } + + .xl\:bui-pb-5 { + padding-bottom: var(--bui-space-5); + } + + .xl\:bui-pb-6 { + padding-bottom: var(--bui-space-6); + } + + .xl\:bui-pb-7 { + padding-bottom: var(--bui-space-7); + } + + .xl\:bui-pb-8 { + padding-bottom: var(--bui-space-8); + } + + .xl\:bui-pb-9 { + padding-bottom: var(--bui-space-9); + } + + .xl\:bui-pb-10 { + padding-bottom: var(--bui-space-10); + } + + .xl\:bui-pb-11 { + padding-bottom: var(--bui-space-11); + } + + .xl\:bui-pb-12 { + padding-bottom: var(--bui-space-12); + } + + .xl\:bui-pb-13 { + padding-bottom: var(--bui-space-13); + } + + .xl\:bui-pb-14 { + padding-bottom: var(--bui-space-14); + } +} diff --git a/packages/ui/src/css/utilities/pl.css b/packages/ui/src/css/utilities/pl.css new file mode 100644 index 0000000000..07e064293a --- /dev/null +++ b/packages/ui/src/css/utilities/pl.css @@ -0,0 +1,422 @@ +.bui-pl { + padding-left: var(--pl); +} + +.bui-pl-0\.5 { + padding-left: var(--bui-space-0_5); +} + +.bui-pl-1 { + padding-left: var(--bui-space-1); +} + +.bui-pl-1\.5 { + padding-left: var(--bui-space-1_5); +} + +.bui-pl-2 { + padding-left: var(--bui-space-2); +} + +.bui-pl-3 { + padding-left: var(--bui-space-3); +} + +.bui-pl-4 { + padding-left: var(--bui-space-4); +} + +.bui-pl-5 { + padding-left: var(--bui-space-5); +} + +.bui-pl-6 { + padding-left: var(--bui-space-6); +} + +.bui-pl-7 { + padding-left: var(--bui-space-7); +} + +.bui-pl-8 { + padding-left: var(--bui-space-8); +} + +.bui-pl-9 { + padding-left: var(--bui-space-9); +} + +.bui-pl-10 { + padding-left: var(--bui-space-10); +} + +.bui-pl-11 { + padding-left: var(--bui-space-11); +} + +.bui-pl-12 { + padding-left: var(--bui-space-12); +} + +.bui-pl-13 { + padding-left: var(--bui-space-13); +} + +.bui-pl-14 { + padding-left: var(--bui-space-14); +} + +/* Breakpoint xs */ +@media (min-width: 640px) { + .xs\:bui-pl { + padding-left: var(--pl-xs); + } + + .xs\:bui-pl-0\.5 { + padding-left: var(--bui-space-0_5); + } + + .xs\:bui-pl-1 { + padding-left: var(--bui-space-1); + } + + .xs\:bui-pl-1\.5 { + padding-left: var(--bui-space-1_5); + } + + .xs\:bui-pl-2 { + padding-left: var(--bui-space-2); + } + + .xs\:bui-pl-3 { + padding-left: var(--bui-space-3); + } + + .xs\:bui-pl-4 { + padding-left: var(--bui-space-4); + } + + .xs\:bui-pl-5 { + padding-left: var(--bui-space-5); + } + + .xs\:bui-pl-6 { + padding-left: var(--bui-space-6); + } + + .xs\:bui-pl-7 { + padding-left: var(--bui-space-7); + } + + .xs\:bui-pl-8 { + padding-left: var(--bui-space-8); + } + + .xs\:bui-pl-9 { + padding-left: var(--bui-space-9); + } + + .xs\:bui-pl-10 { + padding-left: var(--bui-space-10); + } + + .xs\:bui-pl-11 { + padding-left: var(--bui-space-11); + } + + .xs\:bui-pl-12 { + padding-left: var(--bui-space-12); + } + + .xs\:bui-pl-13 { + padding-left: var(--bui-space-13); + } + + .xs\:bui-pl-14 { + padding-left: var(--bui-space-14); + } +} + +/* Breakpoint sm */ +@media (min-width: 768px) { + .sm\:bui-pl { + padding-left: var(--pl-sm); + } + + .sm\:bui-pl-0\.5 { + padding-left: var(--bui-space-0_5); + } + + .sm\:bui-pl-1 { + padding-left: var(--bui-space-1); + } + + .sm\:bui-pl-1\.5 { + padding-left: var(--bui-space-1_5); + } + + .sm\:bui-pl-2 { + padding-left: var(--bui-space-2); + } + + .sm\:bui-pl-3 { + padding-left: var(--bui-space-3); + } + + .sm\:bui-pl-4 { + padding-left: var(--bui-space-4); + } + + .sm\:bui-pl-5 { + padding-left: var(--bui-space-5); + } + + .sm\:bui-pl-6 { + padding-left: var(--bui-space-6); + } + + .sm\:bui-pl-7 { + padding-left: var(--bui-space-7); + } + + .sm\:bui-pl-8 { + padding-left: var(--bui-space-8); + } + + .sm\:bui-pl-9 { + padding-left: var(--bui-space-9); + } + + .sm\:bui-pl-10 { + padding-left: var(--bui-space-10); + } + + .sm\:bui-pl-11 { + padding-left: var(--bui-space-11); + } + + .sm\:bui-pl-12 { + padding-left: var(--bui-space-12); + } + + .sm\:bui-pl-13 { + padding-left: var(--bui-space-13); + } + + .sm\:bui-pl-14 { + padding-left: var(--bui-space-14); + } +} + +/* Breakpoint md */ +@media (min-width: 1024px) { + .md\:bui-pl { + padding-left: var(--pl-md); + } + + .md\:bui-pl-0\.5 { + padding-left: var(--bui-space-0_5); + } + + .md\:bui-pl-1 { + padding-left: var(--bui-space-1); + } + + .md\:bui-pl-1\.5 { + padding-left: var(--bui-space-1_5); + } + + .md\:bui-pl-2 { + padding-left: var(--bui-space-2); + } + + .md\:bui-pl-3 { + padding-left: var(--bui-space-3); + } + + .md\:bui-pl-4 { + padding-left: var(--bui-space-4); + } + + .md\:bui-pl-5 { + padding-left: var(--bui-space-5); + } + + .md\:bui-pl-6 { + padding-left: var(--bui-space-6); + } + + .md\:bui-pl-7 { + padding-left: var(--bui-space-7); + } + + .md\:bui-pl-8 { + padding-left: var(--bui-space-8); + } + + .md\:bui-pl-9 { + padding-left: var(--bui-space-9); + } + + .md\:bui-pl-10 { + padding-left: var(--bui-space-10); + } + + .md\:bui-pl-11 { + padding-left: var(--bui-space-11); + } + + .md\:bui-pl-12 { + padding-left: var(--bui-space-12); + } + + .md\:bui-pl-13 { + padding-left: var(--bui-space-13); + } + + .md\:bui-pl-14 { + padding-left: var(--bui-space-14); + } +} + +/* Breakpoint lg */ +@media (min-width: 1280px) { + .lg\:bui-pl { + padding-left: var(--pl-lg); + } + + .lg\:bui-pl-0\.5 { + padding-left: var(--bui-space-0_5); + } + + .lg\:bui-pl-1 { + padding-left: var(--bui-space-1); + } + + .lg\:bui-pl-1\.5 { + padding-left: var(--bui-space-1_5); + } + + .lg\:bui-pl-2 { + padding-left: var(--bui-space-2); + } + + .lg\:bui-pl-3 { + padding-left: var(--bui-space-3); + } + + .lg\:bui-pl-4 { + padding-left: var(--bui-space-4); + } + + .lg\:bui-pl-5 { + padding-left: var(--bui-space-5); + } + + .lg\:bui-pl-6 { + padding-left: var(--bui-space-6); + } + + .lg\:bui-pl-7 { + padding-left: var(--bui-space-7); + } + + .lg\:bui-pl-8 { + padding-left: var(--bui-space-8); + } + + .lg\:bui-pl-9 { + padding-left: var(--bui-space-9); + } + + .lg\:bui-pl-10 { + padding-left: var(--bui-space-10); + } + + .lg\:bui-pl-11 { + padding-left: var(--bui-space-11); + } + + .lg\:bui-pl-12 { + padding-left: var(--bui-space-12); + } + + .lg\:bui-pl-13 { + padding-left: var(--bui-space-13); + } + + .lg\:bui-pl-14 { + padding-left: var(--bui-space-14); + } +} + +/* Breakpoint xl */ +@media (min-width: 1536px) { + .xl\:bui-pl { + padding-left: var(--pl-xl); + } + + .xl\:bui-pl-0\.5 { + padding-left: var(--bui-space-0_5); + } + + .xl\:bui-pl-1 { + padding-left: var(--bui-space-1); + } + + .xl\:bui-pl-1\.5 { + padding-left: var(--bui-space-1_5); + } + + .xl\:bui-pl-2 { + padding-left: var(--bui-space-2); + } + + .xl\:bui-pl-3 { + padding-left: var(--bui-space-3); + } + + .xl\:bui-pl-4 { + padding-left: var(--bui-space-4); + } + + .xl\:bui-pl-5 { + padding-left: var(--bui-space-5); + } + + .xl\:bui-pl-6 { + padding-left: var(--bui-space-6); + } + + .xl\:bui-pl-7 { + padding-left: var(--bui-space-7); + } + + .xl\:bui-pl-8 { + padding-left: var(--bui-space-8); + } + + .xl\:bui-pl-9 { + padding-left: var(--bui-space-9); + } + + .xl\:bui-pl-10 { + padding-left: var(--bui-space-10); + } + + .xl\:bui-pl-11 { + padding-left: var(--bui-space-11); + } + + .xl\:bui-pl-12 { + padding-left: var(--bui-space-12); + } + + .xl\:bui-pl-13 { + padding-left: var(--bui-space-13); + } + + .xl\:bui-pl-14 { + padding-left: var(--bui-space-14); + } +} diff --git a/packages/canon/src/css/utilities/position.css b/packages/ui/src/css/utilities/position.css similarity index 55% rename from packages/canon/src/css/utilities/position.css rename to packages/ui/src/css/utilities/position.css index 764ada623b..11cc0994b6 100644 --- a/packages/canon/src/css/utilities/position.css +++ b/packages/ui/src/css/utilities/position.css @@ -1,134 +1,134 @@ -.cu-position-absolute { +.bui-position-absolute { position: absolute; } -.cu-position-fixed { +.bui-position-fixed { position: fixed; } -.cu-position-sticky { +.bui-position-sticky { position: sticky; } -.cu-position-relative { +.bui-position-relative { position: relative; } -.cu-position-static { +.bui-position-static { position: static; } /* Breakpoint xs */ @media (min-width: 640px) { - .xs\:cu-position-absolute { + .xs\:bui-position-absolute { position: absolute; } - .xs\:cu-position-fixed { + .xs\:bui-position-fixed { position: fixed; } - .xs\:cu-position-sticky { + .xs\:bui-position-sticky { position: sticky; } - .xs\:cu-position-relative { + .xs\:bui-position-relative { position: relative; } - .xs\:cu-position-static { + .xs\:bui-position-static { position: static; } } /* Breakpoint sm */ @media (min-width: 768px) { - .sm\:cu-position-absolute { + .sm\:bui-position-absolute { position: absolute; } - .sm\:cu-position-fixed { + .sm\:bui-position-fixed { position: fixed; } - .sm\:cu-position-sticky { + .sm\:bui-position-sticky { position: sticky; } - .sm\:cu-position-relative { + .sm\:bui-position-relative { position: relative; } - .sm\:cu-position-static { + .sm\:bui-position-static { position: static; } } /* Breakpoint md */ @media (min-width: 1024px) { - .md\:cu-position-absolute { + .md\:bui-position-absolute { position: absolute; } - .md\:cu-position-fixed { + .md\:bui-position-fixed { position: fixed; } - .md\:cu-position-sticky { + .md\:bui-position-sticky { position: sticky; } - .md\:cu-position-relative { + .md\:bui-position-relative { position: relative; } - .md\:cu-position-static { + .md\:bui-position-static { position: static; } } /* Breakpoint lg */ @media (min-width: 1280px) { - .lg\:cu-position-absolute { + .lg\:bui-position-absolute { position: absolute; } - .lg\:cu-position-fixed { + .lg\:bui-position-fixed { position: fixed; } - .lg\:cu-position-sticky { + .lg\:bui-position-sticky { position: sticky; } - .lg\:cu-position-relative { + .lg\:bui-position-relative { position: relative; } - .lg\:cu-position-static { + .lg\:bui-position-static { position: static; } } /* Breakpoint xl */ @media (min-width: 1536px) { - .xl\:cu-position-absolute { + .xl\:bui-position-absolute { position: absolute; } - .xl\:cu-position-fixed { + .xl\:bui-position-fixed { position: fixed; } - .xl\:cu-position-sticky { + .xl\:bui-position-sticky { position: sticky; } - .xl\:cu-position-relative { + .xl\:bui-position-relative { position: relative; } - .xl\:cu-position-static { + .xl\:bui-position-static { position: static; } } diff --git a/packages/ui/src/css/utilities/pr.css b/packages/ui/src/css/utilities/pr.css new file mode 100644 index 0000000000..c6fa94a1c5 --- /dev/null +++ b/packages/ui/src/css/utilities/pr.css @@ -0,0 +1,422 @@ +.bui-pr { + padding-right: var(--pr); +} + +.bui-pr-0\.5 { + padding-right: var(--bui-space-0_5); +} + +.bui-pr-1 { + padding-right: var(--bui-space-1); +} + +.bui-pr-1\.5 { + padding-right: var(--bui-space-1_5); +} + +.bui-pr-2 { + padding-right: var(--bui-space-2); +} + +.bui-pr-3 { + padding-right: var(--bui-space-3); +} + +.bui-pr-4 { + padding-right: var(--bui-space-4); +} + +.bui-pr-5 { + padding-right: var(--bui-space-5); +} + +.bui-pr-6 { + padding-right: var(--bui-space-6); +} + +.bui-pr-7 { + padding-right: var(--bui-space-7); +} + +.bui-pr-8 { + padding-right: var(--bui-space-8); +} + +.bui-pr-9 { + padding-right: var(--bui-space-9); +} + +.bui-pr-10 { + padding-right: var(--bui-space-10); +} + +.bui-pr-11 { + padding-right: var(--bui-space-11); +} + +.bui-pr-12 { + padding-right: var(--bui-space-12); +} + +.bui-pr-13 { + padding-right: var(--bui-space-13); +} + +.bui-pr-14 { + padding-right: var(--bui-space-14); +} + +/* Breakpoint xs */ +@media (min-width: 640px) { + .xs\:bui-pr { + padding-right: var(--pr-xs); + } + + .xs\:bui-pr-0\.5 { + padding-right: var(--bui-space-0_5); + } + + .xs\:bui-pr-1 { + padding-right: var(--bui-space-1); + } + + .xs\:bui-pr-1\.5 { + padding-right: var(--bui-space-1_5); + } + + .xs\:bui-pr-2 { + padding-right: var(--bui-space-2); + } + + .xs\:bui-pr-3 { + padding-right: var(--bui-space-3); + } + + .xs\:bui-pr-4 { + padding-right: var(--bui-space-4); + } + + .xs\:bui-pr-5 { + padding-right: var(--bui-space-5); + } + + .xs\:bui-pr-6 { + padding-right: var(--bui-space-6); + } + + .xs\:bui-pr-7 { + padding-right: var(--bui-space-7); + } + + .xs\:bui-pr-8 { + padding-right: var(--bui-space-8); + } + + .xs\:bui-pr-9 { + padding-right: var(--bui-space-9); + } + + .xs\:bui-pr-10 { + padding-right: var(--bui-space-10); + } + + .xs\:bui-pr-11 { + padding-right: var(--bui-space-11); + } + + .xs\:bui-pr-12 { + padding-right: var(--bui-space-12); + } + + .xs\:bui-pr-13 { + padding-right: var(--bui-space-13); + } + + .xs\:bui-pr-14 { + padding-right: var(--bui-space-14); + } +} + +/* Breakpoint sm */ +@media (min-width: 768px) { + .sm\:bui-pr { + padding-right: var(--pr-sm); + } + + .sm\:bui-pr-0\.5 { + padding-right: var(--bui-space-0_5); + } + + .sm\:bui-pr-1 { + padding-right: var(--bui-space-1); + } + + .sm\:bui-pr-1\.5 { + padding-right: var(--bui-space-1_5); + } + + .sm\:bui-pr-2 { + padding-right: var(--bui-space-2); + } + + .sm\:bui-pr-3 { + padding-right: var(--bui-space-3); + } + + .sm\:bui-pr-4 { + padding-right: var(--bui-space-4); + } + + .sm\:bui-pr-5 { + padding-right: var(--bui-space-5); + } + + .sm\:bui-pr-6 { + padding-right: var(--bui-space-6); + } + + .sm\:bui-pr-7 { + padding-right: var(--bui-space-7); + } + + .sm\:bui-pr-8 { + padding-right: var(--bui-space-8); + } + + .sm\:bui-pr-9 { + padding-right: var(--bui-space-9); + } + + .sm\:bui-pr-10 { + padding-right: var(--bui-space-10); + } + + .sm\:bui-pr-11 { + padding-right: var(--bui-space-11); + } + + .sm\:bui-pr-12 { + padding-right: var(--bui-space-12); + } + + .sm\:bui-pr-13 { + padding-right: var(--bui-space-13); + } + + .sm\:bui-pr-14 { + padding-right: var(--bui-space-14); + } +} + +/* Breakpoint md */ +@media (min-width: 1024px) { + .md\:bui-pr { + padding-right: var(--pr-md); + } + + .md\:bui-pr-0\.5 { + padding-right: var(--bui-space-0_5); + } + + .md\:bui-pr-1 { + padding-right: var(--bui-space-1); + } + + .md\:bui-pr-1\.5 { + padding-right: var(--bui-space-1_5); + } + + .md\:bui-pr-2 { + padding-right: var(--bui-space-2); + } + + .md\:bui-pr-3 { + padding-right: var(--bui-space-3); + } + + .md\:bui-pr-4 { + padding-right: var(--bui-space-4); + } + + .md\:bui-pr-5 { + padding-right: var(--bui-space-5); + } + + .md\:bui-pr-6 { + padding-right: var(--bui-space-6); + } + + .md\:bui-pr-7 { + padding-right: var(--bui-space-7); + } + + .md\:bui-pr-8 { + padding-right: var(--bui-space-8); + } + + .md\:bui-pr-9 { + padding-right: var(--bui-space-9); + } + + .md\:bui-pr-10 { + padding-right: var(--bui-space-10); + } + + .md\:bui-pr-11 { + padding-right: var(--bui-space-11); + } + + .md\:bui-pr-12 { + padding-right: var(--bui-space-12); + } + + .md\:bui-pr-13 { + padding-right: var(--bui-space-13); + } + + .md\:bui-pr-14 { + padding-right: var(--bui-space-14); + } +} + +/* Breakpoint lg */ +@media (min-width: 1280px) { + .lg\:bui-pr { + padding-right: var(--pr-lg); + } + + .lg\:bui-pr-0\.5 { + padding-right: var(--bui-space-0_5); + } + + .lg\:bui-pr-1 { + padding-right: var(--bui-space-1); + } + + .lg\:bui-pr-1\.5 { + padding-right: var(--bui-space-1_5); + } + + .lg\:bui-pr-2 { + padding-right: var(--bui-space-2); + } + + .lg\:bui-pr-3 { + padding-right: var(--bui-space-3); + } + + .lg\:bui-pr-4 { + padding-right: var(--bui-space-4); + } + + .lg\:bui-pr-5 { + padding-right: var(--bui-space-5); + } + + .lg\:bui-pr-6 { + padding-right: var(--bui-space-6); + } + + .lg\:bui-pr-7 { + padding-right: var(--bui-space-7); + } + + .lg\:bui-pr-8 { + padding-right: var(--bui-space-8); + } + + .lg\:bui-pr-9 { + padding-right: var(--bui-space-9); + } + + .lg\:bui-pr-10 { + padding-right: var(--bui-space-10); + } + + .lg\:bui-pr-11 { + padding-right: var(--bui-space-11); + } + + .lg\:bui-pr-12 { + padding-right: var(--bui-space-12); + } + + .lg\:bui-pr-13 { + padding-right: var(--bui-space-13); + } + + .lg\:bui-pr-14 { + padding-right: var(--bui-space-14); + } +} + +/* Breakpoint xl */ +@media (min-width: 1536px) { + .xl\:bui-pr { + padding-right: var(--pr-xl); + } + + .xl\:bui-pr-0\.5 { + padding-right: var(--bui-space-0_5); + } + + .xl\:bui-pr-1 { + padding-right: var(--bui-space-1); + } + + .xl\:bui-pr-1\.5 { + padding-right: var(--bui-space-1_5); + } + + .xl\:bui-pr-2 { + padding-right: var(--bui-space-2); + } + + .xl\:bui-pr-3 { + padding-right: var(--bui-space-3); + } + + .xl\:bui-pr-4 { + padding-right: var(--bui-space-4); + } + + .xl\:bui-pr-5 { + padding-right: var(--bui-space-5); + } + + .xl\:bui-pr-6 { + padding-right: var(--bui-space-6); + } + + .xl\:bui-pr-7 { + padding-right: var(--bui-space-7); + } + + .xl\:bui-pr-8 { + padding-right: var(--bui-space-8); + } + + .xl\:bui-pr-9 { + padding-right: var(--bui-space-9); + } + + .xl\:bui-pr-10 { + padding-right: var(--bui-space-10); + } + + .xl\:bui-pr-11 { + padding-right: var(--bui-space-11); + } + + .xl\:bui-pr-12 { + padding-right: var(--bui-space-12); + } + + .xl\:bui-pr-13 { + padding-right: var(--bui-space-13); + } + + .xl\:bui-pr-14 { + padding-right: var(--bui-space-14); + } +} diff --git a/packages/ui/src/css/utilities/pt.css b/packages/ui/src/css/utilities/pt.css new file mode 100644 index 0000000000..cf557257e3 --- /dev/null +++ b/packages/ui/src/css/utilities/pt.css @@ -0,0 +1,422 @@ +.bui-pt { + padding-top: var(--pt); +} + +.bui-pt-0\.5 { + padding-top: var(--bui-space-0_5); +} + +.bui-pt-1 { + padding-top: var(--bui-space-1); +} + +.bui-pt-1\.5 { + padding-top: var(--bui-space-1_5); +} + +.bui-pt-2 { + padding-top: var(--bui-space-2); +} + +.bui-pt-3 { + padding-top: var(--bui-space-3); +} + +.bui-pt-4 { + padding-top: var(--bui-space-4); +} + +.bui-pt-5 { + padding-top: var(--bui-space-5); +} + +.bui-pt-6 { + padding-top: var(--bui-space-6); +} + +.bui-pt-7 { + padding-top: var(--bui-space-7); +} + +.bui-pt-8 { + padding-top: var(--bui-space-8); +} + +.bui-pt-9 { + padding-top: var(--bui-space-9); +} + +.bui-pt-10 { + padding-top: var(--bui-space-10); +} + +.bui-pt-11 { + padding-top: var(--bui-space-11); +} + +.bui-pt-12 { + padding-top: var(--bui-space-12); +} + +.bui-pt-13 { + padding-top: var(--bui-space-13); +} + +.bui-pt-14 { + padding-top: var(--bui-space-14); +} + +/* Breakpoint xs */ +@media (min-width: 640px) { + .xs\:bui-pt { + padding-top: var(--pt-xs); + } + + .xs\:bui-pt-0\.5 { + padding-top: var(--bui-space-0_5); + } + + .xs\:bui-pt-1 { + padding-top: var(--bui-space-1); + } + + .xs\:bui-pt-1\.5 { + padding-top: var(--bui-space-1_5); + } + + .xs\:bui-pt-2 { + padding-top: var(--bui-space-2); + } + + .xs\:bui-pt-3 { + padding-top: var(--bui-space-3); + } + + .xs\:bui-pt-4 { + padding-top: var(--bui-space-4); + } + + .xs\:bui-pt-5 { + padding-top: var(--bui-space-5); + } + + .xs\:bui-pt-6 { + padding-top: var(--bui-space-6); + } + + .xs\:bui-pt-7 { + padding-top: var(--bui-space-7); + } + + .xs\:bui-pt-8 { + padding-top: var(--bui-space-8); + } + + .xs\:bui-pt-9 { + padding-top: var(--bui-space-9); + } + + .xs\:bui-pt-10 { + padding-top: var(--bui-space-10); + } + + .xs\:bui-pt-11 { + padding-top: var(--bui-space-11); + } + + .xs\:bui-pt-12 { + padding-top: var(--bui-space-12); + } + + .xs\:bui-pt-13 { + padding-top: var(--bui-space-13); + } + + .xs\:bui-pt-14 { + padding-top: var(--bui-space-14); + } +} + +/* Breakpoint sm */ +@media (min-width: 768px) { + .sm\:bui-pt { + padding-top: var(--pt-sm); + } + + .sm\:bui-pt-0\.5 { + padding-top: var(--bui-space-0_5); + } + + .sm\:bui-pt-1 { + padding-top: var(--bui-space-1); + } + + .sm\:bui-pt-1\.5 { + padding-top: var(--bui-space-1_5); + } + + .sm\:bui-pt-2 { + padding-top: var(--bui-space-2); + } + + .sm\:bui-pt-3 { + padding-top: var(--bui-space-3); + } + + .sm\:bui-pt-4 { + padding-top: var(--bui-space-4); + } + + .sm\:bui-pt-5 { + padding-top: var(--bui-space-5); + } + + .sm\:bui-pt-6 { + padding-top: var(--bui-space-6); + } + + .sm\:bui-pt-7 { + padding-top: var(--bui-space-7); + } + + .sm\:bui-pt-8 { + padding-top: var(--bui-space-8); + } + + .sm\:bui-pt-9 { + padding-top: var(--bui-space-9); + } + + .sm\:bui-pt-10 { + padding-top: var(--bui-space-10); + } + + .sm\:bui-pt-11 { + padding-top: var(--bui-space-11); + } + + .sm\:bui-pt-12 { + padding-top: var(--bui-space-12); + } + + .sm\:bui-pt-13 { + padding-top: var(--bui-space-13); + } + + .sm\:bui-pt-14 { + padding-top: var(--bui-space-14); + } +} + +/* Breakpoint md */ +@media (min-width: 1024px) { + .md\:bui-pt { + padding-top: var(--pt-md); + } + + .md\:bui-pt-0\.5 { + padding-top: var(--bui-space-0_5); + } + + .md\:bui-pt-1 { + padding-top: var(--bui-space-1); + } + + .md\:bui-pt-1\.5 { + padding-top: var(--bui-space-1_5); + } + + .md\:bui-pt-2 { + padding-top: var(--bui-space-2); + } + + .md\:bui-pt-3 { + padding-top: var(--bui-space-3); + } + + .md\:bui-pt-4 { + padding-top: var(--bui-space-4); + } + + .md\:bui-pt-5 { + padding-top: var(--bui-space-5); + } + + .md\:bui-pt-6 { + padding-top: var(--bui-space-6); + } + + .md\:bui-pt-7 { + padding-top: var(--bui-space-7); + } + + .md\:bui-pt-8 { + padding-top: var(--bui-space-8); + } + + .md\:bui-pt-9 { + padding-top: var(--bui-space-9); + } + + .md\:bui-pt-10 { + padding-top: var(--bui-space-10); + } + + .md\:bui-pt-11 { + padding-top: var(--bui-space-11); + } + + .md\:bui-pt-12 { + padding-top: var(--bui-space-12); + } + + .md\:bui-pt-13 { + padding-top: var(--bui-space-13); + } + + .md\:bui-pt-14 { + padding-top: var(--bui-space-14); + } +} + +/* Breakpoint lg */ +@media (min-width: 1280px) { + .lg\:bui-pt { + padding-top: var(--pt-lg); + } + + .lg\:bui-pt-0\.5 { + padding-top: var(--bui-space-0_5); + } + + .lg\:bui-pt-1 { + padding-top: var(--bui-space-1); + } + + .lg\:bui-pt-1\.5 { + padding-top: var(--bui-space-1_5); + } + + .lg\:bui-pt-2 { + padding-top: var(--bui-space-2); + } + + .lg\:bui-pt-3 { + padding-top: var(--bui-space-3); + } + + .lg\:bui-pt-4 { + padding-top: var(--bui-space-4); + } + + .lg\:bui-pt-5 { + padding-top: var(--bui-space-5); + } + + .lg\:bui-pt-6 { + padding-top: var(--bui-space-6); + } + + .lg\:bui-pt-7 { + padding-top: var(--bui-space-7); + } + + .lg\:bui-pt-8 { + padding-top: var(--bui-space-8); + } + + .lg\:bui-pt-9 { + padding-top: var(--bui-space-9); + } + + .lg\:bui-pt-10 { + padding-top: var(--bui-space-10); + } + + .lg\:bui-pt-11 { + padding-top: var(--bui-space-11); + } + + .lg\:bui-pt-12 { + padding-top: var(--bui-space-12); + } + + .lg\:bui-pt-13 { + padding-top: var(--bui-space-13); + } + + .lg\:bui-pt-14 { + padding-top: var(--bui-space-14); + } +} + +/* Breakpoint xl */ +@media (min-width: 1536px) { + .xl\:bui-pt { + padding-top: var(--pt-xl); + } + + .xl\:bui-pt-0\.5 { + padding-top: var(--bui-space-0_5); + } + + .xl\:bui-pt-1 { + padding-top: var(--bui-space-1); + } + + .xl\:bui-pt-1\.5 { + padding-top: var(--bui-space-1_5); + } + + .xl\:bui-pt-2 { + padding-top: var(--bui-space-2); + } + + .xl\:bui-pt-3 { + padding-top: var(--bui-space-3); + } + + .xl\:bui-pt-4 { + padding-top: var(--bui-space-4); + } + + .xl\:bui-pt-5 { + padding-top: var(--bui-space-5); + } + + .xl\:bui-pt-6 { + padding-top: var(--bui-space-6); + } + + .xl\:bui-pt-7 { + padding-top: var(--bui-space-7); + } + + .xl\:bui-pt-8 { + padding-top: var(--bui-space-8); + } + + .xl\:bui-pt-9 { + padding-top: var(--bui-space-9); + } + + .xl\:bui-pt-10 { + padding-top: var(--bui-space-10); + } + + .xl\:bui-pt-11 { + padding-top: var(--bui-space-11); + } + + .xl\:bui-pt-12 { + padding-top: var(--bui-space-12); + } + + .xl\:bui-pt-13 { + padding-top: var(--bui-space-13); + } + + .xl\:bui-pt-14 { + padding-top: var(--bui-space-14); + } +} diff --git a/packages/ui/src/css/utilities/px.css b/packages/ui/src/css/utilities/px.css new file mode 100644 index 0000000000..37a41d832e --- /dev/null +++ b/packages/ui/src/css/utilities/px.css @@ -0,0 +1,524 @@ +.bui-px { + padding-left: var(--px); + padding-right: var(--px); +} + +.bui-px-0\.5 { + padding-left: var(--bui-space-0_5); + padding-right: var(--bui-space-0_5); +} + +.bui-px-1 { + padding-left: var(--bui-space-1); + padding-right: var(--bui-space-1); +} + +.bui-px-1\.5 { + padding-left: var(--bui-space-1_5); + padding-right: var(--bui-space-1_5); +} + +.bui-px-2 { + padding-left: var(--bui-space-2); + padding-right: var(--bui-space-2); +} + +.bui-px-3 { + padding-left: var(--bui-space-3); + padding-right: var(--bui-space-3); +} + +.bui-px-4 { + padding-left: var(--bui-space-4); + padding-right: var(--bui-space-4); +} + +.bui-px-5 { + padding-left: var(--bui-space-5); + padding-right: var(--bui-space-5); +} + +.bui-px-6 { + padding-left: var(--bui-space-6); + padding-right: var(--bui-space-6); +} + +.bui-px-7 { + padding-left: var(--bui-space-7); + padding-right: var(--bui-space-7); +} + +.bui-px-8 { + padding-left: var(--bui-space-8); + padding-right: var(--bui-space-8); +} + +.bui-px-9 { + padding-left: var(--bui-space-9); + padding-right: var(--bui-space-9); +} + +.bui-px-10 { + padding-left: var(--bui-space-10); + padding-right: var(--bui-space-10); +} + +.bui-px-11 { + padding-left: var(--bui-space-11); + padding-right: var(--bui-space-11); +} + +.bui-px-12 { + padding-left: var(--bui-space-12); + padding-right: var(--bui-space-12); +} + +.bui-px-13 { + padding-left: var(--bui-space-13); + padding-right: var(--bui-space-13); +} + +.bui-px-14 { + padding-left: var(--bui-space-14); + padding-right: var(--bui-space-14); +} + +/* Breakpoint xs */ +@media (min-width: 640px) { + .xs\:bui-px { + padding-left: var(--px-xs); + padding-right: var(--px-xs); + } + + .xs\:bui-px-0\.5 { + padding-left: var(--bui-space-0_5); + padding-right: var(--bui-space-0_5); + } + + .xs\:bui-px-1 { + padding-left: var(--bui-space-1); + padding-right: var(--bui-space-1); + } + + .xs\:bui-px-1\.5 { + padding-left: var(--bui-space-1_5); + padding-right: var(--bui-space-1_5); + } + + .xs\:bui-px-2 { + padding-left: var(--bui-space-2); + padding-right: var(--bui-space-2); + } + + .xs\:bui-px-3 { + padding-left: var(--bui-space-3); + padding-right: var(--bui-space-3); + } + + .xs\:bui-px-4 { + padding-left: var(--bui-space-4); + padding-right: var(--bui-space-4); + } + + .xs\:bui-px-5 { + padding-left: var(--bui-space-5); + padding-right: var(--bui-space-5); + } + + .xs\:bui-px-6 { + padding-left: var(--bui-space-6); + padding-right: var(--bui-space-6); + } + + .xs\:bui-px-7 { + padding-left: var(--bui-space-7); + padding-right: var(--bui-space-7); + } + + .xs\:bui-px-8 { + padding-left: var(--bui-space-8); + padding-right: var(--bui-space-8); + } + + .xs\:bui-px-9 { + padding-left: var(--bui-space-9); + padding-right: var(--bui-space-9); + } + + .xs\:bui-px-10 { + padding-left: var(--bui-space-10); + padding-right: var(--bui-space-10); + } + + .xs\:bui-px-11 { + padding-left: var(--bui-space-11); + padding-right: var(--bui-space-11); + } + + .xs\:bui-px-12 { + padding-left: var(--bui-space-12); + padding-right: var(--bui-space-12); + } + + .xs\:bui-px-13 { + padding-left: var(--bui-space-13); + padding-right: var(--bui-space-13); + } + + .xs\:bui-px-14 { + padding-left: var(--bui-space-14); + padding-right: var(--bui-space-14); + } +} + +/* Breakpoint sm */ +@media (min-width: 768px) { + .sm\:bui-px { + padding-left: var(--px-sm); + padding-right: var(--px-sm); + } + + .sm\:bui-px-0\.5 { + padding-left: var(--bui-space-0_5); + padding-right: var(--bui-space-0_5); + } + + .sm\:bui-px-1 { + padding-left: var(--bui-space-1); + padding-right: var(--bui-space-1); + } + + .sm\:bui-px-1\.5 { + padding-left: var(--bui-space-1_5); + padding-right: var(--bui-space-1_5); + } + + .sm\:bui-px-2 { + padding-left: var(--bui-space-2); + padding-right: var(--bui-space-2); + } + + .sm\:bui-px-3 { + padding-left: var(--bui-space-3); + padding-right: var(--bui-space-3); + } + + .sm\:bui-px-4 { + padding-left: var(--bui-space-4); + padding-right: var(--bui-space-4); + } + + .sm\:bui-px-5 { + padding-left: var(--bui-space-5); + padding-right: var(--bui-space-5); + } + + .sm\:bui-px-6 { + padding-left: var(--bui-space-6); + padding-right: var(--bui-space-6); + } + + .sm\:bui-px-7 { + padding-left: var(--bui-space-7); + padding-right: var(--bui-space-7); + } + + .sm\:bui-px-8 { + padding-left: var(--bui-space-8); + padding-right: var(--bui-space-8); + } + + .sm\:bui-px-9 { + padding-left: var(--bui-space-9); + padding-right: var(--bui-space-9); + } + + .sm\:bui-px-10 { + padding-left: var(--bui-space-10); + padding-right: var(--bui-space-10); + } + + .sm\:bui-px-11 { + padding-left: var(--bui-space-11); + padding-right: var(--bui-space-11); + } + + .sm\:bui-px-12 { + padding-left: var(--bui-space-12); + padding-right: var(--bui-space-12); + } + + .sm\:bui-px-13 { + padding-left: var(--bui-space-13); + padding-right: var(--bui-space-13); + } + + .sm\:bui-px-14 { + padding-left: var(--bui-space-14); + padding-right: var(--bui-space-14); + } +} + +/* Breakpoint md */ +@media (min-width: 1024px) { + .md\:bui-px { + padding-left: var(--px-md); + padding-right: var(--px-md); + } + + .md\:bui-px-0\.5 { + padding-left: var(--bui-space-0_5); + padding-right: var(--bui-space-0_5); + } + + .md\:bui-px-1 { + padding-left: var(--bui-space-1); + padding-right: var(--bui-space-1); + } + + .md\:bui-px-1\.5 { + padding-left: var(--bui-space-1_5); + padding-right: var(--bui-space-1_5); + } + + .md\:bui-px-2 { + padding-left: var(--bui-space-2); + padding-right: var(--bui-space-2); + } + + .md\:bui-px-3 { + padding-left: var(--bui-space-3); + padding-right: var(--bui-space-3); + } + + .md\:bui-px-4 { + padding-left: var(--bui-space-4); + padding-right: var(--bui-space-4); + } + + .md\:bui-px-5 { + padding-left: var(--bui-space-5); + padding-right: var(--bui-space-5); + } + + .md\:bui-px-6 { + padding-left: var(--bui-space-6); + padding-right: var(--bui-space-6); + } + + .md\:bui-px-7 { + padding-left: var(--bui-space-7); + padding-right: var(--bui-space-7); + } + + .md\:bui-px-8 { + padding-left: var(--bui-space-8); + padding-right: var(--bui-space-8); + } + + .md\:bui-px-9 { + padding-left: var(--bui-space-9); + padding-right: var(--bui-space-9); + } + + .md\:bui-px-10 { + padding-left: var(--bui-space-10); + padding-right: var(--bui-space-10); + } + + .md\:bui-px-11 { + padding-left: var(--bui-space-11); + padding-right: var(--bui-space-11); + } + + .md\:bui-px-12 { + padding-left: var(--bui-space-12); + padding-right: var(--bui-space-12); + } + + .md\:bui-px-13 { + padding-left: var(--bui-space-13); + padding-right: var(--bui-space-13); + } + + .md\:bui-px-14 { + padding-left: var(--bui-space-14); + padding-right: var(--bui-space-14); + } +} + +/* Breakpoint lg */ +@media (min-width: 1280px) { + .lg\:bui-px { + padding-left: var(--px-lg); + padding-right: var(--px-lg); + } + + .lg\:bui-px-0\.5 { + padding-left: var(--bui-space-0_5); + padding-right: var(--bui-space-0_5); + } + + .lg\:bui-px-1 { + padding-left: var(--bui-space-1); + padding-right: var(--bui-space-1); + } + + .lg\:bui-px-1\.5 { + padding-left: var(--bui-space-1_5); + padding-right: var(--bui-space-1_5); + } + + .lg\:bui-px-2 { + padding-left: var(--bui-space-2); + padding-right: var(--bui-space-2); + } + + .lg\:bui-px-3 { + padding-left: var(--bui-space-3); + padding-right: var(--bui-space-3); + } + + .lg\:bui-px-4 { + padding-left: var(--bui-space-4); + padding-right: var(--bui-space-4); + } + + .lg\:bui-px-5 { + padding-left: var(--bui-space-5); + padding-right: var(--bui-space-5); + } + + .lg\:bui-px-6 { + padding-left: var(--bui-space-6); + padding-right: var(--bui-space-6); + } + + .lg\:bui-px-7 { + padding-left: var(--bui-space-7); + padding-right: var(--bui-space-7); + } + + .lg\:bui-px-8 { + padding-left: var(--bui-space-8); + padding-right: var(--bui-space-8); + } + + .lg\:bui-px-9 { + padding-left: var(--bui-space-9); + padding-right: var(--bui-space-9); + } + + .lg\:bui-px-10 { + padding-left: var(--bui-space-10); + padding-right: var(--bui-space-10); + } + + .lg\:bui-px-11 { + padding-left: var(--bui-space-11); + padding-right: var(--bui-space-11); + } + + .lg\:bui-px-12 { + padding-left: var(--bui-space-12); + padding-right: var(--bui-space-12); + } + + .lg\:bui-px-13 { + padding-left: var(--bui-space-13); + padding-right: var(--bui-space-13); + } + + .lg\:bui-px-14 { + padding-left: var(--bui-space-14); + padding-right: var(--bui-space-14); + } +} + +/* Breakpoint xl */ +@media (min-width: 1536px) { + .xl\:bui-px { + padding-left: var(--px-xl); + padding-right: var(--px-xl); + } + + .xl\:bui-px-0\.5 { + padding-left: var(--bui-space-0_5); + padding-right: var(--bui-space-0_5); + } + + .xl\:bui-px-1 { + padding-left: var(--bui-space-1); + padding-right: var(--bui-space-1); + } + + .xl\:bui-px-1\.5 { + padding-left: var(--bui-space-1_5); + padding-right: var(--bui-space-1_5); + } + + .xl\:bui-px-2 { + padding-left: var(--bui-space-2); + padding-right: var(--bui-space-2); + } + + .xl\:bui-px-3 { + padding-left: var(--bui-space-3); + padding-right: var(--bui-space-3); + } + + .xl\:bui-px-4 { + padding-left: var(--bui-space-4); + padding-right: var(--bui-space-4); + } + + .xl\:bui-px-5 { + padding-left: var(--bui-space-5); + padding-right: var(--bui-space-5); + } + + .xl\:bui-px-6 { + padding-left: var(--bui-space-6); + padding-right: var(--bui-space-6); + } + + .xl\:bui-px-7 { + padding-left: var(--bui-space-7); + padding-right: var(--bui-space-7); + } + + .xl\:bui-px-8 { + padding-left: var(--bui-space-8); + padding-right: var(--bui-space-8); + } + + .xl\:bui-px-9 { + padding-left: var(--bui-space-9); + padding-right: var(--bui-space-9); + } + + .xl\:bui-px-10 { + padding-left: var(--bui-space-10); + padding-right: var(--bui-space-10); + } + + .xl\:bui-px-11 { + padding-left: var(--bui-space-11); + padding-right: var(--bui-space-11); + } + + .xl\:bui-px-12 { + padding-left: var(--bui-space-12); + padding-right: var(--bui-space-12); + } + + .xl\:bui-px-13 { + padding-left: var(--bui-space-13); + padding-right: var(--bui-space-13); + } + + .xl\:bui-px-14 { + padding-left: var(--bui-space-14); + padding-right: var(--bui-space-14); + } +} diff --git a/packages/ui/src/css/utilities/py.css b/packages/ui/src/css/utilities/py.css new file mode 100644 index 0000000000..e3738df00c --- /dev/null +++ b/packages/ui/src/css/utilities/py.css @@ -0,0 +1,524 @@ +.bui-py { + padding-top: var(--py); + padding-bottom: var(--py); +} + +.bui-py-0\.5 { + padding-top: var(--bui-space-0_5); + padding-bottom: var(--bui-space-0_5); +} + +.bui-py-1 { + padding-top: var(--bui-space-1); + padding-bottom: var(--bui-space-1); +} + +.bui-py-1\.5 { + padding-top: var(--bui-space-1_5); + padding-bottom: var(--bui-space-1_5); +} + +.bui-py-2 { + padding-top: var(--bui-space-2); + padding-bottom: var(--bui-space-2); +} + +.bui-py-3 { + padding-top: var(--bui-space-3); + padding-bottom: var(--bui-space-3); +} + +.bui-py-4 { + padding-top: var(--bui-space-4); + padding-bottom: var(--bui-space-4); +} + +.bui-py-5 { + padding-top: var(--bui-space-5); + padding-bottom: var(--bui-space-5); +} + +.bui-py-6 { + padding-top: var(--bui-space-6); + padding-bottom: var(--bui-space-6); +} + +.bui-py-7 { + padding-top: var(--bui-space-7); + padding-bottom: var(--bui-space-7); +} + +.bui-py-8 { + padding-top: var(--bui-space-8); + padding-bottom: var(--bui-space-8); +} + +.bui-py-9 { + padding-top: var(--bui-space-9); + padding-bottom: var(--bui-space-9); +} + +.bui-py-10 { + padding-top: var(--bui-space-10); + padding-bottom: var(--bui-space-10); +} + +.bui-py-11 { + padding-top: var(--bui-space-11); + padding-bottom: var(--bui-space-11); +} + +.bui-py-12 { + padding-top: var(--bui-space-12); + padding-bottom: var(--bui-space-12); +} + +.bui-py-13 { + padding-top: var(--bui-space-13); + padding-bottom: var(--bui-space-13); +} + +.bui-py-14 { + padding-top: var(--bui-space-14); + padding-bottom: var(--bui-space-14); +} + +/* Breakpoint xs */ +@media (min-width: 640px) { + .xs\:bui-py { + padding-top: var(--py-xs); + padding-bottom: var(--py-xs); + } + + .xs\:bui-py-0\.5 { + padding-top: var(--bui-space-0_5); + padding-bottom: var(--bui-space-0_5); + } + + .xs\:bui-py-1 { + padding-top: var(--bui-space-1); + padding-bottom: var(--bui-space-1); + } + + .xs\:bui-py-1\.5 { + padding-top: var(--bui-space-1_5); + padding-bottom: var(--bui-space-1_5); + } + + .xs\:bui-py-2 { + padding-top: var(--bui-space-2); + padding-bottom: var(--bui-space-2); + } + + .xs\:bui-py-3 { + padding-top: var(--bui-space-3); + padding-bottom: var(--bui-space-3); + } + + .xs\:bui-py-4 { + padding-top: var(--bui-space-4); + padding-bottom: var(--bui-space-4); + } + + .xs\:bui-py-5 { + padding-top: var(--bui-space-5); + padding-bottom: var(--bui-space-5); + } + + .xs\:bui-py-6 { + padding-top: var(--bui-space-6); + padding-bottom: var(--bui-space-6); + } + + .xs\:bui-py-7 { + padding-top: var(--bui-space-7); + padding-bottom: var(--bui-space-7); + } + + .xs\:bui-py-8 { + padding-top: var(--bui-space-8); + padding-bottom: var(--bui-space-8); + } + + .xs\:bui-py-9 { + padding-top: var(--bui-space-9); + padding-bottom: var(--bui-space-9); + } + + .xs\:bui-py-10 { + padding-top: var(--bui-space-10); + padding-bottom: var(--bui-space-10); + } + + .xs\:bui-py-11 { + padding-top: var(--bui-space-11); + padding-bottom: var(--bui-space-11); + } + + .xs\:bui-py-12 { + padding-top: var(--bui-space-12); + padding-bottom: var(--bui-space-12); + } + + .xs\:bui-py-13 { + padding-top: var(--bui-space-13); + padding-bottom: var(--bui-space-13); + } + + .xs\:bui-py-14 { + padding-top: var(--bui-space-14); + padding-bottom: var(--bui-space-14); + } +} + +/* Breakpoint sm */ +@media (min-width: 768px) { + .sm\:bui-py { + padding-top: var(--py-sm); + padding-bottom: var(--py-sm); + } + + .sm\:bui-py-0\.5 { + padding-top: var(--bui-space-0_5); + padding-bottom: var(--bui-space-0_5); + } + + .sm\:bui-py-1 { + padding-top: var(--bui-space-1); + padding-bottom: var(--bui-space-1); + } + + .sm\:bui-py-1\.5 { + padding-top: var(--bui-space-1_5); + padding-bottom: var(--bui-space-1_5); + } + + .sm\:bui-py-2 { + padding-top: var(--bui-space-2); + padding-bottom: var(--bui-space-2); + } + + .sm\:bui-py-3 { + padding-top: var(--bui-space-3); + padding-bottom: var(--bui-space-3); + } + + .sm\:bui-py-4 { + padding-top: var(--bui-space-4); + padding-bottom: var(--bui-space-4); + } + + .sm\:bui-py-5 { + padding-top: var(--bui-space-5); + padding-bottom: var(--bui-space-5); + } + + .sm\:bui-py-6 { + padding-top: var(--bui-space-6); + padding-bottom: var(--bui-space-6); + } + + .sm\:bui-py-7 { + padding-top: var(--bui-space-7); + padding-bottom: var(--bui-space-7); + } + + .sm\:bui-py-8 { + padding-top: var(--bui-space-8); + padding-bottom: var(--bui-space-8); + } + + .sm\:bui-py-9 { + padding-top: var(--bui-space-9); + padding-bottom: var(--bui-space-9); + } + + .sm\:bui-py-10 { + padding-top: var(--bui-space-10); + padding-bottom: var(--bui-space-10); + } + + .sm\:bui-py-11 { + padding-top: var(--bui-space-11); + padding-bottom: var(--bui-space-11); + } + + .sm\:bui-py-12 { + padding-top: var(--bui-space-12); + padding-bottom: var(--bui-space-12); + } + + .sm\:bui-py-13 { + padding-top: var(--bui-space-13); + padding-bottom: var(--bui-space-13); + } + + .sm\:bui-py-14 { + padding-top: var(--bui-space-14); + padding-bottom: var(--bui-space-14); + } +} + +/* Breakpoint md */ +@media (min-width: 1024px) { + .md\:bui-py { + padding-top: var(--py-md); + padding-bottom: var(--py-md); + } + + .md\:bui-py-0\.5 { + padding-top: var(--bui-space-0_5); + padding-bottom: var(--bui-space-0_5); + } + + .md\:bui-py-1 { + padding-top: var(--bui-space-1); + padding-bottom: var(--bui-space-1); + } + + .md\:bui-py-1\.5 { + padding-top: var(--bui-space-1_5); + padding-bottom: var(--bui-space-1_5); + } + + .md\:bui-py-2 { + padding-top: var(--bui-space-2); + padding-bottom: var(--bui-space-2); + } + + .md\:bui-py-3 { + padding-top: var(--bui-space-3); + padding-bottom: var(--bui-space-3); + } + + .md\:bui-py-4 { + padding-top: var(--bui-space-4); + padding-bottom: var(--bui-space-4); + } + + .md\:bui-py-5 { + padding-top: var(--bui-space-5); + padding-bottom: var(--bui-space-5); + } + + .md\:bui-py-6 { + padding-top: var(--bui-space-6); + padding-bottom: var(--bui-space-6); + } + + .md\:bui-py-7 { + padding-top: var(--bui-space-7); + padding-bottom: var(--bui-space-7); + } + + .md\:bui-py-8 { + padding-top: var(--bui-space-8); + padding-bottom: var(--bui-space-8); + } + + .md\:bui-py-9 { + padding-top: var(--bui-space-9); + padding-bottom: var(--bui-space-9); + } + + .md\:bui-py-10 { + padding-top: var(--bui-space-10); + padding-bottom: var(--bui-space-10); + } + + .md\:bui-py-11 { + padding-top: var(--bui-space-11); + padding-bottom: var(--bui-space-11); + } + + .md\:bui-py-12 { + padding-top: var(--bui-space-12); + padding-bottom: var(--bui-space-12); + } + + .md\:bui-py-13 { + padding-top: var(--bui-space-13); + padding-bottom: var(--bui-space-13); + } + + .md\:bui-py-14 { + padding-top: var(--bui-space-14); + padding-bottom: var(--bui-space-14); + } +} + +/* Breakpoint lg */ +@media (min-width: 1280px) { + .lg\:bui-py { + padding-top: var(--py-lg); + padding-bottom: var(--py-lg); + } + + .lg\:bui-py-0\.5 { + padding-top: var(--bui-space-0_5); + padding-bottom: var(--bui-space-0_5); + } + + .lg\:bui-py-1 { + padding-top: var(--bui-space-1); + padding-bottom: var(--bui-space-1); + } + + .lg\:bui-py-1\.5 { + padding-top: var(--bui-space-1_5); + padding-bottom: var(--bui-space-1_5); + } + + .lg\:bui-py-2 { + padding-top: var(--bui-space-2); + padding-bottom: var(--bui-space-2); + } + + .lg\:bui-py-3 { + padding-top: var(--bui-space-3); + padding-bottom: var(--bui-space-3); + } + + .lg\:bui-py-4 { + padding-top: var(--bui-space-4); + padding-bottom: var(--bui-space-4); + } + + .lg\:bui-py-5 { + padding-top: var(--bui-space-5); + padding-bottom: var(--bui-space-5); + } + + .lg\:bui-py-6 { + padding-top: var(--bui-space-6); + padding-bottom: var(--bui-space-6); + } + + .lg\:bui-py-7 { + padding-top: var(--bui-space-7); + padding-bottom: var(--bui-space-7); + } + + .lg\:bui-py-8 { + padding-top: var(--bui-space-8); + padding-bottom: var(--bui-space-8); + } + + .lg\:bui-py-9 { + padding-top: var(--bui-space-9); + padding-bottom: var(--bui-space-9); + } + + .lg\:bui-py-10 { + padding-top: var(--bui-space-10); + padding-bottom: var(--bui-space-10); + } + + .lg\:bui-py-11 { + padding-top: var(--bui-space-11); + padding-bottom: var(--bui-space-11); + } + + .lg\:bui-py-12 { + padding-top: var(--bui-space-12); + padding-bottom: var(--bui-space-12); + } + + .lg\:bui-py-13 { + padding-top: var(--bui-space-13); + padding-bottom: var(--bui-space-13); + } + + .lg\:bui-py-14 { + padding-top: var(--bui-space-14); + padding-bottom: var(--bui-space-14); + } +} + +/* Breakpoint xl */ +@media (min-width: 1536px) { + .xl\:bui-py { + padding-top: var(--py-xl); + padding-bottom: var(--py-xl); + } + + .xl\:bui-py-0\.5 { + padding-top: var(--bui-space-0_5); + padding-bottom: var(--bui-space-0_5); + } + + .xl\:bui-py-1 { + padding-top: var(--bui-space-1); + padding-bottom: var(--bui-space-1); + } + + .xl\:bui-py-1\.5 { + padding-top: var(--bui-space-1_5); + padding-bottom: var(--bui-space-1_5); + } + + .xl\:bui-py-2 { + padding-top: var(--bui-space-2); + padding-bottom: var(--bui-space-2); + } + + .xl\:bui-py-3 { + padding-top: var(--bui-space-3); + padding-bottom: var(--bui-space-3); + } + + .xl\:bui-py-4 { + padding-top: var(--bui-space-4); + padding-bottom: var(--bui-space-4); + } + + .xl\:bui-py-5 { + padding-top: var(--bui-space-5); + padding-bottom: var(--bui-space-5); + } + + .xl\:bui-py-6 { + padding-top: var(--bui-space-6); + padding-bottom: var(--bui-space-6); + } + + .xl\:bui-py-7 { + padding-top: var(--bui-space-7); + padding-bottom: var(--bui-space-7); + } + + .xl\:bui-py-8 { + padding-top: var(--bui-space-8); + padding-bottom: var(--bui-space-8); + } + + .xl\:bui-py-9 { + padding-top: var(--bui-space-9); + padding-bottom: var(--bui-space-9); + } + + .xl\:bui-py-10 { + padding-top: var(--bui-space-10); + padding-bottom: var(--bui-space-10); + } + + .xl\:bui-py-11 { + padding-top: var(--bui-space-11); + padding-bottom: var(--bui-space-11); + } + + .xl\:bui-py-12 { + padding-top: var(--bui-space-12); + padding-bottom: var(--bui-space-12); + } + + .xl\:bui-py-13 { + padding-top: var(--bui-space-13); + padding-bottom: var(--bui-space-13); + } + + .xl\:bui-py-14 { + padding-top: var(--bui-space-14); + padding-bottom: var(--bui-space-14); + } +} diff --git a/packages/canon/src/css/utilities/sm.css b/packages/ui/src/css/utilities/sm.css similarity index 52% rename from packages/canon/src/css/utilities/sm.css rename to packages/ui/src/css/utilities/sm.css index 01603f0218..61baaa6b0d 100644 --- a/packages/canon/src/css/utilities/sm.css +++ b/packages/ui/src/css/utilities/sm.css @@ -16,176 +16,176 @@ @layer utilities { @media (min-width: 640px) { - .cu-sm-block { + .bui-sm-block { display: block; } - .cu-sm-border-base { - border-color: var(--canon-border); + .bui-sm-border-base { + border-color: var(--bui-border); border-width: 1px; border-style: solid; } - .cu-sm-border-error { - border-color: var(--canon-border-danger); + .bui-sm-border-error { + border-color: var(--bui-border-danger); border-width: 1px; border-style: solid; } - .cu-sm-border-none { + .bui-sm-border-none { border-color: transparent; border-width: 0; } - .cu-sm-border-selected { - border-color: var(--canon-border-pressed); + .bui-sm-border-selected { + border-color: var(--bui-border-pressed); border-width: 1px; border-style: solid; } - .cu-sm-border-warning { - border-color: var(--canon-border-warning); + .bui-sm-border-warning { + border-color: var(--bui-border-warning); border-width: 1px; border-style: solid; } - .cu-sm-flex { + .bui-sm-flex { display: flex; } - .cu-sm-flex-col { + .bui-sm-flex-col { flex-direction: column; } - .cu-sm-flex-nowrap { + .bui-sm-flex-nowrap { flex-wrap: nowrap; } - .cu-sm-flex-row { + .bui-sm-flex-row { flex-direction: row; } - .cu-sm-flex-wrap { + .bui-sm-flex-wrap { flex-wrap: wrap; } - .cu-sm-flex-wrap-reverse { + .bui-sm-flex-wrap-reverse { flex-wrap: wrap-reverse; } - .cu-sm-gap-2xl { - gap: var(--canon-space-12); + .bui-sm-gap-2xl { + gap: var(--bui-space-12); } - .cu-sm-gap-lg { - gap: var(--canon-space-8); + .bui-sm-gap-lg { + gap: var(--bui-space-8); } - .cu-sm-gap-md { - gap: var(--canon-space-6); + .bui-sm-gap-md { + gap: var(--bui-space-6); } - .cu-sm-gap-none { + .bui-sm-gap-none { gap: 0; } - .cu-sm-gap-sm { - gap: var(--canon-space-4); + .bui-sm-gap-sm { + gap: var(--bui-space-4); } - .cu-sm-gap-xl { - gap: var(--canon-space-10); + .bui-sm-gap-xl { + gap: var(--bui-space-10); } - .cu-sm-gap-xs { - gap: var(--canon-space-3); + .bui-sm-gap-xs { + gap: var(--bui-space-3); } - .cu-sm-hidden { + .bui-sm-hidden { display: none; } - .cu-sm-inline { + .bui-sm-inline { display: inline; } - .cu-sm-inline-block { + .bui-sm-inline-block { display: inline-block; } - .cu-sm-items-center { + .bui-sm-items-center { align-items: center; } - .cu-sm-items-end { + .bui-sm-items-end { align-items: flex-end; } - .cu-sm-items-start { + .bui-sm-items-start { align-items: flex-start; } - .cu-sm-items-stretch { + .bui-sm-items-stretch { align-items: stretch; } - .cu-sm-justify-around { + .bui-sm-justify-around { justify-content: space-around; } - .cu-sm-justify-between { + .bui-sm-justify-between { justify-content: space-between; } - .cu-sm-justify-center { + .bui-sm-justify-center { justify-content: center; } - .cu-sm-justify-end { + .bui-sm-justify-end { justify-content: flex-end; } - .cu-sm-justify-start { + .bui-sm-justify-start { justify-content: flex-start; } - .cu-sm-justify-stretch { + .bui-sm-justify-stretch { justify-content: stretch; } - .cu-sm-rounded-2xl { - border-radius: var(--canon-radius-full); + .bui-sm-rounded-2xl { + border-radius: var(--bui-radius-full); } - .cu-sm-rounded-2xs { - border-radius: var(--canon-radius-1); + .bui-sm-rounded-2xs { + border-radius: var(--bui-radius-1); } - .cu-sm-rounded-lg { - border-radius: var(--canon-radius-5); + .bui-sm-rounded-lg { + border-radius: var(--bui-radius-5); } - .cu-sm-rounded-md { - border-radius: var(--canon-radius-4); + .bui-sm-rounded-md { + border-radius: var(--bui-radius-4); } - .cu-sm-rounded-none { + .bui-sm-rounded-none { border-radius: 0; } - .cu-sm-rounded-sm { - border-radius: var(--canon-radius-3); + .bui-sm-rounded-sm { + border-radius: var(--bui-radius-3); } - .cu-sm-rounded-xl { - border-radius: var(--canon-radius-6); + .bui-sm-rounded-xl { + border-radius: var(--bui-radius-6); } - .cu-sm-rounded-xs { - border-radius: var(--canon-radius-2); + .bui-sm-rounded-xs { + border-radius: var(--bui-radius-2); } - .cu-sm-wrap-reverse { + .bui-sm-wrap-reverse { flex-wrap: wrap-reverse; } } diff --git a/packages/canon/src/css/utilities/width.css b/packages/ui/src/css/utilities/width.css similarity index 74% rename from packages/canon/src/css/utilities/width.css rename to packages/ui/src/css/utilities/width.css index f4f10c9416..6dad08814a 100644 --- a/packages/canon/src/css/utilities/width.css +++ b/packages/ui/src/css/utilities/width.css @@ -1,86 +1,86 @@ -.cu-w { +.bui-w { width: var(--width); } -.cu-min-w { +.bui-min-w { min-width: var(--min-width); } -.cu-max-w { +.bui-max-w { max-width: var(--max-width); } /* Breakpoint xs */ @media (min-width: 640px) { - .xs\:cu-w { + .xs\:bui-w { width: var(--width); } - .xs\:cu-min-w { + .xs\:bui-min-w { min-width: var(--min-width); } - .xs\:cu-max-w { + .xs\:bui-max-w { max-width: var(--max-width); } } /* Breakpoint sm */ @media (min-width: 768px) { - .sm\:cu-w { + .sm\:bui-w { width: var(--width); } - .sm\:cu-min-w { + .sm\:bui-min-w { min-width: var(--min-width); } - .sm\:cu-max-w { + .sm\:bui-max-w { max-width: var(--max-width); } } /* Breakpoint md */ @media (min-width: 1024px) { - .md\:cu-w { + .md\:bui-w { width: var(--width); } - .md\:cu-min-w { + .md\:bui-min-w { min-width: var(--min-width); } - .md\:cu-max-w { + .md\:bui-max-w { max-width: var(--max-width); } } /* Breakpoint lg */ @media (min-width: 1280px) { - .lg\:cu-w { + .lg\:bui-w { width: var(--width); } - .lg\:cu-min-w { + .lg\:bui-min-w { min-width: var(--min-width); } - .lg\:cu-max-w { + .lg\:bui-max-w { max-width: var(--max-width); } } /* Breakpoint xl */ @media (min-width: 1536px) { - .xl\:cu-w { + .xl\:bui-w { width: var(--width); } - .xl\:cu-min-w { + .xl\:bui-min-w { min-width: var(--min-width); } - .xl\:cu-max-w { + .xl\:bui-max-w { max-width: var(--max-width); } } diff --git a/packages/canon/src/css/utilities/xs.css b/packages/ui/src/css/utilities/xs.css similarity index 58% rename from packages/canon/src/css/utilities/xs.css rename to packages/ui/src/css/utilities/xs.css index 5470355fdb..27adb7c7e4 100644 --- a/packages/canon/src/css/utilities/xs.css +++ b/packages/ui/src/css/utilities/xs.css @@ -15,148 +15,148 @@ */ @layer utilities { - .cu-block { + .bui-block { display: block; } - .cu-border-base { - border-color: var(--canon-border); + .bui-border-base { + border-color: var(--bui-border); border-width: 1px; border-style: solid; } - .cu-border-error { - border-color: var(--canon-border-danger); + .bui-border-error { + border-color: var(--bui-border-danger); border-width: 1px; border-style: solid; } - .cu-border-none { + .bui-border-none { border-color: transparent; border-width: 0; } - .cu-border-selected { - border-color: var(--canon-border-pressed); + .bui-border-selected { + border-color: var(--bui-border-pressed); border-width: 1px; border-style: solid; } - .cu-border-warning { - border-color: var(--canon-border-warning); + .bui-border-warning { + border-color: var(--bui-border-warning); border-width: 1px; border-style: solid; } - .cu-flex { + .bui-flex { display: flex; } - .cu-flex-col { + .bui-flex-col { flex-direction: column; } - .cu-flex-nowrap { + .bui-flex-nowrap { flex-wrap: nowrap; } - .cu-flex-row { + .bui-flex-row { flex-direction: row; } - .cu-flex-wrap { + .bui-flex-wrap { flex-wrap: wrap; } - .cu-flex-wrap-reverse { + .bui-flex-wrap-reverse { flex-wrap: wrap-reverse; } - .cu-hidden { + .bui-hidden { display: none; } - .cu-inline { + .bui-inline { display: inline; } - .cu-inline-block { + .bui-inline-block { display: inline-block; } - .cu-items-center { + .bui-items-center { align-items: center; } - .cu-items-end { + .bui-items-end { align-items: flex-end; } - .cu-items-start { + .bui-items-start { align-items: flex-start; } - .cu-items-stretch { + .bui-items-stretch { align-items: stretch; } - .cu-justify-around { + .bui-justify-around { justify-content: space-around; } - .cu-justify-between { + .bui-justify-between { justify-content: space-between; } - .cu-justify-center { + .bui-justify-center { justify-content: center; } - .cu-justify-end { + .bui-justify-end { justify-content: flex-end; } - .cu-justify-start { + .bui-justify-start { justify-content: flex-start; } - .cu-justify-stretch { + .bui-justify-stretch { justify-content: stretch; } - .cu-rounded-2xl { - border-radius: var(--canon-radius-full); + .bui-rounded-2xl { + border-radius: var(--bui-radius-full); } - .cu-rounded-2xs { - border-radius: var(--canon-radius-1); + .bui-rounded-2xs { + border-radius: var(--bui-radius-1); } - .cu-rounded-lg { - border-radius: var(--canon-radius-5); + .bui-rounded-lg { + border-radius: var(--bui-radius-5); } - .cu-rounded-md { - border-radius: var(--canon-radius-4); + .bui-rounded-md { + border-radius: var(--bui-radius-4); } - .cu-rounded-none { + .bui-rounded-none { border-radius: 0; } - .cu-rounded-sm { - border-radius: var(--canon-radius-3); + .bui-rounded-sm { + border-radius: var(--bui-radius-3); } - .cu-rounded-xl { - border-radius: var(--canon-radius-6); + .bui-rounded-xl { + border-radius: var(--bui-radius-6); } - .cu-rounded-xs { - border-radius: var(--canon-radius-2); + .bui-rounded-xs { + border-radius: var(--bui-radius-2); } - .cu-wrap-reverse { + .bui-wrap-reverse { flex-wrap: wrap-reverse; } } diff --git a/packages/canon/src/hooks/useBreakpoint.ts b/packages/ui/src/hooks/useBreakpoint.ts similarity index 100% rename from packages/canon/src/hooks/useBreakpoint.ts rename to packages/ui/src/hooks/useBreakpoint.ts diff --git a/packages/canon/src/hooks/useIsomorphicLayoutEffect.ts b/packages/ui/src/hooks/useIsomorphicLayoutEffect.ts similarity index 100% rename from packages/canon/src/hooks/useIsomorphicLayoutEffect.ts rename to packages/ui/src/hooks/useIsomorphicLayoutEffect.ts diff --git a/packages/canon/src/hooks/useMediaQuery.ts b/packages/ui/src/hooks/useMediaQuery.ts similarity index 100% rename from packages/canon/src/hooks/useMediaQuery.ts rename to packages/ui/src/hooks/useMediaQuery.ts diff --git a/packages/ui/src/hooks/useStyles.ts b/packages/ui/src/hooks/useStyles.ts new file mode 100644 index 0000000000..19f9823236 --- /dev/null +++ b/packages/ui/src/hooks/useStyles.ts @@ -0,0 +1,92 @@ +/* + * 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 { useBreakpoint, breakpoints } from './useBreakpoint'; +import { componentDefinitions } from '../utils/componentDefinitions'; +import type { ComponentDefinitionName, ComponentClassNames } from '../types'; + +/** + * Resolve a responsive value based on the current breakpoint + * @param value - The responsive value (string or object with breakpoint keys) + * @param breakpoint - The current breakpoint + * @returns The resolved value for the current breakpoint + */ +function resolveResponsiveValue( + value: string | Record, + breakpoint: string, +): string | undefined { + if (typeof value === 'string') { + return value; + } + + if (typeof value === 'object' && value !== null) { + const index = breakpoints.findIndex(b => b.id === breakpoint); + + // Look for value at current breakpoint or smaller + for (let i = index; i >= 0; i--) { + if (value[breakpoints[i].id]) { + return value[breakpoints[i].id]; + } + } + + // If no value found, check from smallest breakpoint up + for (let i = 0; i < breakpoints.length; i++) { + if (value[breakpoints[i].id]) { + return value[breakpoints[i].id]; + } + } + } + + return undefined; +} + +/** + * React hook to get class names and data attributes for a component with responsive support + * @param componentName - The name of the component + * @param props - Object with prop values (can be responsive) + * @returns Object with classNames and dataAttributes + */ +export function useStyles( + componentName: T, + props: Record = {}, +): { + classNames: ComponentClassNames; + dataAttributes: Record; + resolvedProps: Record; +} { + const { breakpoint } = useBreakpoint(); + const classNames = componentDefinitions[componentName] + .classNames as ComponentClassNames; + + // Resolve responsive values and generate data attributes + const dataAttributes: Record = {}; + const resolvedProps: Record = {}; + + for (const [key, value] of Object.entries(props)) { + if (value !== undefined && value !== null) { + const resolvedValue = resolveResponsiveValue(value, breakpoint); + if (resolvedValue !== undefined) { + resolvedProps[key] = resolvedValue; + dataAttributes[`data-${key}`] = resolvedValue; + } + } + } + + return { + classNames, + dataAttributes, + resolvedProps, // Also return resolved props for convenience + }; +} diff --git a/packages/canon/src/index.ts b/packages/ui/src/index.ts similarity index 73% rename from packages/canon/src/index.ts rename to packages/ui/src/index.ts index 7677a539c7..583ea365c8 100644 --- a/packages/canon/src/index.ts +++ b/packages/ui/src/index.ts @@ -28,24 +28,33 @@ export * from './components/Box'; export * from './components/Grid'; export * from './components/Flex'; export * from './components/Container'; -export * from './components/Text'; -export * from './components/Heading'; // UI components export * from './components/Avatar'; export * from './components/Button'; +export * from './components/Card'; export * from './components/Collapsible'; -export * from './components/DataTable'; +export * from './components/FieldLabel'; +export * from './components/Header'; +export * from './components/HeaderPage'; export * from './components/Icon'; -export * from './components/IconButton'; +export * from './components/ButtonIcon'; +export * from './components/ButtonLink'; export * from './components/Checkbox'; +export * from './components/RadioGroup'; export * from './components/Table'; +export * from './components/TablePagination'; +export * from './components/Tabs'; +export * from './components/Text'; export * from './components/TextField'; export * from './components/Tooltip'; export * from './components/Menu'; export * from './components/ScrollArea'; +export * from './components/SearchField'; export * from './components/Link'; export * from './components/Select'; +export * from './components/Skeleton'; +export * from './components/Switch'; // Types export * from './types'; @@ -53,3 +62,6 @@ export * from './props'; // Hooks export { useBreakpoint } from './hooks/useBreakpoint'; + +// Component Definitions +export * from './utils/componentDefinitions'; diff --git a/packages/canon/src/props/display.props.ts b/packages/ui/src/props/display.props.ts similarity index 97% rename from packages/canon/src/props/display.props.ts rename to packages/ui/src/props/display.props.ts index a71557f05b..4a18aea910 100644 --- a/packages/canon/src/props/display.props.ts +++ b/packages/ui/src/props/display.props.ts @@ -22,7 +22,7 @@ const displayValues = ['none', 'inline', 'inline-block', 'block'] as const; const displayPropDefs = { display: { type: 'enum', - className: 'cu-display', + className: 'bui-display', values: displayValues, responsive: true, }, diff --git a/packages/canon/src/props/gap-props.ts b/packages/ui/src/props/gap-props.ts similarity index 97% rename from packages/canon/src/props/gap-props.ts rename to packages/ui/src/props/gap-props.ts index 68a03a9f51..2701473004 100644 --- a/packages/canon/src/props/gap-props.ts +++ b/packages/ui/src/props/gap-props.ts @@ -21,7 +21,7 @@ import { spacingValues } from './spacing.props'; const gapPropDefs = { gap: { type: 'enum | string', - className: 'cu-gap', + className: 'bui-gap', customProperties: ['--gap'], values: spacingValues, responsive: true, diff --git a/packages/canon/src/props/height.props.ts b/packages/ui/src/props/height.props.ts similarity index 93% rename from packages/canon/src/props/height.props.ts rename to packages/ui/src/props/height.props.ts index b42f965e54..d6195c1fae 100644 --- a/packages/canon/src/props/height.props.ts +++ b/packages/ui/src/props/height.props.ts @@ -19,19 +19,19 @@ import type { PropDef, GetPropDefTypes } from './prop-def'; const heightPropDefs = { height: { type: 'string', - className: 'cu-h', + className: 'bui-h', customProperties: ['--height'], responsive: true, }, minHeight: { type: 'string', - className: 'cu-min-h', + className: 'bui-min-h', customProperties: ['--min-height'], responsive: true, }, maxHeight: { type: 'string', - className: 'cu-max-h', + className: 'bui-max-h', customProperties: ['--max-height'], responsive: true, }, diff --git a/packages/canon/src/props/index.ts b/packages/ui/src/props/index.ts similarity index 100% rename from packages/canon/src/props/index.ts rename to packages/ui/src/props/index.ts diff --git a/packages/canon/src/props/margin.props.ts b/packages/ui/src/props/margin.props.ts similarity index 92% rename from packages/canon/src/props/margin.props.ts rename to packages/ui/src/props/margin.props.ts index 12cb0989e2..31ebf54747 100644 --- a/packages/canon/src/props/margin.props.ts +++ b/packages/ui/src/props/margin.props.ts @@ -21,49 +21,49 @@ const marginPropDefs = (spacingValues: string[]) => m: { type: 'enum | string', values: spacingValues, - className: 'cu-m', + className: 'bui-m', customProperties: ['--m'], responsive: true, }, mx: { type: 'enum | string', values: spacingValues, - className: 'cu-mx', + className: 'bui-mx', customProperties: ['--mx'], responsive: true, }, my: { type: 'enum | string', values: spacingValues, - className: 'cu-my', + className: 'bui-my', customProperties: ['--my'], responsive: true, }, mt: { type: 'enum | string', values: spacingValues, - className: 'cu-mt', + className: 'bui-mt', customProperties: ['--mt'], responsive: true, }, mr: { type: 'enum | string', values: spacingValues, - className: 'cu-mr', + className: 'bui-mr', customProperties: ['--mr'], responsive: true, }, mb: { type: 'enum | string', values: spacingValues, - className: 'cu-mb', + className: 'bui-mb', customProperties: ['--mb'], responsive: true, }, ml: { type: 'enum | string', values: spacingValues, - className: 'cu-ml', + className: 'bui-ml', customProperties: ['--ml'], responsive: true, }, diff --git a/packages/canon/src/props/padding.props.ts b/packages/ui/src/props/padding.props.ts similarity index 92% rename from packages/canon/src/props/padding.props.ts rename to packages/ui/src/props/padding.props.ts index ea42fcd4f1..16d13cf957 100644 --- a/packages/canon/src/props/padding.props.ts +++ b/packages/ui/src/props/padding.props.ts @@ -21,49 +21,49 @@ const paddingPropDefs = (spacingValues: string[]) => ({ p: { type: 'enum | string', - className: 'cu-p', + className: 'bui-p', customProperties: ['--p'], values: spacingValues, responsive: true, }, px: { type: 'enum | string', - className: 'cu-px', + className: 'bui-px', customProperties: ['--px'], values: spacingValues, responsive: true, }, py: { type: 'enum | string', - className: 'cu-py', + className: 'bui-py', customProperties: ['--py'], values: spacingValues, responsive: true, }, pt: { type: 'enum | string', - className: 'cu-pt', + className: 'bui-pt', customProperties: ['--pt'], values: spacingValues, responsive: true, }, pr: { type: 'enum | string', - className: 'cu-pr', + className: 'bui-pr', customProperties: ['--pr'], values: spacingValues, responsive: true, }, pb: { type: 'enum | string', - className: 'cu-pb', + className: 'bui-pb', customProperties: ['--pb'], values: spacingValues, responsive: true, }, pl: { type: 'enum | string', - className: 'cu-pl', + className: 'bui-pl', customProperties: ['--pl'], values: spacingValues, responsive: true, diff --git a/packages/canon/src/props/position.props.ts b/packages/ui/src/props/position.props.ts similarity index 97% rename from packages/canon/src/props/position.props.ts rename to packages/ui/src/props/position.props.ts index 048e3b13ce..027b5e2a63 100644 --- a/packages/canon/src/props/position.props.ts +++ b/packages/ui/src/props/position.props.ts @@ -28,7 +28,7 @@ const positionValues = [ const positionPropDefs = { position: { type: 'enum', - className: 'cu-position', + className: 'bui-position', values: positionValues, responsive: true, }, diff --git a/packages/canon/src/props/prop-def.ts b/packages/ui/src/props/prop-def.ts similarity index 100% rename from packages/canon/src/props/prop-def.ts rename to packages/ui/src/props/prop-def.ts diff --git a/packages/canon/src/props/spacing.props.ts b/packages/ui/src/props/spacing.props.ts similarity index 100% rename from packages/canon/src/props/spacing.props.ts rename to packages/ui/src/props/spacing.props.ts diff --git a/packages/canon/src/props/width.props.ts b/packages/ui/src/props/width.props.ts similarity index 93% rename from packages/canon/src/props/width.props.ts rename to packages/ui/src/props/width.props.ts index fdd8f3fdd4..019ec978ba 100644 --- a/packages/canon/src/props/width.props.ts +++ b/packages/ui/src/props/width.props.ts @@ -19,19 +19,19 @@ import type { GetPropDefTypes, PropDef } from './prop-def'; const widthPropDefs = { width: { type: 'string', - className: 'cu-w', + className: 'bui-w', customProperties: ['--width'], responsive: true, }, minWidth: { type: 'string', - className: 'cu-min-w', + className: 'bui-min-w', customProperties: ['--min-width'], responsive: true, }, maxWidth: { type: 'string', - className: 'cu-max-w', + className: 'bui-max-w', customProperties: ['--max-width'], responsive: true, }, diff --git a/packages/canon/src/types.ts b/packages/ui/src/types.ts similarity index 65% rename from packages/canon/src/types.ts rename to packages/ui/src/types.ts index ee5d954576..d0011bfe8a 100644 --- a/packages/canon/src/types.ts +++ b/packages/ui/src/types.ts @@ -14,24 +14,7 @@ * limitations under the License. */ -/** @public */ -export type AsProps = - | 'div' - | 'span' - | 'p' - | 'article' - | 'section' - | 'main' - | 'nav' - | 'aside' - | 'ul' - | 'ol' - | 'li' - | 'details' - | 'summary' - | 'dd' - | 'dl' - | 'dt'; +import { componentDefinitions } from './utils/componentDefinitions'; /** @public */ export type Breakpoint = 'initial' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'; @@ -115,6 +98,26 @@ export interface SpaceProps { py?: Responsive; } +/** @public */ +export type TextVariants = + | 'title-large' + | 'title-medium' + | 'title-small' + | 'title-x-small' + | 'body-large' + | 'body-medium' + | 'body-small' + | 'body-x-small'; + +/** @public */ +export type TextColors = 'primary' | 'secondary'; + +/** @public */ +export type TextColorStatus = 'danger' | 'warning' | 'success'; + +/** @public */ +export type TextWeights = 'regular' | 'bold'; + /** @public */ export interface UtilityProps extends SpaceProps { alignItems?: Responsive; @@ -131,3 +134,43 @@ export interface UtilityProps extends SpaceProps { justifyContent?: Responsive; rowSpan?: Responsive; } + +/** + * Base type for the component styles structure + * @public + */ +export type ClassNamesMap = Record; + +/** + * Base type for the component styles structure + * @public + */ +export type DataAttributeValues = readonly (string | number | boolean)[]; + +/** + * Base type for the component styles structure + * @public + */ +export type DataAttributesMap = Record; + +/** + * Base type for the component styles structure + * @public + */ +export interface ComponentDefinition { + classNames: ClassNamesMap; + dataAttributes?: DataAttributesMap; +} + +/** + * Type utilities for extracting information from the component styles + * @public + */ +export type ComponentDefinitionName = keyof typeof componentDefinitions; + +/** + * Helper type to extract class names for a component + * @public + */ +export type ComponentClassNames = + (typeof componentDefinitions)[T]['classNames']; diff --git a/packages/ui/src/utils/componentDefinitions.ts b/packages/ui/src/utils/componentDefinitions.ts new file mode 100644 index 0000000000..cda8b6d324 --- /dev/null +++ b/packages/ui/src/utils/componentDefinitions.ts @@ -0,0 +1,292 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../types'; + +/** + * Component definitions for the Backstage UI library + * @public + */ +export const componentDefinitions = { + Avatar: { + classNames: { + root: 'bui-AvatarRoot', + image: 'bui-AvatarImage', + fallback: 'bui-AvatarFallback', + }, + dataAttributes: { + size: ['small', 'medium', 'large'] as const, + }, + }, + Box: { + classNames: { + root: 'bui-Box', + }, + }, + Button: { + classNames: { + root: 'bui-Button', + }, + dataAttributes: { + size: ['small', 'medium', 'large'] as const, + variant: ['primary', 'secondary', 'tertiary'] as const, + }, + }, + ButtonIcon: { + classNames: { + root: 'bui-ButtonIcon', + }, + }, + ButtonLink: { + classNames: { + root: 'bui-ButtonLink', + }, + }, + Card: { + classNames: { + root: 'bui-Card', + header: 'bui-CardHeader', + body: 'bui-CardBody', + footer: 'bui-CardFooter', + }, + }, + Checkbox: { + classNames: { + root: 'bui-CheckboxRoot', + label: 'bui-CheckboxLabel', + indicator: 'bui-CheckboxIndicator', + }, + dataAttributes: { + checked: [true, false] as const, + }, + }, + Collapsible: { + classNames: { + root: 'bui-CollapsibleRoot', + trigger: 'bui-CollapsibleTrigger', + panel: 'bui-CollapsiblePanel', + }, + }, + Container: { + classNames: { + root: 'bui-Container', + }, + }, + FieldLabel: { + classNames: { + root: 'bui-FieldLabelWrapper', + label: 'bui-FieldLabel', + secondaryLabel: 'bui-FieldSecondaryLabel', + description: 'bui-FieldDescription', + }, + }, + Flex: { + classNames: { + root: 'bui-Flex', + }, + }, + Grid: { + classNames: { + root: 'bui-Grid', + item: 'bui-GridItem', + }, + }, + Header: { + classNames: { + toolbar: 'bui-HeaderToolbar', + toolbarWrapper: 'bui-HeaderToolbarWrapper', + toolbarContent: 'bui-HeaderToolbarContent', + toolbarControls: 'bui-HeaderToolbarControls', + toolbarIcon: 'bui-HeaderToolbarIcon', + toolbarName: 'bui-HeaderToolbarName', + tabsWrapper: 'bui-HeaderTabsWrapper', + }, + }, + HeaderPage: { + classNames: { + root: 'bui-HeaderPage', + content: 'bui-HeaderPageContent', + breadcrumbs: 'bui-HeaderPageBreadcrumbs', + tabsWrapper: 'bui-HeaderPageTabsWrapper', + controls: 'bui-HeaderPageControls', + }, + }, + Heading: { + classNames: { + root: 'bui-Heading', + }, + dataAttributes: { + variant: ['title1', 'title2', 'title3', 'subtitle'] as const, + color: ['primary', 'secondary', 'muted'] as const, + truncate: [true, false] as const, + }, + }, + Icon: { + classNames: { + root: 'bui-Icon', + }, + }, + Link: { + classNames: { + root: 'bui-Link', + }, + dataAttributes: { + variant: ['subtitle', 'body', 'caption', 'label'] as const, + weight: ['regular', 'bold'] as const, + }, + }, + List: { + classNames: { + root: 'bui-List', + row: 'bui-ListRow', + label: 'bui-ListLabel', + }, + }, + Menu: { + classNames: { + trigger: 'bui-MenuTrigger', + backdrop: 'bui-MenuBackdrop', + positioner: 'bui-MenuPositioner', + popup: 'bui-MenuPopup', + arrow: 'bui-MenuArrow', + item: 'bui-MenuItem', + group: 'bui-MenuGroup', + groupLabel: 'bui-MenuGroupLabel', + radioGroup: 'bui-MenuRadioGroup', + radioItem: 'bui-MenuRadioItem', + radioItemIndicator: 'bui-MenuRadioItemIndicator', + checkboxItem: 'bui-MenuCheckboxItem', + checkboxItemIndicator: 'bui-MenuCheckboxItemIndicator', + submenuTrigger: 'bui-MenuSubmenuTrigger', + separator: 'bui-MenuSeparator', + }, + }, + Popover: { + classNames: { + root: 'bui-Popover', + }, + }, + RadioGroup: { + classNames: { + root: 'bui-RadioGroup', + content: 'bui-RadioGroupContent', + radio: 'bui-Radio', + }, + }, + ScrollArea: { + classNames: { + root: 'bui-ScrollAreaRoot', + viewport: 'bui-ScrollAreaViewport', + scrollbar: 'bui-ScrollAreaScrollbar', + thumb: 'bui-ScrollAreaThumb', + }, + }, + SearchField: { + classNames: { + root: 'bui-SearchField', + clear: 'bui-InputClear', + }, + dataAttributes: { + startCollapsed: [true, false] as const, + }, + }, + Select: { + classNames: { + root: 'bui-Select', + trigger: 'bui-SelectTrigger', + value: 'bui-SelectValue', + icon: 'bui-SelectIcon', + list: 'bui-SelectList', + item: 'bui-SelectItem', + itemIndicator: 'bui-SelectItemIndicator', + itemLabel: 'bui-SelectItemLabel', + }, + dataAttributes: { + size: ['small', 'medium'] as const, + }, + }, + Skeleton: { + classNames: { + root: 'bui-Skeleton', + }, + }, + Switch: { + classNames: { + root: 'bui-Switch', + indicator: 'bui-SwitchIndicator', + }, + }, + Table: { + classNames: { + table: 'bui-Table', + header: 'bui-TableHeader', + body: 'bui-TableBody', + row: 'bui-TableRow', + head: 'bui-TableHead', + headSortButton: 'bui-TableHeadSortButton', + caption: 'bui-TableCaption', + cell: 'bui-TableCell', + cellContentWrapper: 'bui-TableCellContentWrapper', + cellContent: 'bui-TableCellContent', + cellIcon: 'bui-TableCellIcon', + cellProfileAvatar: 'bui-TableCellProfileAvatar', + cellProfileAvatarImage: 'bui-TableCellProfileAvatarImage', + cellProfileAvatarFallback: 'bui-TableCellProfileAvatarFallback', + cellProfileName: 'bui-TableCellProfileName', + cellProfileLink: 'bui-TableCellProfileLink', + }, + }, + Tabs: { + classNames: { + tabs: 'bui-Tabs', + tabList: 'bui-TabList', + tabListWrapper: 'bui-TabListWrapper', + tab: 'bui-Tab', + tabActive: 'bui-TabActive', + tabHovered: 'bui-TabHovered', + panel: 'bui-TabPanel', + }, + }, + Text: { + classNames: { + root: 'bui-Text', + }, + dataAttributes: { + variant: ['subtitle', 'body', 'caption', 'label'] as const, + weight: ['regular', 'bold'] as const, + color: ['primary', 'secondary', 'danger', 'warning', 'success'] as const, + truncate: [true, false] as const, + }, + }, + TextField: { + classNames: { + root: 'bui-TextField', + inputWrapper: 'bui-InputWrapper', + input: 'bui-Input', + inputIcon: 'bui-InputIcon', + }, + dataAttributes: { + invalid: [true, false] as const, + disabled: [true, false] as const, + }, + }, + Tooltip: { + classNames: { + tooltip: 'bui-Tooltip', + arrow: 'bui-TooltipArrow', + }, + }, +} as const satisfies Record; diff --git a/packages/canon/src/utils/extractProps.ts b/packages/ui/src/utils/extractProps.ts similarity index 100% rename from packages/canon/src/utils/extractProps.ts rename to packages/ui/src/utils/extractProps.ts diff --git a/packages/ui/src/utils/isExternalLink.ts b/packages/ui/src/utils/isExternalLink.ts new file mode 100644 index 0000000000..a874579ab5 --- /dev/null +++ b/packages/ui/src/utils/isExternalLink.ts @@ -0,0 +1,42 @@ +/* + * 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. + */ + +/** + * Determines if a link is external. + * @param href - The href of the link. + * @returns True if the link is external, false otherwise. + * @internal + */ +export function isExternalLink(href?: string): boolean { + if (!href) return false; + + // Check if it's an absolute URL with protocol + if (href.startsWith('http://') || href.startsWith('https://')) { + return true; + } + + // Check if it's a protocol-relative URL + if (href.startsWith('//')) { + return true; + } + + // Check if it's a mailto: or tel: link + if (href.startsWith('mailto:') || href.startsWith('tel:')) { + return true; + } + + return false; +} diff --git a/packages/ui/static/CNAME b/packages/ui/static/CNAME new file mode 100644 index 0000000000..21060e506a --- /dev/null +++ b/packages/ui/static/CNAME @@ -0,0 +1 @@ +ui.backstage.io diff --git a/packages/canon/static/favicon.svg b/packages/ui/static/favicon.svg similarity index 100% rename from packages/canon/static/favicon.svg rename to packages/ui/static/favicon.svg diff --git a/packages/canon/header.png b/packages/ui/static/header.png similarity index 100% rename from packages/canon/header.png rename to packages/ui/static/header.png diff --git a/packages/canon/static/logo.svg b/packages/ui/static/logo.svg similarity index 100% rename from packages/canon/static/logo.svg rename to packages/ui/static/logo.svg diff --git a/packages/version-bridge/catalog-info.yaml b/packages/version-bridge/catalog-info.yaml index 6f37a4b276..43899985ab 100644 --- a/packages/version-bridge/catalog-info.yaml +++ b/packages/version-bridge/catalog-info.yaml @@ -4,9 +4,8 @@ metadata: name: backstage-version-bridge title: '@backstage/version-bridge' description: >- - Utilities used by @backstage packages to support multiple concurrent - versions + Utilities used by @backstage packages to support multiple concurrent versions spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/yarn-plugin/CHANGELOG.md b/packages/yarn-plugin/CHANGELOG.md index 41ebade0a3..9ca9cbdf6f 100644 --- a/packages/yarn-plugin/CHANGELOG.md +++ b/packages/yarn-plugin/CHANGELOG.md @@ -1,5 +1,43 @@ # yarn-plugin-backstage +## 0.0.7 + +### Patch Changes + +- d6084b8: Fixed a bug that would prevent the yarn plugin from installing new dependencies with the `backstage:^` protocol. + +## 0.0.7-next.0 + +### Patch Changes + +- d6084b8: Fixed a bug that would prevent the yarn plugin from installing new dependencies with the `backstage:^` protocol. + +## 0.0.6 + +### Patch Changes + +- a0f9e4e: added functionality so that adding or updating a backstage dependency to a package would maintain the "backstage:^" placeholder for the version. +- Updated dependencies + - @backstage/cli-common@0.1.15 + - @backstage/release-manifests@0.0.13 + +## 0.0.5 + +### Patch Changes + +- fd70d86: Add both `npm:` and `backstage:` ranges to the lockfile to ensure compatibility with tools that parse the lockfile and ensure dependencies stay locked when building dist workspaces. +- Updated dependencies + - @backstage/release-manifests@0.0.13 + - @backstage/cli-common@0.1.15 + +## 0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/release-manifests@0.0.13-next.0 + - @backstage/cli-common@0.1.15 + ## 0.0.5-next.0 ### Patch Changes diff --git a/packages/yarn-plugin/README.md b/packages/yarn-plugin/README.md index 073669b9a5..5c112506e3 100644 --- a/packages/yarn-plugin/README.md +++ b/packages/yarn-plugin/README.md @@ -82,3 +82,12 @@ specific Backstage repository. As such, when publishing packages, all `backstage:^` versions should be removed from the package.json and replaced with the appropriate npm version ranges. This is handled by the `beforeWorkspacePacking` hook. + +### `afterWorkspaceDependencyAddition` hook + +_Replaces npm version ranges with `backstage:^` ranges for `@backstage/*` dependencies added after +the plugin has converted existing dependencies to `backstage:^` range_ + +### `afterWorkspaceDependencyReplacement` hook + +_warns user with console message when running `yarn add` for a `@backstage/*` scoped dependency that is already a dependency in the target package. Doing so will remove the `backstage:^` scope and replace it with the actual npm version range, which may not be desired._ diff --git a/packages/yarn-plugin/catalog-info.yaml b/packages/yarn-plugin/catalog-info.yaml index c3a9be8fa5..dbb07a7602 100644 --- a/packages/yarn-plugin/catalog-info.yaml +++ b/packages/yarn-plugin/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: tooling-maintainers diff --git a/packages/yarn-plugin/package.json b/packages/yarn-plugin/package.json index 78d34c2788..c534c50c76 100644 --- a/packages/yarn-plugin/package.json +++ b/packages/yarn-plugin/package.json @@ -1,6 +1,6 @@ { "name": "yarn-plugin-backstage", - "version": "0.0.5-next.0", + "version": "0.0.7", "description": "Yarn plugin for working with Backstage monorepos", "backstage": { "role": "node-library" @@ -32,8 +32,9 @@ "dependencies": { "@backstage/cli-common": "workspace:^", "@backstage/release-manifests": "workspace:^", - "@yarnpkg/core": "^4.4.0", + "@yarnpkg/core": "^4.4.1", "@yarnpkg/fslib": "^3.1.2", + "@yarnpkg/plugin-essentials": "^4.4.0", "@yarnpkg/plugin-npm": "patch:@yarnpkg/plugin-npm@npm%3A3.1.0#~/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch", "@yarnpkg/plugin-pack": "^4.0.1", "semver": "^7.6.0" diff --git a/packages/yarn-plugin/report.api.md b/packages/yarn-plugin/report.api.md index 620bbbb567..b9425282b4 100644 --- a/packages/yarn-plugin/report.api.md +++ b/packages/yarn-plugin/report.api.md @@ -4,10 +4,11 @@ ```ts import { Hooks } from '@yarnpkg/core'; -import { Hooks as Hooks_2 } from '@yarnpkg/plugin-pack'; +import { Hooks as Hooks_2 } from '@yarnpkg/plugin-essentials'; +import { Hooks as Hooks_3 } from '@yarnpkg/plugin-pack'; import { Plugin as Plugin_2 } from '@yarnpkg/core'; // @public (undocumented) -const plugin: Plugin_2; +const plugin: Plugin_2; export default plugin; ``` diff --git a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.test.ts b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.test.ts new file mode 100644 index 0000000000..20cf482cb4 --- /dev/null +++ b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.test.ts @@ -0,0 +1,154 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Descriptor, + DescriptorHash, + httpUtils, + IdentHash, + Workspace, +} from '@yarnpkg/core'; +import { npath, ppath } from '@yarnpkg/fslib'; +import { suggestUtils } from '@yarnpkg/plugin-essentials'; +import { afterWorkspaceDependencyAddition } from './afterWorkspaceDependencyAddition'; +import { createMockDirectory } from '@backstage/backend-test-utils'; + +describe('afterWorkspaceDependencyAddition', () => { + const mockDir = createMockDirectory(); + const workspace = { + project: { + configuration: {}, + }, + } as Workspace; + const target = {} as suggestUtils.Target; + const strategies: Array = []; + + const consoleInfoSpy = jest + .spyOn(console, 'info') + .mockImplementation(() => {}); + + beforeEach(() => { + jest.resetAllMocks(); + + jest.spyOn(httpUtils, 'get').mockResolvedValue({ + releaseVersion: '1.23.45', + packages: [ + { + name: '@backstage/test-package', + version: '6.7.8', + }, + ], + }); + + jest + .spyOn(ppath, 'cwd') + .mockReturnValue(npath.toPortablePath(mockDir.path)); + + jest + .spyOn(process, 'cwd') + .mockReturnValue(npath.toPortablePath(mockDir.path)); + + mockDir.setContent({ + 'backstage.json': JSON.stringify({ + version: '1.23.45', + }), + 'package.json': JSON.stringify({ + workspaces: { + packages: ['packages/*'], + }, + }), + }); + }); + + it('should replace the range for a backstage scoped dependency', async () => { + const input: Descriptor = { + scope: 'backstage', + name: 'test-package', + range: '^1.0.0', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + + await afterWorkspaceDependencyAddition( + workspace, + target, + input, + strategies, + ); + + expect(input.range).toBe('backstage:^'); + expect(consoleInfoSpy).toHaveBeenCalledWith( + `Setting ${input.scope}/${input.name} to backstage:^`, + ); + }); + + it('should not replace the range for a backstage scoped dependency where it cant find a version from remote', async () => { + const input: Descriptor = { + scope: 'backstage', + name: 'missing-package', + range: '^1.0.0', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + + await afterWorkspaceDependencyAddition( + workspace, + target, + input, + strategies, + ); + + expect(input.range).toBe('^1.0.0'); + }); + + it('should not replace the range for a non-backstage scoped dependency', async () => { + const input: Descriptor = { + scope: 'backstage-community', + name: 'test-package', + range: '^1.0.0', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + + await afterWorkspaceDependencyAddition( + workspace, + target, + input, + strategies, + ); + + expect(input.range).toBe('^1.0.0'); + }); + + it('should not replace the range for a dependency with backstage:^ version', async () => { + const input: Descriptor = { + scope: 'backstage', + name: 'another-test-package', + range: 'backstage:^', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + + await afterWorkspaceDependencyAddition( + workspace, + target, + input, + strategies, + ); + + expect(input.range).toBe('backstage:^'); + expect(consoleInfoSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.ts b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.ts new file mode 100644 index 0000000000..7fec01b3c7 --- /dev/null +++ b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.ts @@ -0,0 +1,48 @@ +/* + * 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 { Descriptor, structUtils, Workspace } from '@yarnpkg/core'; +import { suggestUtils } from '@yarnpkg/plugin-essentials'; +import { getPackageVersion } from '../util'; +import { PROTOCOL } from '../constants'; + +export const afterWorkspaceDependencyAddition = async ( + workspace: Workspace, + _target: suggestUtils.Target, + descriptor: Descriptor, + _strategies: Array, +) => { + const descriptorRange = structUtils.parseRange(descriptor.range); + + if ( + descriptor.scope === 'backstage' && + descriptorRange.protocol !== PROTOCOL + ) { + const originalRange = descriptor.range; + try { + descriptor.range = `${PROTOCOL}^`; + await getPackageVersion(descriptor, workspace.project.configuration); // Verify that the actual version can be resolved + console.info( + `Setting ${descriptor.scope}/${descriptor.name} to ${PROTOCOL}^`, + ); + } catch (_error: any) { + // if there's no found version then this is likely a deprecated package + // or otherwise the plugin won't be able to resolve the real version + // and we should leave the desired range as is + descriptor.range = originalRange; + } + } +}; diff --git a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.test.ts b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.test.ts new file mode 100644 index 0000000000..137d91d680 --- /dev/null +++ b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.test.ts @@ -0,0 +1,127 @@ +/* + * 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 { + Descriptor, + DescriptorHash, + httpUtils, + IdentHash, + Workspace, +} from '@yarnpkg/core'; +import { npath, ppath } from '@yarnpkg/fslib'; +import { suggestUtils } from '@yarnpkg/plugin-essentials'; +import { afterWorkspaceDependencyReplacement } from './afterWorkspaceDependencyReplacement'; +import { createMockDirectory } from '@backstage/backend-test-utils'; + +describe('afterWorkspaceDependencyReplacement.test', () => { + const mockDir = createMockDirectory(); + const workspace = { + project: { + configuration: {}, + }, + } as Workspace; + const target = {} as suggestUtils.Target; + + const consoleWarnSpy = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + + beforeEach(() => { + jest.resetAllMocks(); + + jest.spyOn(httpUtils, 'get').mockResolvedValue({ + releaseVersion: '1.23.45', + packages: [ + { + name: '@backstage/test-package', + version: '6.7.8', + }, + ], + }); + + jest + .spyOn(ppath, 'cwd') + .mockReturnValue(npath.toPortablePath(mockDir.path)); + + jest + .spyOn(process, 'cwd') + .mockReturnValue(npath.toPortablePath(mockDir.path)); + + mockDir.setContent({ + 'backstage.json': JSON.stringify({ + version: '1.23.45', + }), + 'package.json': JSON.stringify({ + workspaces: { + packages: ['packages/*'], + }, + }), + }); + }); + + it('should warn that the range is being changed for a backstage scoped dependency', async () => { + const fromDescriptor: Descriptor = { + scope: 'backstage', + name: 'test-package', + range: 'backstage:^', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + const toDescriptor: Descriptor = { + scope: 'backstage', + name: 'test-package', + range: '^1.0.0', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + + await afterWorkspaceDependencyReplacement( + workspace, + target, + fromDescriptor, + toDescriptor, + ); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + 'test-package should be set to "backstage:^" instead of "^1.0.0". Make sure this change is intentional and not a mistake.', + ); + }); + + it('should ignore that the range is being changed for a non-backstage scoped dependency', async () => { + const fromDescriptor: Descriptor = { + scope: 'backstage-community', + name: 'test-package', + range: 'backstage:^', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + const toDescriptor: Descriptor = { + scope: 'backstage-community', + name: 'test-package', + range: '^1.0.0', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + await afterWorkspaceDependencyReplacement( + workspace, + target, + fromDescriptor, + toDescriptor, + ); + + expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(toDescriptor.range).toBe('^1.0.0'); + }); +}); diff --git a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.ts b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.ts new file mode 100644 index 0000000000..382a19c48b --- /dev/null +++ b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.ts @@ -0,0 +1,37 @@ +/* + * 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 { Descriptor, structUtils, Workspace } from '@yarnpkg/core'; +import { suggestUtils } from '@yarnpkg/plugin-essentials'; +import { PROTOCOL } from '../constants'; + +export const afterWorkspaceDependencyReplacement = async ( + _workspace: Workspace, + _target: suggestUtils.Target, + _fromDescriptor: Descriptor, + toDescriptor: Descriptor, +) => { + const toDescriptorRange = structUtils.parseRange(toDescriptor.range); + + if ( + toDescriptor.scope === 'backstage' && + toDescriptorRange.protocol !== PROTOCOL + ) { + console.warn( + `${toDescriptor.name} should be set to "${PROTOCOL}^" instead of "${toDescriptor.range}". Make sure this change is intentional and not a mistake.`, + ); + } +}; diff --git a/packages/yarn-plugin/src/handlers/index.ts b/packages/yarn-plugin/src/handlers/index.ts index baa144b36f..fe8f05c935 100644 --- a/packages/yarn-plugin/src/handlers/index.ts +++ b/packages/yarn-plugin/src/handlers/index.ts @@ -16,3 +16,5 @@ export { beforeWorkspacePacking } from './beforeWorkspacePacking'; export { reduceDependency } from './reduceDependency'; +export { afterWorkspaceDependencyAddition } from './afterWorkspaceDependencyAddition'; +export { afterWorkspaceDependencyReplacement } from './afterWorkspaceDependencyReplacement'; diff --git a/packages/yarn-plugin/src/index.ts b/packages/yarn-plugin/src/index.ts index ad18deb0ff..b08d98eae7 100644 --- a/packages/yarn-plugin/src/index.ts +++ b/packages/yarn-plugin/src/index.ts @@ -23,7 +23,13 @@ import { Plugin, Hooks, semverUtils, YarnVersion } from '@yarnpkg/core'; import { Hooks as PackHooks } from '@yarnpkg/plugin-pack'; -import { beforeWorkspacePacking, reduceDependency } from './handlers'; +import { Hooks as EssentialHooks } from '@yarnpkg/plugin-essentials'; +import { + afterWorkspaceDependencyAddition, + afterWorkspaceDependencyReplacement, + beforeWorkspacePacking, + reduceDependency, +} from './handlers'; import { BackstageNpmResolver } from './resolvers'; // All dependencies of the yarn plugin are bundled during the build. Chalk @@ -44,8 +50,10 @@ if (!semverUtils.satisfiesWithPrereleases(YarnVersion, '^4.1.1')) { /** * @public */ -const plugin: Plugin = { +const plugin: Plugin = { hooks: { + afterWorkspaceDependencyAddition, + afterWorkspaceDependencyReplacement, reduceDependency, beforeWorkspacePacking, }, diff --git a/playwright.config.ts b/playwright.config.ts index ee8228e413..a5867ce217 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -32,18 +32,17 @@ export default defineConfig({ ? [] : [ { - command: 'yarn start', + command: 'yarn start example-app', port: 3000, reuseExistingServer: true, timeout: 60_000, }, - // TODO: Before encouraging e2e tests for backend we'll want to provide better utilities for mocking auth - // { - // command: 'yarn start-backend', - // port: 7007, - // reuseExistingServer: true, - // timeout: 60_000, - // }, + { + command: 'yarn start example-backend', + port: 7007, + reuseExistingServer: true, + timeout: 60_000, + }, ], forbidOnly: !!process.env.CI, diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index ea622deaba..29edd6cb7e 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,191 @@ # @backstage/plugin-api-docs +## 0.12.10-next.3 + +### Patch Changes + +- 8b1bf6e: Updated README instructions for the new frontend system +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.2 + - @backstage/core-compat-api@0.5.0-next.3 + - @backstage/core-components@0.17.5-next.2 + +## 0.12.10-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-catalog@1.31.2-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + +## 0.12.10-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/plugin-catalog@1.31.2-next.1 + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + +## 0.12.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/plugin-catalog@1.31.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.12.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-catalog@1.31.1 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + +## 0.12.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-catalog@1.31.1-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 0.12.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.1-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.35 + +## 0.12.8 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/plugin-catalog@1.31.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.35 + +## 0.12.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog@1.31.0-next.2 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.34 + +## 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 + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-catalog@1.30.0 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.12.7-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.30.0-next.3 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + ## 0.12.7-next.2 ### Patch Changes diff --git a/plugins/api-docs/README-alpha.md b/plugins/api-docs/README-alpha.md index 5cdd386b89..1c30d62cd4 100644 --- a/plugins/api-docs/README-alpha.md +++ b/plugins/api-docs/README-alpha.md @@ -68,9 +68,8 @@ To link that a component provides or consumes an API, see the [`providesApis`](h ```yaml # app-config.yaml app: - experimental: - # Auto discovering all plugins extensions - packages: all + # Auto discovering all plugins extensions + packages: all extensions: # Enabling some entity cards # The cards will be displayed in the same order it appears in this setting list @@ -287,7 +286,7 @@ export default createFrontendModule({ createPageExtension({ // Omitting name since we are overriding a plugin index page // It's up to you whether to use the original default path or not, but links that are hardcoded to the default path won't work if you change it - defaultPath: '/api-docs', + path: '/api-docs', // Associating the page with a different route ref may result in the sidebar item or external plugin route pointing to an unreachable page routeRef: convertLegacyRouteRef(rootRoute), // Custom page components are loaded here diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 4854695e23..9d16e76f1b 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-next.2", + "version": "0.12.10-next.3", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin", diff --git a/plugins/api-docs/report-alpha.api.md b/plugins/api-docs/report-alpha.api.md index b13fca4996..ef4cb297ca 100644 --- a/plugins/api-docs/report-alpha.api.md +++ b/plugins/api-docs/report-alpha.api.md @@ -4,13 +4,14 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; -import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { defaultEntityContentGroups } from '@backstage/plugin-catalog-react/alpha'; import { Entity } from '@backstage/catalog-model'; import { EntityCardType } from '@backstage/plugin-catalog-react/alpha'; import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; @@ -67,15 +68,15 @@ const _default: FrontendPlugin< name: 'config'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'entity-card:api-docs/consumed-apis': ExtensionDefinition<{ kind: 'entity-card'; @@ -89,22 +90,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -130,22 +131,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -171,22 +172,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -212,22 +213,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -253,22 +254,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -294,22 +295,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -339,35 +340,31 @@ const _default: FrontendPlugin< group?: string | false | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { optional: true; } > - | ConfigurableExtensionDataRef< - string, - 'catalog.entity-content-title', - {} - > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< string, 'catalog.entity-content-group', { @@ -376,10 +373,13 @@ const _default: FrontendPlugin< >; inputs: {}; params: { + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; + defaultTitle?: [Error: `Use the 'title' param instead`]; + title: string; + defaultGroup?: [Error: `Use the 'group' param instead`]; + group?: keyof defaultEntityContentGroups | (string & {}); loader: () => Promise; - defaultPath: string; - defaultTitle: string; - defaultGroup?: keyof defaultEntityContentGroups | (string & {}); routeRef?: RouteRef; filter?: string | EntityPredicate | ((entity: Entity) => boolean); }; @@ -400,35 +400,31 @@ const _default: FrontendPlugin< group?: string | false | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { optional: true; } > - | ConfigurableExtensionDataRef< - string, - 'catalog.entity-content-title', - {} - > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< string, 'catalog.entity-content-group', { @@ -437,10 +433,13 @@ const _default: FrontendPlugin< >; inputs: {}; params: { + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; + defaultTitle?: [Error: `Use the 'title' param instead`]; + title: string; + defaultGroup?: [Error: `Use the 'group' param instead`]; + group?: keyof defaultEntityContentGroups | (string & {}); loader: () => Promise; - defaultPath: string; - defaultTitle: string; - defaultGroup?: keyof defaultEntityContentGroups | (string & {}); routeRef?: RouteRef; filter?: string | EntityPredicate | ((entity: Entity) => boolean); }; @@ -450,7 +449,7 @@ const _default: FrontendPlugin< name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { title: string; icon: IconComponent; @@ -478,9 +477,9 @@ const _default: FrontendPlugin< path?: string | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { @@ -489,7 +488,7 @@ const _default: FrontendPlugin< >; inputs: { [x: string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; @@ -499,7 +498,8 @@ const _default: FrontendPlugin< kind: 'page'; name: undefined; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise; routeRef?: RouteRef; }; diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx index cb55f620a7..7693b97259 100644 --- a/plugins/api-docs/src/alpha.tsx +++ b/plugins/api-docs/src/alpha.tsx @@ -20,7 +20,6 @@ import { ApiBlueprint, NavItemBlueprint, PageBlueprint, - createApiFactory, createFrontendPlugin, } from '@backstage/frontend-plugin-api'; @@ -55,8 +54,8 @@ const apiDocsNavItem = NavItemBlueprint.make({ const apiDocsConfigApi = ApiBlueprint.make({ name: 'config', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: apiDocsConfigRef, deps: {}, factory: () => { @@ -68,7 +67,6 @@ const apiDocsConfigApi = ApiBlueprint.make({ }; }, }), - }, }); const apiDocsExplorerPage = PageBlueprint.makeWithOverrides({ @@ -81,7 +79,7 @@ const apiDocsExplorerPage = PageBlueprint.makeWithOverrides({ }, factory(originalFactory, { config }) { return originalFactory({ - defaultPath: '/api-docs', + path: '/api-docs', routeRef: convertLegacyRouteRef(rootRoute), loader: () => import('./components/ApiExplorerPage').then(m => @@ -188,8 +186,8 @@ const apiDocsProvidingComponentsEntityCard = EntityCardBlueprint.make({ const apiDocsDefinitionEntityContent = EntityContentBlueprint.make({ name: 'definition', params: { - defaultPath: '/definition', - defaultTitle: 'Definition', + path: '/definition', + title: 'Definition', filter: 'kind:api', loader: async () => import('./components/ApiDefinitionCard').then(m => @@ -207,8 +205,8 @@ const apiDocsDefinitionEntityContent = EntityContentBlueprint.make({ const apiDocsApisEntityContent = EntityContentBlueprint.make({ name: 'apis', params: { - defaultPath: '/apis', - defaultTitle: 'APIs', + path: '/apis', + title: 'APIs', filter: 'kind:component', loader: async () => import('./components/ApisCards').then(m => @@ -228,6 +226,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 966860f088..fb427cde4d 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,102 @@ # @backstage/plugin-app-backend +## 0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config-loader@1.10.2 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.36-next.0 + +## 0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2 + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-app-node@0.1.35 + - @backstage/plugin-auth-node@0.6.5 + +## 0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2-next.0 + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-app-node@0.1.35-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.5.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +## 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 + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config-loader@1.10.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.33 + +## 0.5.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.33-next.2 + ## 0.5.2-next.1 ### Patch Changes diff --git a/plugins/app-backend/catalog-info.yaml b/plugins/app-backend/catalog-info.yaml index 27135f4f8d..d75255357b 100644 --- a/plugins/app-backend/catalog-info.yaml +++ b/plugins/app-backend/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin - owner: maintainers + owner: framework-maintainers diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 432e17151a..0094df6cc3 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-next.1", + "version": "0.5.5-next.0", "description": "A Backstage backend plugin that serves the Backstage frontend app", "backstage": { "role": "backend-plugin", diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md index 6d1e1a2368..315559cd0a 100644 --- a/plugins/app-node/CHANGELOG.md +++ b/plugins/app-node/CHANGELOG.md @@ -1,5 +1,69 @@ # @backstage/plugin-app-node +## 0.1.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config-loader@1.10.2 + +## 0.1.35 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + +## 0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + +## 0.1.34 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config-loader@1.10.1 + +## 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config-loader@1.10.1 + +## 0.1.33-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config-loader@1.10.1-next.0 + ## 0.1.33-next.1 ### Patch Changes diff --git a/plugins/app-node/catalog-info.yaml b/plugins/app-node/catalog-info.yaml index 6624153c6c..3eb9184e2e 100644 --- a/plugins/app-node/catalog-info.yaml +++ b/plugins/app-node/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json index b92d045fd8..beac2d174a 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-next.1", + "version": "0.1.36-next.0", "description": "Node.js library for the app plugin", "backstage": { "role": "node-library", diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md index 9d0513b41a..2b96c3b04f 100644 --- a/plugins/app-visualizer/CHANGELOG.md +++ b/plugins/app-visualizer/CHANGELOG.md @@ -1,5 +1,88 @@ # @backstage/plugin-app-visualizer +## 0.1.22-next.1 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-components@0.17.5-next.1 + - @backstage/core-plugin-api@1.10.9 + +## 0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + +## 0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + +## 0.1.20 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + +## 0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/core-plugin-api@1.10.7 + +## 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 + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + ## 0.1.19-next.1 ### Patch Changes diff --git a/plugins/app-visualizer/catalog-info.yaml b/plugins/app-visualizer/catalog-info.yaml index cb444af524..ed59a5eb45 100644 --- a/plugins/app-visualizer/catalog-info.yaml +++ b/plugins/app-visualizer/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-frontend-plugin - owner: maintainers + owner: framework-maintainers diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index d481e89225..6d4d2717b2 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-next.1", + "version": "0.1.22-next.1", "description": "Visualizes the Backstage app structure", "backstage": { "role": "frontend-plugin", diff --git a/plugins/app-visualizer/report.api.md b/plugins/app-visualizer/report.api.md index 370647afc9..414c47cecc 100644 --- a/plugins/app-visualizer/report.api.md +++ b/plugins/app-visualizer/report.api.md @@ -4,7 +4,7 @@ ```ts import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; @@ -21,7 +21,7 @@ const visualizerPlugin: FrontendPlugin< name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { title: string; icon: IconComponent; @@ -47,9 +47,9 @@ const visualizerPlugin: FrontendPlugin< path?: string | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { @@ -58,7 +58,8 @@ const visualizerPlugin: FrontendPlugin< >; inputs: {}; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise; routeRef?: RouteRef; }; diff --git a/plugins/app-visualizer/src/plugin.tsx b/plugins/app-visualizer/src/plugin.tsx index 92ccd00159..58e1b2effa 100644 --- a/plugins/app-visualizer/src/plugin.tsx +++ b/plugins/app-visualizer/src/plugin.tsx @@ -26,7 +26,7 @@ const rootRouteRef = createRouteRef(); const appVisualizerPage = PageBlueprint.make({ params: { - defaultPath: '/visualizer', + path: '/visualizer', routeRef: rootRouteRef, loader: () => import('./components/AppVisualizerPage').then(m => ( @@ -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 790b735bb1..90cf023618 100644 --- a/plugins/app/CHANGELOG.md +++ b/plugins/app/CHANGELOG.md @@ -1,5 +1,171 @@ # @backstage/plugin-app +## 0.2.0-next.2 + +### Minor Changes + +- fda1bbc: **BREAKING**: The `componentsApi` implementation has been removed from the plugin and replaced with the new `SwappableComponentsApi` instead. + + If you were overriding the `componentsApi` implementation, you can now use the new `SwappableComponentsApi` instead. + + ```ts + // old + appPlugin.getExtension('api:app/components').override(...) + + // new + appPlugin.getExtension('api:app/swappable-components').override(...) + ``` + +### Patch Changes + +- 91cbdf4: Log a warning when `SwappableComponent` extensions are installed outside of using the `app` plugin +- fda1bbc: Default implementations of core components are now provided by this package. + + A backwards compatible `componentsApi` implementation is also provided from this package which uses the `SwappableComponentsApi` as the implementation. This backwards compatible wrapper will be removed in the future. + +- 9831f4e: Adjusted the dialog API types to have more sensible defaults +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.2 + - @backstage/core-components@0.17.5-next.2 + +## 0.2.0-next.1 + +### Minor Changes + +- 121899a: **BREAKING**: The `app-root-element` extension now only accepts `JSX.Element` in its `element` param, meaning overrides need to be updated. + +### Patch Changes + +- a08f95f: Added a new module for implementing public sign-in apps, exported as `appModulePublicSignIn` via the `/alpha` sub-path export. This replaces the `createPublicSignInApp` export from `@backstage/frontend-defaults`, which is now deprecated. +- 5d31d66: Updated the usage of the `RouterBlueprint` and `AppRootWrapperBlueprint` to use the lowercase `component` parameter +- 93b5e38: The default implementation of the Analytics API now collects and instantiates analytics implementations exposed via `AnalyticsImplementationBlueprint` extensions. If no such extensions are discovered, the API continues to do nothing with analytics events fired within Backstage. If multiple such extensions are discovered, every discovered implementation automatically receives analytics events. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-components@0.17.5-next.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/theme@0.6.8-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-react@0.4.36 + +## 0.2.0-next.0 + +### Minor Changes + +- 29786f6: Updated the `app/nav` extension to use the new `NavContentBlueprint`, and removed support for extensions created with the now removed `NavLogoBlueprint`. + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-react@0.4.36 + +## 0.1.11 + +### Patch Changes + +- 09f5e36: Remove trailing slashes in the `AppRoutes` extension to ensure any nested routing behaves correctly. +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/integration-react@1.2.9 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-permission-react@0.4.36 + +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.9-next.0 + - @backstage/theme@0.6.7-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-react@0.4.35 + +## 0.1.10 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/integration-react@1.2.8 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-react@0.4.35 + +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/core-plugin-api@1.10.7 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-react@0.4.34 + +## 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 + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/types@1.2.1 + +## 0.1.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + ## 0.1.9-next.2 ### Patch Changes diff --git a/plugins/app/README.md b/plugins/app/README.md index f54a8e0a4c..83085c2a63 100644 --- a/plugins/app/README.md +++ b/plugins/app/README.md @@ -1,9 +1,3 @@ # app -Welcome to the app plugin! - -_This plugin was created through the Backstage CLI_ - -## Getting started - -Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/app](http://localhost:3000/app). +This plugin is part of the new frontend system, and represents the foundation of the frontend application itself, which then other features attach themselves into. diff --git a/plugins/app/catalog-info.yaml b/plugins/app/catalog-info.yaml index 5bae243ea7..3396a5c98e 100644 --- a/plugins/app/catalog-info.yaml +++ b/plugins/app/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-frontend-plugin - owner: maintainers + owner: framework-maintainers diff --git a/plugins/app/package.json b/plugins/app/package.json index fed1f47f4c..9af72050b5 100644 --- a/plugins/app/package.json +++ b/plugins/app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app", - "version": "0.1.9-next.2", + "version": "0.2.0-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "app", @@ -11,9 +11,7 @@ ] }, "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" }, "repository": { "type": "git", @@ -22,8 +20,23 @@ }, "license": "Apache-2.0", "sideEffects": false, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha/index.ts", + "./package.json": "./package.json" + }, "main": "src/index.ts", "types": "src/index.ts", + "typesVersions": { + "*": { + "alpha": [ + "src/alpha/index.ts" + ], + "package.json": [ + "package.json" + ] + } + }, "files": [ "dist" ], @@ -44,15 +57,20 @@ "@backstage/plugin-permission-react": "workspace:^", "@backstage/theme": "workspace:^", "@backstage/types": "workspace:^", + "@backstage/version-bridge": "workspace:^", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.61", - "react-use": "^17.2.4" + "@react-hookz/web": "^24.0.0", + "react-use": "^17.2.4", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/dev-utils": "workspace:^", + "@backstage/frontend-defaults": "workspace:^", "@backstage/frontend-test-utils": "workspace:^", + "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", diff --git a/plugins/app/report-alpha.api.md b/plugins/app/report-alpha.api.md new file mode 100644 index 0000000000..9496430e7b --- /dev/null +++ b/plugins/app/report-alpha.api.md @@ -0,0 +1,12 @@ +## API Report File for "@backstage/plugin-app" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { FrontendModule } from '@backstage/frontend-plugin-api'; + +// @alpha +export const appModulePublicSignIn: FrontendModule; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/app/report.api.md b/plugins/app/report.api.md index 3654a3965a..376dbc8db6 100644 --- a/plugins/app/report.api.md +++ b/plugins/app/report.api.md @@ -3,22 +3,26 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnalyticsImplementationFactory } from '@backstage/frontend-plugin-api'; import { AnyApiFactory } from '@backstage/frontend-plugin-api'; -import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { AppTheme } from '@backstage/frontend-plugin-api'; -import { ComponentRef } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { IconComponent as IconComponent_2 } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; +import { NavContentComponent } from '@backstage/frontend-plugin-api'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { SignInPageProps } from '@backstage/core-plugin-api'; +import { SwappableComponentRef } from '@backstage/frontend-plugin-api'; import { TranslationMessages } from '@backstage/frontend-plugin-api'; import { TranslationResource } from '@backstage/frontend-plugin-api'; @@ -30,11 +34,7 @@ const appPlugin: FrontendPlugin< app: ExtensionDefinition<{ config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + output: ExtensionDataRef; inputs: { root: ExtensionInput< ConfigurableExtensionDataRef, @@ -51,11 +51,7 @@ const appPlugin: FrontendPlugin< 'app/layout': ExtensionDefinition<{ config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + output: ExtensionDataRef; inputs: { nav: ExtensionInput< ConfigurableExtensionDataRef, @@ -79,11 +75,7 @@ const appPlugin: FrontendPlugin< 'app/nav': ExtensionDefinition<{ config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + output: ExtensionDataRef; inputs: { items: ExtensionInput< ConfigurableExtensionDataRef< @@ -100,13 +92,10 @@ const appPlugin: FrontendPlugin< optional: false; } >; - logos: ExtensionInput< + content: ExtensionInput< ConfigurableExtensionDataRef< - { - logoIcon?: JSX.Element; - logoFull?: JSX.Element; - }, - 'core.nav-logo.logo-elements', + NavContentComponent, + 'core.nav-content.component', {} >, { @@ -122,17 +111,11 @@ const appPlugin: FrontendPlugin< 'app/root': ExtensionDefinition<{ config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + output: ExtensionDataRef; inputs: { router: ExtensionInput< ConfigurableExtensionDataRef< - ComponentType<{ - children?: ReactNode | undefined; - }>, + (props: { children: ReactNode }) => JSX.Element | null, 'app.router.wrapper', {} >, @@ -168,9 +151,7 @@ const appPlugin: FrontendPlugin< >; wrappers: ExtensionInput< ConfigurableExtensionDataRef< - ComponentType<{ - children?: ReactNode | undefined; - }>, + (props: { children: ReactNode }) => JSX.Element | null, 'app.root.wrapper', {} >, @@ -187,11 +168,7 @@ const appPlugin: FrontendPlugin< 'app/routes': ExtensionDefinition<{ config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + output: ExtensionDataRef; inputs: { routes: ExtensionInput< | ConfigurableExtensionDataRef @@ -218,54 +195,62 @@ const appPlugin: FrontendPlugin< name: 'alert'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/analytics': ExtensionDefinition<{ - kind: 'api'; - name: 'analytics'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; - inputs: {}; - params: { - factory: AnyApiFactory; + output: ExtensionDataRef; + inputs: { + implementations: ExtensionInput< + ConfigurableExtensionDataRef< + AnalyticsImplementationFactory<{}>, + 'core.analytics.factory', + {} + >, + { + singleton: false; + optional: false; + } + >; }; + kind: 'api'; + name: 'analytics'; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/app-language': ExtensionDefinition<{ kind: 'api'; name: 'app-language'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/app-theme': ExtensionDefinition<{ config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: { themes: ExtensionInput< ConfigurableExtensionDataRef, @@ -277,213 +262,198 @@ const appPlugin: FrontendPlugin< }; kind: 'api'; name: 'app-theme'; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/atlassian-auth': ExtensionDefinition<{ kind: 'api'; name: 'atlassian-auth'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/bitbucket-auth': ExtensionDefinition<{ kind: 'api'; name: 'bitbucket-auth'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/bitbucket-server-auth': ExtensionDefinition<{ kind: 'api'; name: 'bitbucket-server-auth'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/components': ExtensionDefinition<{ - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; - inputs: { - components: ExtensionInput< - ConfigurableExtensionDataRef< - { - ref: ComponentRef; - impl: ComponentType; - }, - 'core.component.component', - {} - >, - { - singleton: false; - optional: false; - } - >; - }; kind: 'api'; name: 'components'; - params: { - factory: AnyApiFactory; - }; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/dialog': ExtensionDefinition<{ kind: 'api'; name: 'dialog'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/discovery': ExtensionDefinition<{ kind: 'api'; name: 'discovery'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/error': ExtensionDefinition<{ kind: 'api'; name: 'error'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/feature-flags': ExtensionDefinition<{ kind: 'api'; name: 'feature-flags'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/fetch': ExtensionDefinition<{ kind: 'api'; name: 'fetch'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/github-auth': ExtensionDefinition<{ kind: 'api'; name: 'github-auth'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/gitlab-auth': ExtensionDefinition<{ kind: 'api'; name: 'gitlab-auth'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/google-auth': ExtensionDefinition<{ kind: 'api'; name: 'google-auth'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/icons': ExtensionDefinition<{ config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: { icons: ExtensionInput< ConfigurableExtensionDataRef< @@ -501,138 +471,170 @@ const appPlugin: FrontendPlugin< }; kind: 'api'; name: 'icons'; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/microsoft-auth': ExtensionDefinition<{ kind: 'api'; name: 'microsoft-auth'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/oauth-request': ExtensionDefinition<{ kind: 'api'; name: 'oauth-request'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/okta-auth': ExtensionDefinition<{ kind: 'api'; name: 'okta-auth'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/onelogin-auth': ExtensionDefinition<{ kind: 'api'; name: 'onelogin-auth'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/permission': ExtensionDefinition<{ kind: 'api'; name: 'permission'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/scm-auth': ExtensionDefinition<{ kind: 'api'; name: 'scm-auth'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/scm-integrations': ExtensionDefinition<{ kind: 'api'; name: 'scm-integrations'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/storage': ExtensionDefinition<{ kind: 'api'; name: 'storage'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; + }>; + 'api:app/swappable-components': ExtensionDefinition<{ + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: { + components: ExtensionInput< + ConfigurableExtensionDataRef< + { + ref: SwappableComponentRef; + loader: + | (() => (props: {}) => JSX.Element | null) + | (() => Promise<(props: {}) => JSX.Element | null>); + }, + 'core.swappableComponent', + {} + >, + { + singleton: false; + optional: false; + } + >; }; + kind: 'api'; + name: 'swappable-components'; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/translations': ExtensionDefinition<{ config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: { translations: ExtensionInput< ConfigurableExtensionDataRef< @@ -655,24 +657,28 @@ const appPlugin: FrontendPlugin< }; kind: 'api'; name: 'translations'; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:app/vmware-cloud-auth': ExtensionDefinition<{ kind: 'api'; name: 'vmware-cloud-auth'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'app-root-element:app/alert-display': ExtensionDefinition<{ config: { @@ -691,14 +697,10 @@ const appPlugin: FrontendPlugin< | undefined; transientTimeoutMs?: number | undefined; }; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + output: ExtensionDataRef; inputs: { [x: string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; @@ -708,20 +710,16 @@ const appPlugin: FrontendPlugin< kind: 'app-root-element'; name: 'alert-display'; params: { - element: JSX.Element | (() => JSX.Element); + element: JSX.Element; }; }>; 'app-root-element:app/dialog-display': ExtensionDefinition<{ config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + output: ExtensionDataRef; inputs: { [x: string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; @@ -731,7 +729,7 @@ const appPlugin: FrontendPlugin< kind: 'app-root-element'; name: 'dialog-display'; params: { - element: JSX.Element | (() => JSX.Element); + element: JSX.Element; }; }>; 'app-root-element:app/oauth-request-dialog': ExtensionDefinition<{ @@ -739,22 +737,186 @@ const appPlugin: FrontendPlugin< name: 'oauth-request-dialog'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', + output: ExtensionDataRef; + inputs: {}; + params: { + element: JSX.Element; + }; + }>; + 'component:app/core-error-display': ExtensionDefinition<{ + kind: 'component'; + name: 'core-error-display'; + config: {}; + configInput: {}; + output: ExtensionDataRef< + { + ref: SwappableComponentRef; + loader: + | (() => (props: {}) => JSX.Element | null) + | (() => Promise<(props: {}) => JSX.Element | null>); + }, + 'core.swappableComponent', {} >; inputs: {}; - params: { - element: JSX.Element | (() => JSX.Element); - }; + params: >(params: { + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > + ? { + ref: Ref; + } & ((props: IExternalComponentProps) => JSX.Element | null) + : never; + loader: Ref extends SwappableComponentRef< + infer IInnerComponentProps, + any + > + ? + | (() => (props: IInnerComponentProps) => JSX.Element | null) + | (() => Promise< + (props: IInnerComponentProps) => JSX.Element | null + >) + : never; + }) => ExtensionBlueprintParams<{ + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > + ? { + ref: Ref; + } & ((props: IExternalComponentProps) => JSX.Element | null) + : never; + loader: Ref extends SwappableComponentRef< + infer IInnerComponentProps, + any + > + ? + | (() => (props: IInnerComponentProps) => JSX.Element | null) + | (() => Promise< + (props: IInnerComponentProps) => JSX.Element | null + >) + : never; + }>; + }>; + 'component:app/core-not-found-error-page': ExtensionDefinition<{ + kind: 'component'; + name: 'core-not-found-error-page'; + config: {}; + configInput: {}; + output: ExtensionDataRef< + { + ref: SwappableComponentRef; + loader: + | (() => (props: {}) => JSX.Element | null) + | (() => Promise<(props: {}) => JSX.Element | null>); + }, + 'core.swappableComponent', + {} + >; + inputs: {}; + params: >(params: { + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > + ? { + ref: Ref; + } & ((props: IExternalComponentProps) => JSX.Element | null) + : never; + loader: Ref extends SwappableComponentRef< + infer IInnerComponentProps, + any + > + ? + | (() => (props: IInnerComponentProps) => JSX.Element | null) + | (() => Promise< + (props: IInnerComponentProps) => JSX.Element | null + >) + : never; + }) => ExtensionBlueprintParams<{ + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > + ? { + ref: Ref; + } & ((props: IExternalComponentProps) => JSX.Element | null) + : never; + loader: Ref extends SwappableComponentRef< + infer IInnerComponentProps, + any + > + ? + | (() => (props: IInnerComponentProps) => JSX.Element | null) + | (() => Promise< + (props: IInnerComponentProps) => JSX.Element | null + >) + : never; + }>; + }>; + 'component:app/core-progress': ExtensionDefinition<{ + kind: 'component'; + name: 'core-progress'; + config: {}; + configInput: {}; + output: ExtensionDataRef< + { + ref: SwappableComponentRef; + loader: + | (() => (props: {}) => JSX.Element | null) + | (() => Promise<(props: {}) => JSX.Element | null>); + }, + 'core.swappableComponent', + {} + >; + inputs: {}; + params: >(params: { + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > + ? { + ref: Ref; + } & ((props: IExternalComponentProps) => JSX.Element | null) + : never; + loader: Ref extends SwappableComponentRef< + infer IInnerComponentProps, + any + > + ? + | (() => (props: IInnerComponentProps) => JSX.Element | null) + | (() => Promise< + (props: IInnerComponentProps) => JSX.Element | null + >) + : never; + }) => ExtensionBlueprintParams<{ + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > + ? { + ref: Ref; + } & ((props: IExternalComponentProps) => JSX.Element | null) + : never; + loader: Ref extends SwappableComponentRef< + infer IInnerComponentProps, + any + > + ? + | (() => (props: IInnerComponentProps) => JSX.Element | null) + | (() => Promise< + (props: IInnerComponentProps) => JSX.Element | null + >) + : never; + }>; }>; 'sign-in-page:app': ExtensionDefinition<{ kind: 'sign-in-page'; name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< ComponentType, 'core.sign-in-page.component', {} @@ -769,7 +931,7 @@ const appPlugin: FrontendPlugin< name: 'dark'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef; + output: ExtensionDataRef; inputs: {}; params: { theme: AppTheme; @@ -780,7 +942,7 @@ const appPlugin: FrontendPlugin< name: 'light'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef; + output: ExtensionDataRef; inputs: {}; params: { theme: AppTheme; diff --git a/plugins/app/src/alpha/appModulePublicSignIn.test.tsx b/plugins/app/src/alpha/appModulePublicSignIn.test.tsx new file mode 100644 index 0000000000..a40bf19dd6 --- /dev/null +++ b/plugins/app/src/alpha/appModulePublicSignIn.test.tsx @@ -0,0 +1,128 @@ +/* + * 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 { + SignInPageBlueprint, + createFrontendModule, +} from '@backstage/frontend-plugin-api'; +import { render, screen, waitFor } from '@testing-library/react'; +import { useEffect } from 'react'; +import { appModulePublicSignIn } from './appModulePublicSignIn'; +import { mockApis } from '@backstage/test-utils'; +import { createApp } from '@backstage/frontend-defaults'; + +describe('appModulePublicSignIn', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should render a sign-in page', async () => { + const app = createApp({ + advanced: { + configLoader: async () => ({ config: mockApis.config() }), + }, + features: [ + appModulePublicSignIn, + createFrontendModule({ + pluginId: 'app', + extensions: [ + SignInPageBlueprint.make({ + params: { + loader: async () => () =>
    Sign in page
    , + }, + }), + ], + }), + ], + }); + + render(app.createRoot()); + + await expect( + screen.findByText('Sign in page'), + ).resolves.toBeInTheDocument(); + }); + + it('should render the form redirect on sign-in', async () => { + const submitSpy = jest + .spyOn(HTMLFormElement.prototype, 'submit') + .mockReturnValue(); + + const app = createApp({ + advanced: { + configLoader: async () => ({ config: mockApis.config() }), + }, + features: [ + appModulePublicSignIn, + createFrontendModule({ + pluginId: 'app', + extensions: [ + SignInPageBlueprint.make({ + params: { + loader: + async () => + ({ onSignInSuccess }) => { + useEffect(() => { + onSignInSuccess( + mockApis.identity({ token: 'mock-token' }), + ); + }, [onSignInSuccess]); + return
    ; + }, + }, + }), + ], + }), + ], + }); + + const { baseElement } = render(app.createRoot()); + + await waitFor(() => { + expect(submitSpy).toHaveBeenCalled(); + }); + + expect(baseElement).toMatchInlineSnapshot(` + +
    +
    + + + +
    +
    + + `); + }); +}); diff --git a/plugins/app/src/alpha/appModulePublicSignIn.tsx b/plugins/app/src/alpha/appModulePublicSignIn.tsx new file mode 100644 index 0000000000..4a188d1d5f --- /dev/null +++ b/plugins/app/src/alpha/appModulePublicSignIn.tsx @@ -0,0 +1,126 @@ +/* + * 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 appPlugin from '@backstage/plugin-app'; +import { useAsync, useMountEffect } from '@react-hookz/web'; +import { + coreExtensionData, + createFrontendModule, + identityApiRef, + useApi, +} from '@backstage/frontend-plugin-api'; + +// This is a copy of the CookieAuthRedirect component from the auth-react +// plugin, to avoid a dependency on that package. Long-term we want this to be +// the only implementation and remove the one in auth-react once the old frontend system is gone. + +// TODO(Rugvip): Should this be part of the app plugin instead? since it owns the backend part of it. + +/** @internal */ +export function InternalCookieAuthRedirect() { + const identityApi = useApi(identityApiRef); + + const [state, actions] = useAsync(async () => { + const { token } = await identityApi.getCredentials(); + if (!token) { + throw new Error('Expected Backstage token in sign-in response'); + } + return token; + }); + + useMountEffect(actions.execute); + + if (state.status === 'error' && state.error) { + return <>An error occurred: {state.error.message}; + } + + if (state.status === 'success' && state.result) { + return ( +
    form?.submit()} + action={window.location.href} + method="POST" + style={{ visibility: 'hidden' }} + > + + + +
    + ); + } + + return null; +} + +/** + * This module is intended for use in public sign-in page apps, in the + * `index-public-experimental.tsx` file. + * + * @remarks + * + * This module is used to enable the public sign-in flow where the build output + * is split into one small publicly accessible app, and the full app protected + * by auth. + * + * This module overrides the `app/layout` extension, which means that most + * extension typically installed in an app will be ignored. However, you can + * still for example install API and root element extensions. + * + * A typical setup of this app will only install a custom sign-in page. + * + * @example + * + *#### In `index-public-experimental.tsx` + * + *```ts + *import { createApp } from '@backstage/frontend-defaults + *import { appModulePublicSignIn } from '@backstage/plugin-app/alpha'; + *import { appModuleSignInPage } from './appModuleSignInPage'; + * + *const app = createApp({ + * features: [appModuleSignInPage, appModulePublicSignIn], + *}); + *``` + * + *#### In `appModuleSignInPage.tsx` + * + *```tsx + *import { createFrontendModule, SignInPageBlueprint } from '@backstage/frontend-plugin-api'; + * + *export const appModuleSignInPage = createFrontendModule({ + * pluginId: 'app', + * extensions: [ + * SignInPageBlueprint.make({ + * params: { + * ... + * } + * }), + * ], + *}) + *``` + * + * @alpha + */ +export const appModulePublicSignIn = createFrontendModule({ + pluginId: 'app', + extensions: [ + appPlugin.getExtension('app/layout').override({ + factory: () => [ + coreExtensionData.reactElement(), + ], + }), + ], +}); diff --git a/plugins/app/src/alpha/index.ts b/plugins/app/src/alpha/index.ts new file mode 100644 index 0000000000..8ffa92e8aa --- /dev/null +++ b/plugins/app/src/alpha/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { appModulePublicSignIn } from './appModulePublicSignIn'; diff --git a/plugins/app/src/apis/DefaultDialogApi.ts b/plugins/app/src/apis/DefaultDialogApi.ts index a05f9b17a1..49fbf1c5e6 100644 --- a/plugins/app/src/apis/DefaultDialogApi.ts +++ b/plugins/app/src/apis/DefaultDialogApi.ts @@ -19,7 +19,7 @@ import { DialogApi, DialogApiDialog } from '@backstage/frontend-plugin-api'; export type OnShowDialog = (options: { component: (props: { dialog: DialogApiDialog }) => React.JSX.Element; modal: boolean; -}) => DialogApiDialog; +}) => DialogApiDialog; /** * Default implementation for the {@link DialogApi}. @@ -28,7 +28,7 @@ export type OnShowDialog = (options: { export class DefaultDialogApi implements DialogApi { #onShow?: OnShowDialog; - show( + show( elementOrComponent: | JSX.Element | ((props: { @@ -47,7 +47,7 @@ export class DefaultDialogApi implements DialogApi { }) as DialogApiDialog; } - showModal( + showModal( elementOrComponent: | JSX.Element | ((props: { dialog: DialogApiDialog }) => JSX.Element), diff --git a/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.test.tsx b/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.test.tsx new file mode 100644 index 0000000000..07ef891926 --- /dev/null +++ b/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.test.tsx @@ -0,0 +1,283 @@ +/* + * 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 { + ApiBlueprint, + createExtensionInput, + createFrontendModule, + createSwappableComponent, + SwappableComponentBlueprint, + swappableComponentsApiRef, +} from '@backstage/frontend-plugin-api'; +import { DefaultSwappableComponentsApi } from './DefaultSwappableComponentsApi'; +import { render, screen } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/frontend-test-utils'; + +const { ref: testRefA } = createSwappableComponent({ id: 'test.a' }); +const { ref: testRefB1 } = createSwappableComponent({ id: 'test.b' }); +const { ref: testRefB2 } = createSwappableComponent({ id: 'test.b' }); + +describe('DefaultSwappableComponentsApi', () => { + it('should provide components', async () => { + const api = DefaultSwappableComponentsApi.fromComponents([ + { + ref: testRefA, + loader: () => () =>
    test.a
    , + }, + ]); + + const ComponentA = api.getComponent(testRefA); + + render(); + + await expect(screen.findByText('test.a')).resolves.toBeInTheDocument(); + }); + + it('should key extension refs by ID', async () => { + const mockLoader = jest.fn(() =>
    test.b
    ); + const api = DefaultSwappableComponentsApi.fromComponents([ + { + ref: testRefB1, + loader: () => mockLoader, + }, + ]); + + const ComponentB2 = api.getComponent(testRefB2); + + render(); + + await expect(screen.findByText('test.b')).resolves.toBeInTheDocument(); + }); + + describe('integration tests', () => { + const api = ApiBlueprint.makeWithOverrides({ + name: 'swappable-components', + inputs: { + components: createExtensionInput([ + SwappableComponentBlueprint.dataRefs.component, + ]), + }, + factory: (originalFactory, { inputs }) => { + return originalFactory(defineParams => + defineParams({ + api: swappableComponentsApiRef, + deps: {}, + factory: () => + DefaultSwappableComponentsApi.fromComponents( + inputs.components.map(i => + i.get(SwappableComponentBlueprint.dataRefs.component), + ), + ), + }), + ); + }, + }); + + describe('no api provided', () => { + it('should render the fallback if no other component is provided', async () => { + const MockComponent = createSwappableComponent({ + id: 'test.mock', + }); + + renderInTestApp(, {}); + + await expect( + screen.findByTestId('test.mock'), + ).resolves.toBeInTheDocument(); + }); + + it('should render the default compnoent if no other component is provided', async () => { + const MockComponent = createSwappableComponent({ + id: 'test.mock', + loader: () => () =>
    test.mock
    , + }); + + renderInTestApp(, {}); + + await expect( + screen.findByText('test.mock'), + ).resolves.toBeInTheDocument(); + }); + + it('should render async loader component if no other component is provided', async () => { + const MockComponent = createSwappableComponent({ + id: 'test.mock', + loader: async () => () =>
    test.mock
    , + }); + + renderInTestApp(, {}); + + await expect( + screen.findByText('test.mock'), + ).resolves.toBeInTheDocument(); + }); + + it('should transform props correctly', async () => { + const MockComponent = createSwappableComponent({ + id: 'test.mock', + loader: () => (props: { inner: string }) => +
    inner: {props.inner}
    , + transformProps: (props: { external: string }) => ({ + inner: props.external, + }), + }); + + renderInTestApp(, {}); + + await expect( + screen.findByText('inner: test'), + ).resolves.toBeInTheDocument(); + }); + }); + + describe('with overrides', () => { + it('should render the fallback if no other component is provided', async () => { + const MockComponent = createSwappableComponent({ + id: 'test.mock', + }); + + renderInTestApp(, { + extensions: [api], + }); + + await expect( + screen.findByTestId('test.mock'), + ).resolves.toBeInTheDocument(); + }); + + it('should render the default compnoent if no other component is provided', async () => { + const MockComponent = createSwappableComponent({ + id: 'test.mock', + loader: () => () =>
    test.mock
    , + }); + + renderInTestApp(, { + extensions: [api], + }); + + await expect( + screen.findByText('test.mock'), + ).resolves.toBeInTheDocument(); + }); + + it('should render async loader component if no other component is provided', async () => { + const MockComponent = createSwappableComponent({ + id: 'test.mock', + loader: async () => () =>
    test.mock
    , + }); + + renderInTestApp(, { + extensions: [api], + }); + + await expect( + screen.findByText('test.mock'), + ).resolves.toBeInTheDocument(); + }); + + it('should render the component provided by the blueprint', async () => { + const MockComponent = createSwappableComponent({ + id: 'test.mock', + loader: () => () =>
    test.mock
    , + }); + + const override = SwappableComponentBlueprint.make({ + params: define => + define({ + component: MockComponent, + loader: () => () =>
    Overridden!
    , + }), + }); + + renderInTestApp(, { + extensions: [api], + features: [ + createFrontendModule({ + pluginId: 'app', + extensions: [override], + }), + ], + }); + + await expect( + screen.findByText('Overridden!'), + ).resolves.toBeInTheDocument(); + }); + + it('should render the async component provided by the blueprint', async () => { + const MockComponent = createSwappableComponent({ + id: 'test.mock', + loader: () => () =>
    test.mock
    , + }); + + const override = SwappableComponentBlueprint.make({ + params: define => + define({ + component: MockComponent, + loader: async () => () =>
    Overridden!
    , + }), + }); + + renderInTestApp(, { + extensions: [api], + features: [ + createFrontendModule({ + pluginId: 'app', + extensions: [override], + }), + ], + }); + + await expect( + screen.findByText('Overridden!'), + ).resolves.toBeInTheDocument(); + }); + + it('should transform props correctly', async () => { + const MockComponent = createSwappableComponent({ + id: 'test.mock', + loader: () => (props: { inner: string }) => +
    inner: {props.inner}
    , + transformProps: (props: { external: string }) => ({ + inner: props.external, + }), + }); + + const override = SwappableComponentBlueprint.make({ + params: define => + define({ + component: MockComponent, + loader: () => props =>
    overridden: {props.inner}
    , + }), + }); + + renderInTestApp(, { + extensions: [api], + features: [ + createFrontendModule({ + pluginId: 'app', + extensions: [override], + }), + ], + }); + + await expect( + screen.findByText('overridden: test'), + ).resolves.toBeInTheDocument(); + }); + }); + }); +}); diff --git a/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.tsx b/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.tsx new file mode 100644 index 0000000000..593fff24ff --- /dev/null +++ b/plugins/app/src/apis/SwappableComponentsApi/DefaultSwappableComponentsApi.tsx @@ -0,0 +1,74 @@ +/* + * 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 { + SwappableComponentRef, + SwappableComponentsApi, + SwappableComponentBlueprint, +} from '@backstage/frontend-plugin-api'; +import { OpaqueSwappableComponentRef } from '@internal/frontend'; + +import { lazy } from 'react'; + +/** + * Implementation for the {@link SwappableComponentsApi} + * + * @internal + */ +export class DefaultSwappableComponentsApi implements SwappableComponentsApi { + #components: Map JSX.Element | null) | undefined>; + + static fromComponents( + components: Array, + ) { + return new DefaultSwappableComponentsApi( + new Map( + components.map(entry => { + return [ + entry.ref.id, + entry.loader + ? lazy(async () => ({ + default: await entry.loader!(), + })) + : undefined, + ]; + }), + ), + ); + } + + constructor(components: Map) { + this.#components = components; + } + + getComponent( + ref: SwappableComponentRef, + ): (props: object) => JSX.Element | null { + const OverrideComponent = this.#components.get(ref.id); + const { defaultComponent: DefaultComponent, transformProps } = + OpaqueSwappableComponentRef.toInternal(ref); + + return (props: object) => { + const innerProps = transformProps?.(props) ?? props; + + if (OverrideComponent) { + return ; + } + + return ; + }; + } +} diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts b/plugins/app/src/apis/SwappableComponentsApi/index.ts similarity index 88% rename from packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts rename to plugins/app/src/apis/SwappableComponentsApi/index.ts index 18604f15de..20f90eb32d 100644 --- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts +++ b/plugins/app/src/apis/SwappableComponentsApi/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { DefaultComponentsApi } from './DefaultComponentsApi'; +export { DefaultSwappableComponentsApi } from './DefaultSwappableComponentsApi'; diff --git a/plugins/app/src/defaultApis.ts b/plugins/app/src/defaultApis.ts index 535be78e05..0337f6c4f4 100644 --- a/plugins/app/src/defaultApis.ts +++ b/plugins/app/src/defaultApis.ts @@ -17,7 +17,6 @@ // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { AlertApiForwarder, - NoOpAnalyticsApi, ErrorApiForwarder, ErrorAlerter, GoogleAuth, @@ -39,9 +38,7 @@ import { } from '../../../packages/core-app-api/src/apis/implementations'; import { - createApiFactory, alertApiRef, - analyticsApiRef, errorApiRef, discoveryApiRef, fetchApiRef, @@ -71,22 +68,22 @@ import { IdentityPermissionApi, } from '@backstage/plugin-permission-react'; import { DefaultDialogApi } from './apis/DefaultDialogApi'; +import { analyticsApi } from './extensions/AnalyticsApi'; export const apis = [ ApiBlueprint.make({ name: 'dialog', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: dialogApiRef, deps: {}, factory: () => new DefaultDialogApi(), }), - }, }), ApiBlueprint.make({ name: 'discovery', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: discoveryApiRef, deps: { configApi: configApiRef }, factory: ({ configApi }) => @@ -94,32 +91,21 @@ export const apis = [ `${configApi.getString('backend.baseUrl')}/api/{{ pluginId }}`, ), }), - }, }), ApiBlueprint.make({ name: 'alert', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: alertApiRef, deps: {}, factory: () => new AlertApiForwarder(), }), - }, - }), - ApiBlueprint.make({ - name: 'analytics', - params: { - factory: createApiFactory({ - api: analyticsApiRef, - deps: {}, - factory: () => new NoOpAnalyticsApi(), - }), - }, }), + analyticsApi, ApiBlueprint.make({ name: 'error', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: errorApiRef, deps: { alertApi: alertApiRef }, factory: ({ alertApi }) => { @@ -128,22 +114,20 @@ export const apis = [ return errorApi; }, }), - }, }), ApiBlueprint.make({ name: 'storage', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: storageApiRef, deps: { errorApi: errorApiRef }, factory: ({ errorApi }) => WebStorage.create({ errorApi }), }), - }, }), ApiBlueprint.make({ name: 'fetch', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: fetchApiRef, deps: { configApi: configApiRef, @@ -164,22 +148,20 @@ export const apis = [ }); }, }), - }, }), ApiBlueprint.make({ name: 'oauth-request', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: oauthRequestApiRef, deps: {}, factory: () => new OAuthRequestManager(), }), - }, }), ApiBlueprint.make({ name: 'google-auth', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: googleAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -194,12 +176,11 @@ export const apis = [ environment: configApi.getOptionalString('auth.environment'), }), }), - }, }), ApiBlueprint.make({ name: 'microsoft-auth', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: microsoftAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -214,12 +195,11 @@ export const apis = [ environment: configApi.getOptionalString('auth.environment'), }), }), - }, }), ApiBlueprint.make({ name: 'github-auth', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: githubAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -235,12 +215,11 @@ export const apis = [ environment: configApi.getOptionalString('auth.environment'), }), }), - }, }), ApiBlueprint.make({ name: 'okta-auth', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: oktaAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -255,12 +234,11 @@ export const apis = [ environment: configApi.getOptionalString('auth.environment'), }), }), - }, }), ApiBlueprint.make({ name: 'gitlab-auth', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: gitlabAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -275,12 +253,11 @@ export const apis = [ environment: configApi.getOptionalString('auth.environment'), }), }), - }, }), ApiBlueprint.make({ name: 'onelogin-auth', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: oneloginAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -295,12 +272,11 @@ export const apis = [ environment: configApi.getOptionalString('auth.environment'), }), }), - }, }), ApiBlueprint.make({ name: 'bitbucket-auth', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: bitbucketAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -316,12 +292,11 @@ export const apis = [ environment: configApi.getOptionalString('auth.environment'), }), }), - }, }), ApiBlueprint.make({ name: 'bitbucket-server-auth', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: bitbucketServerAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -337,12 +312,11 @@ export const apis = [ environment: configApi.getOptionalString('auth.environment'), }), }), - }, }), ApiBlueprint.make({ name: 'atlassian-auth', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: atlassianAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -358,12 +332,11 @@ export const apis = [ }); }, }), - }, }), ApiBlueprint.make({ name: 'vmware-cloud-auth', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: vmwareCloudAuthApiRef, deps: { discoveryApi: discoveryApiRef, @@ -379,12 +352,11 @@ export const apis = [ }); }, }), - }, }), ApiBlueprint.make({ name: 'permission', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: permissionApiRef, deps: { discovery: discoveryApiRef, @@ -394,22 +366,18 @@ export const apis = [ factory: ({ config, discovery, identity }) => IdentityPermissionApi.create({ config, discovery, identity }), }), - }, }), ApiBlueprint.make({ name: 'scm-auth', - params: { - factory: ScmAuth.createDefaultApiFactory(), - }, + params: defineParams => defineParams(ScmAuth.createDefaultApiFactory()), }), ApiBlueprint.make({ name: 'scm-integrations', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: scmIntegrationsApiRef, deps: { configApi: configApiRef }, factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), }), - }, }), ] as const; diff --git a/plugins/app/src/extensions/AnalyticsApi.test.ts b/plugins/app/src/extensions/AnalyticsApi.test.ts new file mode 100644 index 0000000000..68796895d2 --- /dev/null +++ b/plugins/app/src/extensions/AnalyticsApi.test.ts @@ -0,0 +1,162 @@ +/* + * 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 { createExtensionTester } from '@backstage/frontend-test-utils'; +import { analyticsApi } from './AnalyticsApi'; +import { + AnalyticsImplementationBlueprint, + AnalyticsImplementation, + ApiBlueprint, + configApiRef, + createExtension, + identityApiRef, +} from '@backstage/frontend-plugin-api'; + +describe('analyticsApi', () => { + const mockEvent = { + action: '', + subject: '', + context: { + pluginId: '', + extensionId: '', + routeRef: '', + }, + }; + const captureEventSpy1 = jest.fn(); + const MockImplementation1 = createExtension({ + name: 'mock-implementation-1', + attachTo: { id: 'api:analytics', input: 'implementations' }, + output: [AnalyticsImplementationBlueprint.dataRefs.factory], + factory() { + return [ + AnalyticsImplementationBlueprint.dataRefs.factory({ + deps: { configApi: configApiRef }, + factory: deps => ({ + captureEvent: event => captureEventSpy1(event, deps), + }), + }), + ]; + }, + }); + const captureEventSpy2 = jest.fn(); + const MockImplementation2 = createExtension({ + name: 'mock-implementation-2', + attachTo: { id: 'api:analytics', input: 'implementations' }, + output: [AnalyticsImplementationBlueprint.dataRefs.factory], + factory() { + return [ + AnalyticsImplementationBlueprint.dataRefs.factory({ + deps: { config: configApiRef, identityApi: identityApiRef }, + factory: deps => ({ + captureEvent: event => captureEventSpy2(event, deps), + }), + }), + ]; + }, + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('wires up a single AnalyticsImplementationFactory', async () => { + // Given the Analytics API and a single mock implementation + const tester = createExtensionTester(analyticsApi).add(MockImplementation1); + const apiFactory = tester.get(ApiBlueprint.dataRefs.factory); + + // Then the API's deps should contain the mock implementation's deps. + expect(apiFactory.deps).toMatchObject({ + 'core.config': expect.anything(), + }); + + // And the returned instance should be an Analytics Implementation + const concreteImplementation = apiFactory.factory({ + 'core.config': 'ConfigApiImplementation', + }); + expect(concreteImplementation).toHaveProperty('captureEvent'); + + // When the resulting API's captureEvent method is called + (concreteImplementation as AnalyticsImplementation).captureEvent(mockEvent); + + // Then the mock implementation's captureEvent should have been called + expect(captureEventSpy1.mock.calls[0][0]).toEqual(mockEvent); + + // And the mock's factory should have resolved the expected deps + expect(captureEventSpy1.mock.calls[0][1]).toEqual({ + configApi: 'ConfigApiImplementation', + }); + }); + + it('wires up more than one AnalyticsImplementationFactory', () => { + // Given the Analytics API and two mock implementations + const tester = createExtensionTester(analyticsApi) + .add(MockImplementation1) + .add(MockImplementation2); + const apiFactory = tester.get(ApiBlueprint.dataRefs.factory); + + // Then the API's deps should contain the mock implementations' deps. + expect(apiFactory.deps).toMatchObject({ + 'core.config': expect.anything(), + 'core.identity': expect.anything(), + }); + + // And the returned instance should be an Analytics Implementation + const concreteImplementation = apiFactory.factory({ + 'core.config': 'ConfigApiImplementation', + 'core.identity': 'IdentityApiImplementation', + }); + expect(concreteImplementation).toHaveProperty('captureEvent'); + + // When the resulting API's captureEvent method is called + (concreteImplementation as AnalyticsImplementation).captureEvent(mockEvent); + + // Then the 1st mock implementation's captureEvent should have been called + expect(captureEventSpy1.mock.calls[0][0]).toEqual(mockEvent); + + // And the 1st mock's factory should have resolved the expected deps + expect(captureEventSpy1.mock.calls[0][1]).toEqual({ + configApi: 'ConfigApiImplementation', + }); + + // And the 2nd mock implementation's captureEvent should have been called + expect(captureEventSpy2.mock.calls[0][0]).toEqual(mockEvent); + + // And the 2nd mock's factory should have resolved the expected deps + expect(captureEventSpy2.mock.calls[0][1]).toEqual({ + config: 'ConfigApiImplementation', + identityApi: 'IdentityApiImplementation', + }); + }); + + it('works fine with no AnalyticsImplementationFactory instances provided', () => { + // Given the Analytics API and no mock implementations + const tester = createExtensionTester(analyticsApi); + const apiFactory = tester.get(ApiBlueprint.dataRefs.factory); + + // Then the API's deps should be empty + expect(apiFactory.deps).toEqual({}); + + // And the returned instance should be an Analytics Implementation + const concreteImplementation = apiFactory.factory({}); + expect(concreteImplementation).toHaveProperty('captureEvent'); + + // Invoking the API's captureEvent method should result in no errors + expect(() => + (concreteImplementation as AnalyticsImplementation).captureEvent( + mockEvent, + ), + ).not.toThrow(); + }); +}); diff --git a/plugins/app/src/extensions/AnalyticsApi.ts b/plugins/app/src/extensions/AnalyticsApi.ts new file mode 100644 index 0000000000..e969649262 --- /dev/null +++ b/plugins/app/src/extensions/AnalyticsApi.ts @@ -0,0 +1,86 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + analyticsApiRef, + AnalyticsImplementationBlueprint, + ApiBlueprint, + ApiRef, + createExtensionInput, +} from '@backstage/frontend-plugin-api'; + +export const analyticsApi = ApiBlueprint.makeWithOverrides({ + name: 'analytics', + inputs: { + implementations: createExtensionInput([ + AnalyticsImplementationBlueprint.dataRefs.factory, + ]), + }, + factory(originalFactory, { inputs }) { + // Pull out and aggregate deps from every implementation input into an + // object keyed by the apiRef ID to be passed to this API implementation as + // if they were its own deps. + const aggregatedDeps = inputs.implementations + .flatMap>(impls => + Object.values( + impls.get(AnalyticsImplementationBlueprint.dataRefs.factory).deps, + ), + ) + .reduce<{ [x: string]: ApiRef }>((accum, ref) => { + accum[ref.id] = ref; + return accum; + }, {}); + + return originalFactory(defineParams => + defineParams({ + api: analyticsApiRef, + deps: aggregatedDeps, + factory: analyticsApiDeps => { + const actualApis = inputs.implementations + .map(impl => + impl.get(AnalyticsImplementationBlueprint.dataRefs.factory), + ) + .map(({ factory, deps }) => + factory( + // Reconstruct a deps argument to pass to this analytics + // implementation factory from those passed into ours. + Object.keys(deps).reduce<{ [x: string]: ApiRef }>( + (accum, dep) => { + accum[dep] = analyticsApiDeps[ + (deps as { [x: string]: ApiRef })[dep].id + ] as ApiRef; + return accum; + }, + {}, + ), + ), + ); + return { + captureEvent: event => { + actualApis.forEach(api => { + try { + api.captureEvent(event); + } catch { + /* ignored */ + } + }); + }, + }; + }, + }), + ); + }, +}); diff --git a/plugins/app/src/extensions/AppLanguageApi.ts b/plugins/app/src/extensions/AppLanguageApi.ts index 7a6c10db96..325dffa2fd 100644 --- a/plugins/app/src/extensions/AppLanguageApi.ts +++ b/plugins/app/src/extensions/AppLanguageApi.ts @@ -17,14 +17,14 @@ // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { AppLanguageSelector } from '../../../../packages/core-app-api/src/apis/implementations/AppLanguageApi'; import { appLanguageApiRef } from '@backstage/core-plugin-api/alpha'; -import { ApiBlueprint, createApiFactory } from '@backstage/frontend-plugin-api'; +import { ApiBlueprint } from '@backstage/frontend-plugin-api'; export const AppLanguageApi = ApiBlueprint.make({ name: 'app-language', - params: { - factory: createApiFactory( - appLanguageApiRef, - AppLanguageSelector.createWithStorage(), - ), - }, + params: defineParams => + defineParams({ + api: appLanguageApiRef, + deps: {}, + factory: () => AppLanguageSelector.createWithStorage(), + }), }); diff --git a/plugins/app/src/extensions/AppNav.tsx b/plugins/app/src/extensions/AppNav.tsx index eaec264dca..aa030300fe 100644 --- a/plugins/app/src/extensions/AppNav.tsx +++ b/plugins/app/src/extensions/AppNav.tsx @@ -18,93 +18,93 @@ import { createExtension, coreExtensionData, createExtensionInput, - useRouteRef, NavItemBlueprint, - NavLogoBlueprint, + NavContentBlueprint, + NavContentComponentProps, + routeResolutionApiRef, + IconComponent, + RouteRef, + useApi, + NavContentComponent, } from '@backstage/frontend-plugin-api'; -import { makeStyles } from '@material-ui/core/styles'; -import { - Sidebar, - useSidebarOpenState, - Link, - sidebarConfig, - SidebarDivider, - SidebarItem, -} from '@backstage/core-components'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import LogoIcon from '../../../../packages/app/src/components/Root/LogoIcon'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import LogoFull from '../../../../packages/app/src/components/Root/LogoFull'; - -const useSidebarLogoStyles = makeStyles({ - root: { - width: sidebarConfig.drawerWidthClosed, - height: 3 * sidebarConfig.logoHeight, - display: 'flex', - flexFlow: 'row nowrap', - alignItems: 'center', - marginBottom: -14, - }, - link: { - width: sidebarConfig.drawerWidthClosed, - marginLeft: 24, - }, -}); - -const SidebarLogo = ( - props: (typeof NavLogoBlueprint.dataRefs.logoElements)['T'], -) => { - const classes = useSidebarLogoStyles(); - const { isOpen } = useSidebarOpenState(); +import { Sidebar, SidebarItem } from '@backstage/core-components'; +import { useMemo } from 'react'; +function DefaultNavContent(props: NavContentComponentProps) { return ( -
    - - {isOpen - ? props?.logoFull ?? - : props?.logoIcon ?? } - -
    + + {props.items.map((item, index) => ( + + ))} + ); -}; +} -const SidebarNavItem = ( - props: (typeof NavItemBlueprint.dataRefs.target)['T'], -) => { - const { icon: Icon, title, routeRef } = props; - const link = useRouteRef(routeRef); - if (!link) { - return null; - } - // TODO: Support opening modal, for example, the search one - return ; -}; +// This helps defer rendering until the app is being rendered, which is needed +// because the RouteResolutionApi can't be called until the app has been fully initialized. +function NavContentRenderer(props: { + Content: NavContentComponent; + items: Array<{ + title: string; + icon: IconComponent; + routeRef: RouteRef; + }>; +}) { + const routeResolutionApi = useApi(routeResolutionApiRef); + + const items = useMemo(() => { + return props.items.flatMap(item => { + const link = routeResolutionApi.resolve(item.routeRef); + if (!link) { + // eslint-disable-next-line no-console + console.warn( + `NavItemBlueprint: unable to resolve route ref ${item.routeRef}`, + ); + return []; + } + return [ + { + to: link(), + text: item.title, + icon: item.icon, + title: item.title, + routeRef: item.routeRef, + }, + ]; + }); + }, [props.items, routeResolutionApi]); + + return ; +} export const AppNav = createExtension({ name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, inputs: { items: createExtensionInput([NavItemBlueprint.dataRefs.target]), - logos: createExtensionInput([NavLogoBlueprint.dataRefs.logoElements], { + content: createExtensionInput([NavContentBlueprint.dataRefs.component], { singleton: true, optional: true, }), }, output: [coreExtensionData.reactElement], - factory: ({ inputs }) => [ - coreExtensionData.reactElement( - - - - {inputs.items.map((item, index) => ( - - ))} - , - ), - ], + *factory({ inputs }) { + const Content = + inputs.content?.get(NavContentBlueprint.dataRefs.component) ?? + DefaultNavContent; + + yield coreExtensionData.reactElement( + + item.get(NavItemBlueprint.dataRefs.target), + )} + Content={Content} + />, + ); + }, }); diff --git a/plugins/app/src/extensions/AppRoot.tsx b/plugins/app/src/extensions/AppRoot.tsx index 17e8aa3b1f..9ce2b055dd 100644 --- a/plugins/app/src/extensions/AppRoot.tsx +++ b/plugins/app/src/extensions/AppRoot.tsx @@ -182,7 +182,7 @@ type RouteResolverProxy = { export interface AppRouterProps { children?: ReactNode; SignInPageComponent?: ComponentType; - RouterComponent?: ComponentType>; + RouterComponent?: (props: { children: ReactNode }) => JSX.Element | null; extraElements?: Array; } diff --git a/plugins/app/src/extensions/AppRoutes.tsx b/plugins/app/src/extensions/AppRoutes.tsx index 304b0b0289..5cc3c3f4b8 100644 --- a/plugins/app/src/extensions/AppRoutes.tsx +++ b/plugins/app/src/extensions/AppRoutes.tsx @@ -18,8 +18,7 @@ import { createExtension, coreExtensionData, createExtensionInput, - coreComponentRefs, - useComponentRef, + NotFoundErrorPage, } from '@backstage/frontend-plugin-api'; import { useRoutes } from 'react-router-dom'; @@ -36,13 +35,11 @@ export const AppRoutes = createExtension({ output: [coreExtensionData.reactElement], factory({ inputs }) { const Routes = () => { - const NotFoundErrorPage = useComponentRef( - coreComponentRefs.notFoundErrorPage, - ); - const element = useRoutes([ ...inputs.routes.map(route => ({ - path: `${route.get(coreExtensionData.routePath)}/*`, + path: `${route + .get(coreExtensionData.routePath) + .replace(/\/$/, '')}/*`, element: route.get(coreExtensionData.reactElement), })), { diff --git a/plugins/app/src/extensions/AppThemeApi.tsx b/plugins/app/src/extensions/AppThemeApi.tsx index 2d35a50cd5..e3c516ca69 100644 --- a/plugins/app/src/extensions/AppThemeApi.tsx +++ b/plugins/app/src/extensions/AppThemeApi.tsx @@ -24,7 +24,6 @@ import { createExtensionInput, ThemeBlueprint, ApiBlueprint, - createApiFactory, appThemeApiRef, } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports @@ -41,14 +40,16 @@ export const AppThemeApi = ApiBlueprint.makeWithOverrides({ }), }, factory: (originalFactory, { inputs }) => { - return originalFactory({ - factory: createApiFactory( - appThemeApiRef, - AppThemeSelector.createWithStorage( - inputs.themes.map(i => i.get(ThemeBlueprint.dataRefs.theme)), - ), - ), - }); + return originalFactory(defineParams => + defineParams({ + api: appThemeApiRef, + deps: {}, + factory: () => + AppThemeSelector.createWithStorage( + inputs.themes.map(i => i.get(ThemeBlueprint.dataRefs.theme)), + ), + }), + ); }, }); diff --git a/plugins/app/src/extensions/ComponentsApi.tsx b/plugins/app/src/extensions/ComponentsApi.tsx deleted file mode 100644 index b578429253..0000000000 --- a/plugins/app/src/extensions/ComponentsApi.tsx +++ /dev/null @@ -1,50 +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 { - createComponentExtension, - createExtensionInput, - ApiBlueprint, - createApiFactory, - componentsApiRef, -} from '@backstage/frontend-plugin-api'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { DefaultComponentsApi } from '../../../../packages/frontend-app-api/src/apis/implementations/ComponentsApi'; - -/** - * Contains the shareable components installed into the app. - */ -export const ComponentsApi = ApiBlueprint.makeWithOverrides({ - name: 'components', - inputs: { - components: createExtensionInput( - [createComponentExtension.componentDataRef], - { replaces: [{ id: 'app', input: 'components' }] }, - ), - }, - factory: (originalFactory, { inputs }) => { - return originalFactory({ - factory: createApiFactory( - componentsApiRef, - DefaultComponentsApi.fromComponents( - inputs.components.map(i => - i.get(createComponentExtension.componentDataRef), - ), - ), - ), - }); - }, -}); diff --git a/plugins/app/src/extensions/DialogDisplay.tsx b/plugins/app/src/extensions/DialogDisplay.tsx index ba74aa7172..688641650e 100644 --- a/plugins/app/src/extensions/DialogDisplay.tsx +++ b/plugins/app/src/extensions/DialogDisplay.tsx @@ -31,7 +31,7 @@ function getDialogId() { return dialogId.toString(36); } -type DialogState = DialogApiDialog & { +type DialogState = DialogApiDialog & { id: string; modal: boolean; }; diff --git a/plugins/app/src/extensions/FeatureFlagsApi.ts b/plugins/app/src/extensions/FeatureFlagsApi.ts index 40a44d1348..b0370f4c9d 100644 --- a/plugins/app/src/extensions/FeatureFlagsApi.ts +++ b/plugins/app/src/extensions/FeatureFlagsApi.ts @@ -16,7 +16,6 @@ import { ApiBlueprint, - createApiFactory, featureFlagsApiRef, } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports @@ -27,12 +26,11 @@ import { LocalStorageFeatureFlags } from '../../../../packages/core-app-api/src/ */ export const FeatureFlagsApi = ApiBlueprint.make({ name: 'feature-flags', - params: { - // TODO: properly discovery feature flags, maybe rework the whole thing - factory: createApiFactory({ + params: defineParams => + defineParams({ + // TODO: properly discovery feature flags, maybe rework the whole thing api: featureFlagsApiRef, deps: {}, factory: () => new LocalStorageFeatureFlags(), }), - }, }); diff --git a/plugins/app/src/extensions/IconsApi.ts b/plugins/app/src/extensions/IconsApi.ts index e73a74132a..fcce9ea718 100644 --- a/plugins/app/src/extensions/IconsApi.ts +++ b/plugins/app/src/extensions/IconsApi.ts @@ -18,7 +18,6 @@ import { createExtensionInput, IconBundleBlueprint, ApiBlueprint, - createApiFactory, iconsApiRef, } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports @@ -37,15 +36,17 @@ export const IconsApi = ApiBlueprint.makeWithOverrides({ }), }, factory: (originalFactory, { inputs }) => { - return originalFactory({ - factory: createApiFactory( - iconsApiRef, - new DefaultIconsApi( - inputs.icons - .map(i => i.get(IconBundleBlueprint.dataRefs.icons)) - .reduce((acc, bundle) => ({ ...acc, ...bundle }), defaultIcons), - ), - ), - }); + return originalFactory(defineParams => + defineParams({ + api: iconsApiRef, + deps: {}, + factory: () => + new DefaultIconsApi( + inputs.icons + .map(i => i.get(IconBundleBlueprint.dataRefs.icons)) + .reduce((acc, bundle) => ({ ...acc, ...bundle }), defaultIcons), + ), + }), + ); }, }); diff --git a/plugins/app/src/extensions/LegacyComponentsApi.ts b/plugins/app/src/extensions/LegacyComponentsApi.ts new file mode 100644 index 0000000000..aa05a52826 --- /dev/null +++ b/plugins/app/src/extensions/LegacyComponentsApi.ts @@ -0,0 +1,58 @@ +/* + * 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 { + ApiBlueprint, + createApiRef, + ErrorDisplay, + NotFoundErrorPage, + Progress, +} from '@backstage/frontend-plugin-api'; +import { ComponentType } from 'react'; + +/** + * This is the old component API that has been replaced by the new SwappableComponentsApi. + * + * This backwards compatibility implementation exists to avoid breaking older plugins. + * + * This was added for the 1.42 release, and can be removed in the future. + * + * @internal + */ +export const LegacyComponentsApi = ApiBlueprint.make({ + name: 'components', + params: defineParams => + defineParams({ + api: createApiRef<{ + getComponent(ref: { id: string }): ComponentType; + }>({ id: 'core.components' }), + deps: {}, + factory: () => ({ + getComponent(ref) { + if (ref.id === 'core.components.progress') { + return Progress; + } + if (ref.id === 'core.components.notFoundErrorPage') { + return NotFoundErrorPage; + } + if (ref.id === 'core.components.errorBoundaryFallback') { + return ErrorDisplay; + } + throw new Error(`No implementation found for component ref ${ref}`); + }, + }), + }), +}); diff --git a/plugins/app/src/extensions/SwappableComponentsApi.ts b/plugins/app/src/extensions/SwappableComponentsApi.ts new file mode 100644 index 0000000000..f261d999c0 --- /dev/null +++ b/plugins/app/src/extensions/SwappableComponentsApi.ts @@ -0,0 +1,67 @@ +/* + * 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 { + SwappableComponentBlueprint, + createExtensionInput, + ApiBlueprint, + swappableComponentsApiRef, +} from '@backstage/frontend-plugin-api'; +import { DefaultSwappableComponentsApi } from '../apis/SwappableComponentsApi'; + +/** + * Contains the shareable components installed into the app. + */ +export const SwappableComponentsApi = ApiBlueprint.makeWithOverrides({ + name: 'swappable-components', + inputs: { + components: createExtensionInput([ + SwappableComponentBlueprint.dataRefs.component, + ]), + }, + factory: (originalFactory, { inputs }) => { + return originalFactory(defineParams => + defineParams({ + api: swappableComponentsApiRef, + deps: {}, + factory: () => { + const nonAppExtensions = inputs.components.filter( + i => i.node.spec.plugin?.id !== 'app', + ); + + if (nonAppExtensions.length > 0) { + // eslint-disable-next-line no-console + console.warn( + `SwappableComponents should only be installed as an extension in the app plugin. You can either use appPlugin.override(), or provide a module for the app-plugin with the extension there instead. Invalid extensions: ${nonAppExtensions + .map(i => i.node.spec.id) + .join(', ')}`, + ); + } + + const appExtensions = inputs.components.filter( + i => i.node.spec.plugin?.id === 'app', + ); + + return DefaultSwappableComponentsApi.fromComponents( + appExtensions.map(i => + i.get(SwappableComponentBlueprint.dataRefs.component), + ), + ); + }, + }), + ); + }, +}); diff --git a/plugins/app/src/extensions/TranslationsApi.tsx b/plugins/app/src/extensions/TranslationsApi.tsx index ed7ab1abdb..31d89a5acb 100644 --- a/plugins/app/src/extensions/TranslationsApi.tsx +++ b/plugins/app/src/extensions/TranslationsApi.tsx @@ -16,7 +16,6 @@ import { ApiBlueprint, TranslationBlueprint, - createApiFactory, createExtensionInput, } from '@backstage/frontend-plugin-api'; import { @@ -39,8 +38,8 @@ export const TranslationsApi = ApiBlueprint.makeWithOverrides({ ), }, factory: (originalFactory, { inputs }) => { - return originalFactory({ - factory: createApiFactory({ + return originalFactory(defineParams => + defineParams({ api: translationApiRef, deps: { languageApi: appLanguageApiRef }, factory: ({ languageApi }) => @@ -51,6 +50,6 @@ export const TranslationsApi = ApiBlueprint.makeWithOverrides({ ), }), }), - }); + ); }, }); diff --git a/plugins/app/src/extensions/components.tsx b/plugins/app/src/extensions/components.tsx index c5fa8411c7..99f6291e87 100644 --- a/plugins/app/src/extensions/components.tsx +++ b/plugins/app/src/extensions/components.tsx @@ -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,41 +13,54 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import Button from '@material-ui/core/Button'; +import { + NotFoundErrorPage as SwappableNotFoundErrorPage, + Progress as SwappableProgress, + ErrorDisplay as SwappableErrorDisplay, + SwappableComponentBlueprint, +} from '@backstage/frontend-plugin-api'; import { - createComponentExtension, - coreComponentRefs, -} from '@backstage/frontend-plugin-api'; -import { ErrorPanel } from '@backstage/core-components'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { components as defaultComponents } from '../../../../packages/app-defaults/src/defaults'; + ErrorPage, + ErrorPanel, + Progress as ProgressComponent, +} from '@backstage/core-components'; +import Button from '@material-ui/core/Button'; -export const DefaultProgressComponent = createComponentExtension({ - ref: coreComponentRefs.progress, - loader: { sync: () => defaultComponents.Progress }, +export const Progress = SwappableComponentBlueprint.make({ + name: 'core-progress', + params: define => + define({ + component: SwappableProgress, + loader: () => ProgressComponent, + }), }); -export const DefaultNotFoundErrorPageComponent = createComponentExtension({ - ref: coreComponentRefs.notFoundErrorPage, - loader: { sync: () => defaultComponents.NotFoundErrorPage }, +export const NotFoundErrorPage = SwappableComponentBlueprint.make({ + name: 'core-not-found-error-page', + params: define => + define({ + component: SwappableNotFoundErrorPage, + loader: () => () => + , + }), }); -export const DefaultErrorBoundaryComponent = createComponentExtension({ - ref: coreComponentRefs.errorBoundaryFallback, - loader: { - sync: () => props => { - const { plugin, error, resetError } = props; - const title = `Error in ${plugin?.id}`; - - return ( - - - - ); - }, - }, +export const ErrorDisplay = SwappableComponentBlueprint.make({ + name: 'core-error-display', + params: define => + define({ + component: SwappableErrorDisplay, + loader: () => props => { + const { plugin, error, resetError } = props; + const title = `Error in ${plugin?.id}`; + return ( + + + + ); + }, + }), }); diff --git a/plugins/app/src/extensions/elements.tsx b/plugins/app/src/extensions/elements.tsx index 5a9644e4ef..b6519ef707 100644 --- a/plugins/app/src/extensions/elements.tsx +++ b/plugins/app/src/extensions/elements.tsx @@ -41,7 +41,7 @@ export const alertDisplayAppRootElement = }, factory: (originalFactory, { config }) => { return originalFactory({ - element: () => , + element: , }); }, }); diff --git a/plugins/app/src/extensions/index.ts b/plugins/app/src/extensions/index.ts index 090e7c2cae..0d3ad84e6f 100644 --- a/plugins/app/src/extensions/index.ts +++ b/plugins/app/src/extensions/index.ts @@ -20,18 +20,15 @@ export { AppNav } from './AppNav'; export { AppRoot } from './AppRoot'; export { AppRoutes } from './AppRoutes'; export { AppThemeApi, DarkTheme, LightTheme } from './AppThemeApi'; -export { ComponentsApi } from './ComponentsApi'; +export { SwappableComponentsApi } from './SwappableComponentsApi'; +export { LegacyComponentsApi } from './LegacyComponentsApi'; export { IconsApi } from './IconsApi'; export { FeatureFlagsApi } from './FeatureFlagsApi'; export { TranslationsApi } from './TranslationsApi'; export { DefaultSignInPage } from './DefaultSignInPage'; export { dialogDisplayAppRootElement } from './DialogDisplay'; -export { - DefaultProgressComponent, - DefaultErrorBoundaryComponent, - DefaultNotFoundErrorPageComponent, -} from './components'; export { oauthRequestDialogAppRootElement, alertDisplayAppRootElement, } from './elements'; +export { Progress, NotFoundErrorPage, ErrorDisplay } from './components'; diff --git a/plugins/app/src/plugin.ts b/plugins/app/src/plugin.ts index b166423fa1..c9deddb4f9 100644 --- a/plugins/app/src/plugin.ts +++ b/plugins/app/src/plugin.ts @@ -25,23 +25,25 @@ import { AppThemeApi, DarkTheme, LightTheme, - ComponentsApi, + SwappableComponentsApi, IconsApi, FeatureFlagsApi, TranslationsApi, - DefaultProgressComponent, - DefaultNotFoundErrorPageComponent, - DefaultErrorBoundaryComponent, oauthRequestDialogAppRootElement, alertDisplayAppRootElement, DefaultSignInPage, dialogDisplayAppRootElement, + Progress, + NotFoundErrorPage, + ErrorDisplay, + LegacyComponentsApi, } from './extensions'; import { apis } from './defaultApis'; /** @public */ export const appPlugin = createFrontendPlugin({ pluginId: 'app', + info: { packageJson: () => import('../package.json') }, extensions: [ ...apis, App, @@ -53,16 +55,17 @@ export const appPlugin = createFrontendPlugin({ AppThemeApi, DarkTheme, LightTheme, - ComponentsApi, + SwappableComponentsApi, IconsApi, FeatureFlagsApi, TranslationsApi, - DefaultProgressComponent, - DefaultNotFoundErrorPageComponent, - DefaultErrorBoundaryComponent, DefaultSignInPage, oauthRequestDialogAppRootElement, alertDisplayAppRootElement, dialogDisplayAppRootElement, + Progress, + NotFoundErrorPage, + ErrorDisplay, + LegacyComponentsApi, ], }); diff --git a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md index 2ce29207f7..81bdd7ce42 100644 --- a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/plugin-auth-backend-module-atlassian-provider +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 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 + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + ## 0.4.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-atlassian-provider/catalog-info.yaml b/plugins/auth-backend-module-atlassian-provider/catalog-info.yaml index d3e2c11c17..dc54d48600 100644 --- a/plugins/auth-backend-module-atlassian-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-atlassian-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-atlassian-provider/config.d.ts b/plugins/auth-backend-module-atlassian-provider/config.d.ts index f6433c123e..2729ad31fd 100644 --- a/plugins/auth-backend-module-atlassian-provider/config.d.ts +++ b/plugins/auth-backend-module-atlassian-provider/config.d.ts @@ -32,12 +32,19 @@ export interface Config { additionalScopes?: string | string[]; signIn?: { resolvers: Array< - | { resolver: 'usernameMatchingUserEntityName' } + | { + resolver: 'usernameMatchingUserEntityName'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } | { resolver: 'emailLocalPartMatchingUserEntityName'; allowedDomains?: string[]; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'emailMatchingUserEntityProfileEmail'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; } - | { resolver: 'emailMatchingUserEntityProfileEmail' } >; }; sessionDuration?: HumanDuration | string; diff --git a/plugins/auth-backend-module-atlassian-provider/package.json b/plugins/auth-backend-module-atlassian-provider/package.json index e458540b8f..01fa23f23e 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-next.1", + "version": "0.4.6-next.0", "description": "The atlassian-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -38,7 +38,8 @@ "@backstage/plugin-auth-node": "workspace:^", "express": "^4.18.2", "passport": "^0.7.0", - "passport-atlassian-oauth2": "^2.1.0" + "passport-atlassian-oauth2": "^2.1.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/auth-backend-module-atlassian-provider/report.api.md b/plugins/auth-backend-module-atlassian-provider/report.api.md index 7abdf7ee70..c9e184aeac 100644 --- a/plugins/auth-backend-module-atlassian-provider/report.api.md +++ b/plugins/auth-backend-module-atlassian-provider/report.api.md @@ -20,7 +20,10 @@ export const atlassianAuthenticator: OAuthAuthenticator< export namespace atlassianSignInResolvers { const usernameMatchingUserEntityName: SignInResolverFactory< OAuthAuthenticatorResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; } diff --git a/plugins/auth-backend-module-atlassian-provider/src/resolvers.ts b/plugins/auth-backend-module-atlassian-provider/src/resolvers.ts index 1f3090bfdd..949f25db9c 100644 --- a/plugins/auth-backend-module-atlassian-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-atlassian-provider/src/resolvers.ts @@ -20,6 +20,7 @@ import { PassportProfile, SignInInfo, } from '@backstage/plugin-auth-node'; +import { z } from 'zod'; /** * Available sign-in resolvers for the Atlassian auth provider. @@ -31,7 +32,12 @@ export namespace atlassianSignInResolvers { * Looks up the user by matching their Atlassian username to the entity name. */ export const usernameMatchingUserEntityName = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async ( info: SignInInfo>, ctx, @@ -43,7 +49,15 @@ export namespace atlassianSignInResolvers { throw new Error(`Atlassian user profile does not contain a username`); } - return ctx.signInWithCatalogUser({ entityRef: { name: id } }); + return ctx.signInWithCatalogUser( + { entityRef: { name: id } }, + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: id } } + : undefined, + }, + ); }; }, }); diff --git a/plugins/auth-backend-module-auth0-provider/CHANGELOG.md b/plugins/auth-backend-module-auth0-provider/CHANGELOG.md index c14a7d28bd..811d5ef23c 100644 --- a/plugins/auth-backend-module-auth0-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-auth0-provider/CHANGELOG.md @@ -1,5 +1,69 @@ # @backstage/plugin-auth-backend-module-auth0-provider +## 0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 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 + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + ## 0.2.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-auth0-provider/catalog-info.yaml b/plugins/auth-backend-module-auth0-provider/catalog-info.yaml index 26b540ca8a..46503619da 100644 --- a/plugins/auth-backend-module-auth0-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-auth0-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-auth0-provider/package.json b/plugins/auth-backend-module-auth0-provider/package.json index 8a8b878ff2..03c6e9cb28 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-next.1", + "version": "0.2.6-next.0", "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 687bd3a63f..a10e48d26b 100644 --- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md @@ -1,5 +1,85 @@ # @backstage/plugin-auth-backend-module-aws-alb-provider +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.3-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-backend@0.25.2-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.4.4 + +### Patch Changes + +- c83cd8b: Fixed some circular or otherwise unclear imports +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-backend@0.25.1 + +## 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 + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + +## 0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0-next.2 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 + ## 0.4.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-aws-alb-provider/catalog-info.yaml b/plugins/auth-backend-module-aws-alb-provider/catalog-info.yaml index b3ced6b59d..8282046b05 100644 --- a/plugins/auth-backend-module-aws-alb-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-aws-alb-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-aws-alb-provider/config.d.ts b/plugins/auth-backend-module-aws-alb-provider/config.d.ts index 41c84872de..3a5d2d5eca 100644 --- a/plugins/auth-backend-module-aws-alb-provider/config.d.ts +++ b/plugins/auth-backend-module-aws-alb-provider/config.d.ts @@ -46,8 +46,12 @@ export interface Config { | { resolver: 'emailLocalPartMatchingUserEntityName'; allowedDomains?: string[]; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'emailMatchingUserEntityProfileEmail'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; } - | { resolver: 'emailMatchingUserEntityProfileEmail' } >; }; sessionDuration?: HumanDuration | string; diff --git a/plugins/auth-backend-module-aws-alb-provider/package.json b/plugins/auth-backend-module-aws-alb-provider/package.json index 9b98642ab1..2e92f4b019 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-next.1", + "version": "0.4.6-next.0", "description": "The aws-alb provider module for the Backstage auth backend.", "backstage": { "role": "backend-plugin-module", @@ -42,7 +42,8 @@ "@backstage/plugin-auth-backend": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "jose": "^5.0.0", - "node-cache": "^5.1.2" + "node-cache": "^5.1.2", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/auth-backend-module-aws-alb-provider/report.api.md b/plugins/auth-backend-module-aws-alb-provider/report.api.md index 567bff993c..e809ae0fe3 100644 --- a/plugins/auth-backend-module-aws-alb-provider/report.api.md +++ b/plugins/auth-backend-module-aws-alb-provider/report.api.md @@ -6,7 +6,7 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; import { JWTHeaderParameters } from 'jose'; import { KeyObject } from 'crypto'; -import type { PassportProfile } from '@backstage/plugin-auth-node/'; +import type { PassportProfile } from '@backstage/plugin-auth-node'; import { ProxyAuthenticator } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; @@ -41,7 +41,10 @@ export namespace awsAlbSignInResolvers { const // (undocumented) emailMatchingUserEntityProfileEmail: SignInResolverFactory< AwsAlbResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; } ``` diff --git a/plugins/auth-backend-module-aws-alb-provider/src/resolvers.ts b/plugins/auth-backend-module-aws-alb-provider/src/resolvers.ts index 990c447124..38b4b60f27 100644 --- a/plugins/auth-backend-module-aws-alb-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-aws-alb-provider/src/resolvers.ts @@ -19,6 +19,8 @@ import { SignInInfo, } from '@backstage/plugin-auth-node'; import { AwsAlbResult } from './types'; +import { z } from 'zod'; + /** * Available sign-in resolvers for the AWS ALB auth provider. * @@ -27,19 +29,37 @@ import { AwsAlbResult } from './types'; export namespace awsAlbSignInResolvers { export const emailMatchingUserEntityProfileEmail = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async (info: SignInInfo, ctx) => { if (!info.result.fullProfile.emails) { throw new Error( 'Login failed, user profile does not contain an email', ); } - return ctx.signInWithCatalogUser({ - filter: { - kind: ['User'], - 'spec.profile.email': info.result.fullProfile.emails[0].value, + + return ctx.signInWithCatalogUser( + { + filter: { + kind: ['User'], + 'spec.profile.email': info.result.fullProfile.emails[0].value, + }, }, - }); + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { + entityRef: { + name: info.result.fullProfile.emails[0].value, + }, + } + : undefined, + }, + ); }; }, }); diff --git a/plugins/auth-backend-module-aws-alb-provider/src/types.ts b/plugins/auth-backend-module-aws-alb-provider/src/types.ts index b4c792a5f9..fd3b3dd80b 100644 --- a/plugins/auth-backend-module-aws-alb-provider/src/types.ts +++ b/plugins/auth-backend-module-aws-alb-provider/src/types.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import type { PassportProfile } from '@backstage/plugin-auth-node/'; +import type { PassportProfile } from '@backstage/plugin-auth-node'; + /** * JWT header extraction result, containing the raw value and the parsed JWT * payload. @@ -26,6 +27,7 @@ export type AwsAlbResult = { expiresInSeconds?: number; accessToken: string; }; + /** * @public */ @@ -39,6 +41,7 @@ export type AwsAlbClaims = { exp: number; iss: string; }; + /** * @internal */ diff --git a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md index da8c7e6e81..5baeb7de11 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md @@ -1,5 +1,83 @@ # @backstage/plugin-auth-backend-module-azure-easyauth-provider +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/errors@1.2.7 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + +## 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 + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + +## 0.2.8-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + ## 0.2.8-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-azure-easyauth-provider/catalog-info.yaml b/plugins/auth-backend-module-azure-easyauth-provider/catalog-info.yaml index 174f1a52e4..edbfcbca30 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-azure-easyauth-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-azure-easyauth-provider/package.json b/plugins/auth-backend-module-azure-easyauth-provider/package.json index 837ed7c9d1..e88909bf95 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-next.1", + "version": "0.2.11-next.0", "description": "The azure-easyauth-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -40,7 +40,8 @@ "@types/passport": "^1.0.16", "express": "^4.19.2", "jose": "^5.0.0", - "passport": "^0.7.0" + "passport": "^0.7.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/auth-backend-module-azure-easyauth-provider/report.api.md b/plugins/auth-backend-module-azure-easyauth-provider/report.api.md index 9d337e3f34..717864f422 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/report.api.md +++ b/plugins/auth-backend-module-azure-easyauth-provider/report.api.md @@ -32,7 +32,10 @@ export namespace azureEasyAuthSignInResolvers { const // (undocumented) idMatchingUserEntityAnnotation: SignInResolverFactory< AzureEasyAuthResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; } diff --git a/plugins/auth-backend-module-azure-easyauth-provider/src/resolvers.ts b/plugins/auth-backend-module-azure-easyauth-provider/src/resolvers.ts index e9e35420d1..94afa90c5f 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-azure-easyauth-provider/src/resolvers.ts @@ -19,11 +19,17 @@ import { SignInInfo, } from '@backstage/plugin-auth-node'; import { AzureEasyAuthResult } from './types'; +import { z } from 'zod'; /** @public */ export namespace azureEasyAuthSignInResolvers { export const idMatchingUserEntityAnnotation = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async (info: SignInInfo, ctx) => { const { fullProfile: { id }, @@ -32,12 +38,19 @@ export namespace azureEasyAuthSignInResolvers { if (!id) { throw new Error('User profile contained no id'); } - - return await ctx.signInWithCatalogUser({ - annotations: { - 'graph.microsoft.com/user-id': id, + return ctx.signInWithCatalogUser( + { + annotations: { + 'graph.microsoft.com/user-id': id, + }, }, - }); + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: id } } + : undefined, + }, + ); }; }, }); diff --git a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md index fe706216c2..f985290bc3 100644 --- a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/plugin-auth-backend-module-bitbucket-provider +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 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 + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.3.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + ## 0.3.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-bitbucket-provider/catalog-info.yaml b/plugins/auth-backend-module-bitbucket-provider/catalog-info.yaml index 817eb561a5..2e6f8d4481 100644 --- a/plugins/auth-backend-module-bitbucket-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-bitbucket-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-bitbucket-provider/config.d.ts b/plugins/auth-backend-module-bitbucket-provider/config.d.ts index f3d82608bd..56303b5215 100644 --- a/plugins/auth-backend-module-bitbucket-provider/config.d.ts +++ b/plugins/auth-backend-module-bitbucket-provider/config.d.ts @@ -30,12 +30,23 @@ export interface Config { additionalScopes?: string | string[]; signIn?: { resolvers: Array< - | { resolver: 'userIdMatchingUserEntityAnnotation' } + | { + resolver: 'userIdMatchingUserEntityAnnotation'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'usernameMatchingUserEntityAnnotation'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } | { resolver: 'emailLocalPartMatchingUserEntityName'; allowedDomains?: string[]; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'emailMatchingUserEntityProfileEmail'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; } - | { resolver: 'emailMatchingUserEntityProfileEmail' } >; }; sessionDuration?: HumanDuration | string; diff --git a/plugins/auth-backend-module-bitbucket-provider/package.json b/plugins/auth-backend-module-bitbucket-provider/package.json index 55182829cb..7009e55ca2 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-next.1", + "version": "0.3.6-next.0", "description": "The bitbucket-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -38,7 +38,8 @@ "@backstage/plugin-auth-node": "workspace:^", "express": "^4.18.2", "passport": "^0.7.0", - "passport-bitbucket-oauth2": "^0.1.2" + "passport-bitbucket-oauth2": "^0.1.2", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/auth-backend-module-bitbucket-provider/report.api.md b/plugins/auth-backend-module-bitbucket-provider/report.api.md index ceb4141fd9..86cff50a0f 100644 --- a/plugins/auth-backend-module-bitbucket-provider/report.api.md +++ b/plugins/auth-backend-module-bitbucket-provider/report.api.md @@ -24,11 +24,17 @@ export const bitbucketAuthenticator: OAuthAuthenticator< export namespace bitbucketSignInResolvers { const userIdMatchingUserEntityAnnotation: SignInResolverFactory< OAuthAuthenticatorResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; const usernameMatchingUserEntityAnnotation: SignInResolverFactory< OAuthAuthenticatorResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; } ``` diff --git a/plugins/auth-backend-module-bitbucket-provider/src/resolvers.ts b/plugins/auth-backend-module-bitbucket-provider/src/resolvers.ts index f9d834a3a7..6691806e63 100644 --- a/plugins/auth-backend-module-bitbucket-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-bitbucket-provider/src/resolvers.ts @@ -20,6 +20,7 @@ import { PassportProfile, SignInInfo, } from '@backstage/plugin-auth-node'; +import { z } from 'zod'; /** * Available sign-in resolvers for the Bitbucket auth provider. @@ -32,7 +33,12 @@ export namespace bitbucketSignInResolvers { */ export const userIdMatchingUserEntityAnnotation = createSignInResolverFactory( { - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async ( info: SignInInfo>, ctx, @@ -44,11 +50,19 @@ export namespace bitbucketSignInResolvers { throw new Error('Bitbucket user profile does not contain an ID'); } - return ctx.signInWithCatalogUser({ - annotations: { - 'bitbucket.org/user-id': id, + return ctx.signInWithCatalogUser( + { + annotations: { + 'bitbucket.org/user-id': id, + }, }, - }); + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: id } } + : undefined, + }, + ); }; }, }, @@ -59,7 +73,12 @@ export namespace bitbucketSignInResolvers { */ export const usernameMatchingUserEntityAnnotation = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async ( info: SignInInfo>, ctx, @@ -73,11 +92,19 @@ export namespace bitbucketSignInResolvers { ); } - return ctx.signInWithCatalogUser({ - annotations: { - 'bitbucket.org/username': username, + return ctx.signInWithCatalogUser( + { + annotations: { + 'bitbucket.org/username': username, + }, }, - }); + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: username } } + : undefined, + }, + ); }; }, }); diff --git a/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md index e4fc051072..c88b9e011d 100644 --- a/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/plugin-auth-backend-module-bitbucket-server-provider +## 0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 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 + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.2.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + ## 0.2.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-bitbucket-server-provider/catalog-info.yaml b/plugins/auth-backend-module-bitbucket-server-provider/catalog-info.yaml index bcb659b1c2..893f873c4e 100644 --- a/plugins/auth-backend-module-bitbucket-server-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-bitbucket-server-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-bitbucket-server-provider/config.d.ts b/plugins/auth-backend-module-bitbucket-server-provider/config.d.ts index 8e63581625..26afb46f09 100644 --- a/plugins/auth-backend-module-bitbucket-server-provider/config.d.ts +++ b/plugins/auth-backend-module-bitbucket-server-provider/config.d.ts @@ -29,6 +29,27 @@ export interface Config { clientSecret: string; host: string; callbackUrl?: string; + signIn?: { + resolvers: Array< + | { + resolver: 'userIdMatchingUserEntityAnnotation'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'usernameMatchingUserEntityAnnotation'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'emailLocalPartMatchingUserEntityName'; + allowedDomains?: string[]; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'emailMatchingUserEntityProfileEmail'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + >; + }; sessionDuration?: HumanDuration | string; }; }; diff --git a/plugins/auth-backend-module-bitbucket-server-provider/package.json b/plugins/auth-backend-module-bitbucket-server-provider/package.json index dff0e32895..78db7d203e 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-next.1", + "version": "0.2.6-next.0", "description": "The bitbucket-server-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -37,7 +37,8 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "passport": "^0.7.0", - "passport-oauth2": "^1.6.1" + "passport-oauth2": "^1.6.1", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/auth-backend-module-bitbucket-server-provider/report.api.md b/plugins/auth-backend-module-bitbucket-server-provider/report.api.md index d5c484f6e3..d9409bd36d 100644 --- a/plugins/auth-backend-module-bitbucket-server-provider/report.api.md +++ b/plugins/auth-backend-module-bitbucket-server-provider/report.api.md @@ -27,7 +27,10 @@ export const bitbucketServerAuthenticator: OAuthAuthenticator< export namespace bitbucketServerSignInResolvers { const emailMatchingUserEntityProfileEmail: SignInResolverFactory< OAuthAuthenticatorResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; } ``` diff --git a/plugins/auth-backend-module-bitbucket-server-provider/src/resolvers.ts b/plugins/auth-backend-module-bitbucket-server-provider/src/resolvers.ts index dc2ceef7e9..2e92d8c6ad 100644 --- a/plugins/auth-backend-module-bitbucket-server-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-bitbucket-server-provider/src/resolvers.ts @@ -19,6 +19,7 @@ import { PassportProfile, SignInInfo, } from '@backstage/plugin-auth-node'; +import { z } from 'zod'; /** * Available sign-in resolvers for the Bitbucket Server auth provider. @@ -31,7 +32,12 @@ export namespace bitbucketServerSignInResolvers { */ export const emailMatchingUserEntityProfileEmail = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async ( info: SignInInfo>, ctx, @@ -44,11 +50,19 @@ export namespace bitbucketServerSignInResolvers { ); } - return ctx.signInWithCatalogUser({ - filter: { - 'spec.profile.email': profile.email, + return ctx.signInWithCatalogUser( + { + filter: { + 'spec.profile.email': profile.email, + }, }, - }); + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: profile.email } } + : undefined, + }, + ); }; }, }); diff --git a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md index 1495904a13..f83942615f 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md @@ -1,5 +1,83 @@ # @backstage/plugin-auth-backend-module-cloudflare-access-provider +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 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 + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + ## 0.4.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-cloudflare-access-provider/catalog-info.yaml b/plugins/auth-backend-module-cloudflare-access-provider/catalog-info.yaml index cdd42a040b..0c8547e048 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-cloudflare-access-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-cloudflare-access-provider/config.d.ts b/plugins/auth-backend-module-cloudflare-access-provider/config.d.ts index 39a4ca3eb6..c4ea052db3 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/config.d.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/config.d.ts @@ -34,8 +34,12 @@ export interface Config { | { resolver: 'emailLocalPartMatchingUserEntityName'; allowedDomains?: string[]; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'emailMatchingUserEntityProfileEmail'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; } - | { resolver: 'emailMatchingUserEntityProfileEmail' } >; }; }; diff --git a/plugins/auth-backend-module-cloudflare-access-provider/package.json b/plugins/auth-backend-module-cloudflare-access-provider/package.json index 3f8e18b5bb..17a4ac2044 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-next.1", + "version": "0.4.6-next.0", "description": "The cloudflare-access-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -39,7 +39,8 @@ "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "express": "^4.18.2", - "jose": "^5.0.0" + "jose": "^5.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/auth-backend-module-cloudflare-access-provider/report.api.md b/plugins/auth-backend-module-cloudflare-access-provider/report.api.md index 1547190d39..98c55e80bf 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/report.api.md +++ b/plugins/auth-backend-module-cloudflare-access-provider/report.api.md @@ -52,7 +52,10 @@ export type CloudflareAccessResult = { export namespace cloudflareAccessSignInResolvers { const emailMatchingUserEntityProfileEmail: SignInResolverFactory< CloudflareAccessResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; } diff --git a/plugins/auth-backend-module-cloudflare-access-provider/src/resolvers.test.ts b/plugins/auth-backend-module-cloudflare-access-provider/src/resolvers.test.ts index 27cdfa001c..2ab49c313a 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/src/resolvers.test.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/src/resolvers.test.ts @@ -37,8 +37,13 @@ describe('resolvers', () => { } satisfies Partial; await resolver(info, context as any); - expect(context.signInWithCatalogUser).toHaveBeenCalledWith({ - filter: { 'spec.profile.email': 'hello@example.com' }, - }); + expect(context.signInWithCatalogUser).toHaveBeenCalledWith( + { + filter: { 'spec.profile.email': 'hello@example.com' }, + }, + { + dangerousEntityRefFallback: undefined, + }, + ); }); }); diff --git a/plugins/auth-backend-module-cloudflare-access-provider/src/resolvers.ts b/plugins/auth-backend-module-cloudflare-access-provider/src/resolvers.ts index ad0b512a65..21cb124be4 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/src/resolvers.ts @@ -19,6 +19,7 @@ import { SignInInfo, } from '@backstage/plugin-auth-node'; import { CloudflareAccessResult } from './types'; +import { z } from 'zod'; /** * Available sign-in resolvers for the Cloudflare Access auth provider. @@ -31,7 +32,12 @@ export namespace cloudflareAccessSignInResolvers { */ export const emailMatchingUserEntityProfileEmail = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async (info: SignInInfo, ctx) => { const { profile } = info; @@ -41,11 +47,19 @@ export namespace cloudflareAccessSignInResolvers { ); } - return ctx.signInWithCatalogUser({ - filter: { - 'spec.profile.email': profile.email, + return ctx.signInWithCatalogUser( + { + filter: { + 'spec.profile.email': profile.email, + }, }, - }); + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: profile.email } } + : undefined, + }, + ); }; }, }); diff --git a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md index 6af6a5844b..63d1a8310f 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md @@ -1,5 +1,81 @@ # @backstage/plugin-auth-backend-module-gcp-iap-provider +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 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 + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.4.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-gcp-iap-provider/catalog-info.yaml b/plugins/auth-backend-module-gcp-iap-provider/catalog-info.yaml index ef604027df..a043e002c3 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-gcp-iap-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-gcp-iap-provider/config.d.ts b/plugins/auth-backend-module-gcp-iap-provider/config.d.ts index d99d890b50..572afdb0e6 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/config.d.ts +++ b/plugins/auth-backend-module-gcp-iap-provider/config.d.ts @@ -36,13 +36,23 @@ export interface Config { signIn?: { resolvers: Array< - | { resolver: 'emailMatchingUserEntityAnnotation' } - | { resolver: 'idMatchingUserEntityAnnotation' } + | { + resolver: 'emailMatchingUserEntityAnnotation'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'idMatchingUserEntityAnnotation'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } | { resolver: 'emailLocalPartMatchingUserEntityName'; allowedDomains?: string[]; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'emailMatchingUserEntityProfileEmail'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; } - | { resolver: 'emailMatchingUserEntityProfileEmail' } >; }; sessionDuration?: HumanDuration | string; diff --git a/plugins/auth-backend-module-gcp-iap-provider/package.json b/plugins/auth-backend-module-gcp-iap-provider/package.json index 7ee3097075..4e07ced5c9 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-next.1", + "version": "0.4.6-next.0", "description": "A GCP IAP auth provider module for the Backstage auth backend", "backstage": { "role": "backend-plugin-module", @@ -42,7 +42,8 @@ "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/types": "workspace:^", - "google-auth-library": "^9.0.0" + "google-auth-library": "^9.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/auth-backend-module-gcp-iap-provider/report.api.md b/plugins/auth-backend-module-gcp-iap-provider/report.api.md index 1476694f99..5cad28ac87 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/report.api.md +++ b/plugins/auth-backend-module-gcp-iap-provider/report.api.md @@ -35,11 +35,17 @@ export type GcpIapResult = { export namespace gcpIapSignInResolvers { const emailMatchingUserEntityAnnotation: SignInResolverFactory< GcpIapResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; const idMatchingUserEntityAnnotation: SignInResolverFactory< GcpIapResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; } diff --git a/plugins/auth-backend-module-gcp-iap-provider/src/resolvers.ts b/plugins/auth-backend-module-gcp-iap-provider/src/resolvers.ts index 557ee8fdfe..77dc7b1062 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-gcp-iap-provider/src/resolvers.ts @@ -19,6 +19,7 @@ import { SignInInfo, } from '@backstage/plugin-auth-node'; import { GcpIapResult } from './types'; +import { z } from 'zod'; /** * Available sign-in resolvers for the Google auth provider. @@ -30,7 +31,12 @@ export namespace gcpIapSignInResolvers { * Looks up the user by matching their email to the `google.com/email` annotation. */ export const emailMatchingUserEntityAnnotation = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async (info: SignInInfo, ctx) => { const email = info.result.iapToken.email; @@ -38,11 +44,19 @@ export namespace gcpIapSignInResolvers { throw new Error('Google IAP sign-in result is missing email'); } - return ctx.signInWithCatalogUser({ - annotations: { - 'google.com/email': email, + return ctx.signInWithCatalogUser( + { + annotations: { + 'google.com/email': email, + }, }, - }); + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: email } } + : undefined, + }, + ); }; }, }); @@ -51,15 +65,28 @@ export namespace gcpIapSignInResolvers { * Looks up the user by matching their user ID to the `google.com/user-id` annotation. */ export const idMatchingUserEntityAnnotation = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async (info: SignInInfo, ctx) => { const userId = info.result.iapToken.sub.split(':')[1]; - return ctx.signInWithCatalogUser({ - annotations: { - 'google.com/user-id': userId, + return ctx.signInWithCatalogUser( + { + annotations: { + 'google.com/user-id': userId, + }, }, - }); + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: userId } } + : undefined, + }, + ); }; }, }); diff --git a/plugins/auth-backend-module-github-provider/CHANGELOG.md b/plugins/auth-backend-module-github-provider/CHANGELOG.md index dc2635956e..5e289adb81 100644 --- a/plugins/auth-backend-module-github-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-github-provider/CHANGELOG.md @@ -1,5 +1,72 @@ # @backstage/plugin-auth-backend-module-github-provider +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 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 + +- 5cdfe05: Added missing types package +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.3.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + ## 0.3.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-github-provider/catalog-info.yaml b/plugins/auth-backend-module-github-provider/catalog-info.yaml index 8eda78fdaa..f8c11a2a17 100644 --- a/plugins/auth-backend-module-github-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-github-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-github-provider/config.d.ts b/plugins/auth-backend-module-github-provider/config.d.ts index 69abfa0d12..15ed412a6f 100644 --- a/plugins/auth-backend-module-github-provider/config.d.ts +++ b/plugins/auth-backend-module-github-provider/config.d.ts @@ -32,12 +32,18 @@ export interface Config { additionalScopes?: string | string[]; signIn?: { resolvers: Array< - | { resolver: 'usernameMatchingUserEntityName' } | { - resolver: 'emailLocalPartMatchingUserEntityName'; - allowedDomains?: string[]; + resolver: 'usernameMatchingUserEntityName'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'emailMatchingUserEntityProfileEmail'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'preferredUsernameMatchingUserEntityName'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; } - | { resolver: 'emailMatchingUserEntityProfileEmail' } >; }; sessionDuration?: HumanDuration | string; diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index dabe2c4c5b..a89e4ff213 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-next.1", + "version": "0.3.6-next.0", "description": "The github-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -36,7 +36,8 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", - "passport-github2": "^0.1.12" + "passport-github2": "^0.1.12", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/auth-backend-module-github-provider/report.api.md b/plugins/auth-backend-module-github-provider/report.api.md index 222305adfd..6bad840d00 100644 --- a/plugins/auth-backend-module-github-provider/report.api.md +++ b/plugins/auth-backend-module-github-provider/report.api.md @@ -24,7 +24,10 @@ export const githubAuthenticator: OAuthAuthenticator< export namespace githubSignInResolvers { const usernameMatchingUserEntityName: SignInResolverFactory< OAuthAuthenticatorResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; } ``` diff --git a/plugins/auth-backend-module-github-provider/src/resolvers.ts b/plugins/auth-backend-module-github-provider/src/resolvers.ts index 496080a33c..5a6934439f 100644 --- a/plugins/auth-backend-module-github-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-github-provider/src/resolvers.ts @@ -20,6 +20,7 @@ import { PassportProfile, SignInInfo, } from '@backstage/plugin-auth-node'; +import { z } from 'zod'; /** * Available sign-in resolvers for the GitHub auth provider. @@ -31,7 +32,12 @@ export namespace githubSignInResolvers { * Looks up the user by matching their GitHub username to the entity name. */ export const usernameMatchingUserEntityName = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async ( info: SignInInfo>, ctx, @@ -43,7 +49,17 @@ export namespace githubSignInResolvers { throw new Error(`GitHub user profile does not contain a username`); } - return ctx.signInWithCatalogUser({ entityRef: { name: userId } }); + return ctx.signInWithCatalogUser( + { + entityRef: { name: userId }, + }, + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: userId } } + : undefined, + }, + ); }; }, }); diff --git a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md index 6ecb16c14a..4bda0b90ec 100644 --- a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/plugin-auth-backend-module-gitlab-provider +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 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 + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.3.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + ## 0.3.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-gitlab-provider/catalog-info.yaml b/plugins/auth-backend-module-gitlab-provider/catalog-info.yaml index 55086778e4..5d355a19ad 100644 --- a/plugins/auth-backend-module-gitlab-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-gitlab-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-gitlab-provider/config.d.ts b/plugins/auth-backend-module-gitlab-provider/config.d.ts index 8d47421255..8cbb253e2c 100644 --- a/plugins/auth-backend-module-gitlab-provider/config.d.ts +++ b/plugins/auth-backend-module-gitlab-provider/config.d.ts @@ -32,12 +32,19 @@ export interface Config { additionalScopes?: string | string[]; signIn?: { resolvers: Array< - | { resolver: 'usernameMatchingUserEntityName' } + | { + resolver: 'usernameMatchingUserEntityName'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } | { resolver: 'emailLocalPartMatchingUserEntityName'; allowedDomains?: string[]; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'emailMatchingUserEntityProfileEmail'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; } - | { resolver: 'emailMatchingUserEntityProfileEmail' } >; }; sessionDuration?: HumanDuration | string; diff --git a/plugins/auth-backend-module-gitlab-provider/package.json b/plugins/auth-backend-module-gitlab-provider/package.json index fd726ebed5..fc0ff796bf 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-next.1", + "version": "0.3.6-next.0", "description": "The gitlab-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -38,7 +38,8 @@ "@backstage/plugin-auth-node": "workspace:^", "express": "^4.18.2", "passport": "^0.7.0", - "passport-gitlab2": "^5.0.0" + "passport-gitlab2": "^5.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/auth-backend-module-gitlab-provider/report.api.md b/plugins/auth-backend-module-gitlab-provider/report.api.md index 44c5ffb214..cb914deeea 100644 --- a/plugins/auth-backend-module-gitlab-provider/report.api.md +++ b/plugins/auth-backend-module-gitlab-provider/report.api.md @@ -24,7 +24,10 @@ export const gitlabAuthenticator: OAuthAuthenticator< export namespace gitlabSignInResolvers { const usernameMatchingUserEntityName: SignInResolverFactory< OAuthAuthenticatorResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; } ``` diff --git a/plugins/auth-backend-module-gitlab-provider/src/resolvers.ts b/plugins/auth-backend-module-gitlab-provider/src/resolvers.ts index 755ed08aa0..d5715f7e91 100644 --- a/plugins/auth-backend-module-gitlab-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-gitlab-provider/src/resolvers.ts @@ -20,6 +20,7 @@ import { PassportProfile, SignInInfo, } from '@backstage/plugin-auth-node'; +import { z } from 'zod'; /** * Available sign-in resolvers for the GitLab auth provider. @@ -31,7 +32,12 @@ export namespace gitlabSignInResolvers { * Looks up the user by matching their GitLab username to the entity name. */ export const usernameMatchingUserEntityName = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async ( info: SignInInfo>, ctx, @@ -43,7 +49,17 @@ export namespace gitlabSignInResolvers { throw new Error(`GitLab user profile does not contain a username`); } - return ctx.signInWithCatalogUser({ entityRef: { name: id } }); + return ctx.signInWithCatalogUser( + { + entityRef: { name: id }, + }, + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: id } } + : undefined, + }, + ); }; }, }); diff --git a/plugins/auth-backend-module-google-provider/CHANGELOG.md b/plugins/auth-backend-module-google-provider/CHANGELOG.md index 6271e93dec..da07eb0d82 100644 --- a/plugins/auth-backend-module-google-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-google-provider/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/plugin-auth-backend-module-google-provider +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 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 + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.3.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + ## 0.3.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-google-provider/catalog-info.yaml b/plugins/auth-backend-module-google-provider/catalog-info.yaml index 0897a12168..f32053ea3f 100644 --- a/plugins/auth-backend-module-google-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-google-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-google-provider/config.d.ts b/plugins/auth-backend-module-google-provider/config.d.ts index ec3cd92b31..842635480c 100644 --- a/plugins/auth-backend-module-google-provider/config.d.ts +++ b/plugins/auth-backend-module-google-provider/config.d.ts @@ -32,12 +32,19 @@ export interface Config { additionalScopes?: string | string[]; signIn?: { resolvers: Array< - | { resolver: 'emailMatchingUserEntityAnnotation' } + | { + resolver: 'emailMatchingUserEntityAnnotation'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } | { resolver: 'emailLocalPartMatchingUserEntityName'; allowedDomains?: string[]; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'emailMatchingUserEntityProfileEmail'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; } - | { resolver: 'emailMatchingUserEntityProfileEmail' } >; }; sessionDuration?: HumanDuration | string; diff --git a/plugins/auth-backend-module-google-provider/package.json b/plugins/auth-backend-module-google-provider/package.json index cdc365ddfb..4e43445c5e 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-next.1", + "version": "0.3.6-next.0", "description": "A Google auth provider module for the Backstage auth backend", "backstage": { "role": "backend-plugin-module", @@ -41,7 +41,8 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "google-auth-library": "^9.0.0", - "passport-google-oauth20": "^2.0.0" + "passport-google-oauth20": "^2.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/auth-backend-module-google-provider/report.api.md b/plugins/auth-backend-module-google-provider/report.api.md index eeefb8e837..f06a3c3355 100644 --- a/plugins/auth-backend-module-google-provider/report.api.md +++ b/plugins/auth-backend-module-google-provider/report.api.md @@ -24,7 +24,10 @@ export const googleAuthenticator: OAuthAuthenticator< export namespace googleSignInResolvers { const emailMatchingUserEntityAnnotation: SignInResolverFactory< OAuthAuthenticatorResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; } diff --git a/plugins/auth-backend-module-google-provider/src/resolvers.ts b/plugins/auth-backend-module-google-provider/src/resolvers.ts index b19fc4d49f..297ac0da6e 100644 --- a/plugins/auth-backend-module-google-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-google-provider/src/resolvers.ts @@ -20,6 +20,7 @@ import { PassportProfile, SignInInfo, } from '@backstage/plugin-auth-node'; +import { z } from 'zod'; /** * Available sign-in resolvers for the Google auth provider. @@ -31,7 +32,12 @@ export namespace googleSignInResolvers { * Looks up the user by matching their email to the `google.com/email` annotation. */ export const emailMatchingUserEntityAnnotation = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async ( info: SignInInfo>, ctx, @@ -42,11 +48,19 @@ export namespace googleSignInResolvers { throw new Error('Google profile contained no email'); } - return ctx.signInWithCatalogUser({ - annotations: { - 'google.com/email': profile.email, + return ctx.signInWithCatalogUser( + { + annotations: { + 'google.com/email': profile.email, + }, }, - }); + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: profile.email } } + : undefined, + }, + ); }; }, }); diff --git a/plugins/auth-backend-module-guest-provider/CHANGELOG.md b/plugins/auth-backend-module-guest-provider/CHANGELOG.md index 3bb742c6ef..28218175b5 100644 --- a/plugins/auth-backend-module-guest-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-guest-provider/CHANGELOG.md @@ -1,5 +1,81 @@ # @backstage/plugin-auth-backend-module-guest-provider +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/errors@1.2.7 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + +## 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 + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + +## 0.2.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + ## 0.2.8-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-guest-provider/catalog-info.yaml b/plugins/auth-backend-module-guest-provider/catalog-info.yaml index 5d3513296b..45a2bd8701 100644 --- a/plugins/auth-backend-module-guest-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-guest-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json index 80fe709602..576bc46bfa 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-next.1", + "version": "0.2.11-next.0", "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 2375a09efd..52cbe2a7e5 100644 --- a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/plugin-auth-backend-module-microsoft-provider +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 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 + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.3.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + ## 0.3.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-microsoft-provider/catalog-info.yaml b/plugins/auth-backend-module-microsoft-provider/catalog-info.yaml index 1742fc9ed6..4f6609bef3 100644 --- a/plugins/auth-backend-module-microsoft-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-microsoft-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-microsoft-provider/config.d.ts b/plugins/auth-backend-module-microsoft-provider/config.d.ts index 3cfcd45a2a..8ea62ddd86 100644 --- a/plugins/auth-backend-module-microsoft-provider/config.d.ts +++ b/plugins/auth-backend-module-microsoft-provider/config.d.ts @@ -34,13 +34,23 @@ export interface Config { skipUserProfile?: boolean; signIn?: { resolvers: Array< - | { resolver: 'emailMatchingUserEntityAnnotation' } + | { + resolver: 'emailMatchingUserEntityAnnotation'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } | { resolver: 'emailLocalPartMatchingUserEntityName'; allowedDomains?: string[]; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'emailMatchingUserEntityProfileEmail'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'userIdMatchingUserEntityAnnotation'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; } - | { resolver: 'emailMatchingUserEntityProfileEmail' } - | { resolver: 'userIdMatchingUserEntityAnnotation' } >; }; sessionDuration?: HumanDuration | string; diff --git a/plugins/auth-backend-module-microsoft-provider/package.json b/plugins/auth-backend-module-microsoft-provider/package.json index a64b9d04d4..89169e19de 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-next.1", + "version": "0.3.6-next.0", "description": "The microsoft-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -38,7 +38,8 @@ "@backstage/plugin-auth-node": "workspace:^", "express": "^4.18.2", "jose": "^5.0.0", - "passport-microsoft": "^1.0.0" + "passport-microsoft": "^1.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/auth-backend-module-microsoft-provider/report.api.md b/plugins/auth-backend-module-microsoft-provider/report.api.md index 6aef395d7c..9e37eee771 100644 --- a/plugins/auth-backend-module-microsoft-provider/report.api.md +++ b/plugins/auth-backend-module-microsoft-provider/report.api.md @@ -30,11 +30,17 @@ export const microsoftAuthenticator: OAuthAuthenticator< export namespace microsoftSignInResolvers { const emailMatchingUserEntityAnnotation: SignInResolverFactory< OAuthAuthenticatorResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; const userIdMatchingUserEntityAnnotation: SignInResolverFactory< OAuthAuthenticatorResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; } ``` diff --git a/plugins/auth-backend-module-microsoft-provider/src/resolvers.ts b/plugins/auth-backend-module-microsoft-provider/src/resolvers.ts index db7aa4f7f3..0ce276522e 100644 --- a/plugins/auth-backend-module-microsoft-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-microsoft-provider/src/resolvers.ts @@ -20,6 +20,7 @@ import { PassportProfile, SignInInfo, } from '@backstage/plugin-auth-node'; +import { z } from 'zod'; /** * Available sign-in resolvers for the Microsoft auth provider. @@ -31,7 +32,12 @@ export namespace microsoftSignInResolvers { * Looks up the user by matching their Microsoft email to the email entity annotation. */ export const emailMatchingUserEntityAnnotation = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async ( info: SignInInfo>, ctx, @@ -42,11 +48,19 @@ export namespace microsoftSignInResolvers { throw new Error('Microsoft profile contained no email'); } - return ctx.signInWithCatalogUser({ - annotations: { - 'microsoft.com/email': profile.email, + return ctx.signInWithCatalogUser( + { + annotations: { + 'microsoft.com/email': profile.email, + }, }, - }); + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: profile.email } } + : undefined, + }, + ); }; }, }); @@ -55,7 +69,12 @@ export namespace microsoftSignInResolvers { */ export const userIdMatchingUserEntityAnnotation = createSignInResolverFactory( { - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async ( info: SignInInfo>, ctx, @@ -68,11 +87,19 @@ export namespace microsoftSignInResolvers { throw new Error('Microsoft profile contained no id'); } - return ctx.signInWithCatalogUser({ - annotations: { - 'graph.microsoft.com/user-id': id, + return ctx.signInWithCatalogUser( + { + annotations: { + 'graph.microsoft.com/user-id': id, + }, }, - }); + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: id } } + : undefined, + }, + ); }; }, }, diff --git a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md index e8359616eb..7c9fdd934e 100644 --- a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/plugin-auth-backend-module-oauth2-provider +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 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 + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + ## 0.4.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-provider/catalog-info.yaml b/plugins/auth-backend-module-oauth2-provider/catalog-info.yaml index 4ee7997b0c..cae10fccd6 100644 --- a/plugins/auth-backend-module-oauth2-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-oauth2-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-oauth2-provider/config.d.ts b/plugins/auth-backend-module-oauth2-provider/config.d.ts index ba047e3340..8818211318 100644 --- a/plugins/auth-backend-module-oauth2-provider/config.d.ts +++ b/plugins/auth-backend-module-oauth2-provider/config.d.ts @@ -36,12 +36,19 @@ export interface Config { includeBasicAuth?: boolean; signIn?: { resolvers: Array< - | { resolver: 'usernameMatchingUserEntityName' } + | { + resolver: 'usernameMatchingUserEntityName'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } | { resolver: 'emailLocalPartMatchingUserEntityName'; allowedDomains?: string[]; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'emailMatchingUserEntityProfileEmail'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; } - | { resolver: 'emailMatchingUserEntityProfileEmail' } >; }; sessionDuration?: HumanDuration | string; diff --git a/plugins/auth-backend-module-oauth2-provider/package.json b/plugins/auth-backend-module-oauth2-provider/package.json index 4b9f9b6c02..de18a707f5 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-next.1", + "version": "0.4.6-next.0", "description": "The oauth2-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -37,7 +37,8 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "passport": "^0.7.0", - "passport-oauth2": "^1.6.1" + "passport-oauth2": "^1.6.1", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/auth-backend-module-oauth2-provider/report.api.md b/plugins/auth-backend-module-oauth2-provider/report.api.md index 632591ec04..b4b7d6d465 100644 --- a/plugins/auth-backend-module-oauth2-provider/report.api.md +++ b/plugins/auth-backend-module-oauth2-provider/report.api.md @@ -24,7 +24,10 @@ export const oauth2Authenticator: OAuthAuthenticator< export namespace oauth2SignInResolvers { const usernameMatchingUserEntityName: SignInResolverFactory< OAuthAuthenticatorResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; } ``` diff --git a/plugins/auth-backend-module-oauth2-provider/src/resolvers.ts b/plugins/auth-backend-module-oauth2-provider/src/resolvers.ts index c77f5afa03..bad3f015c8 100644 --- a/plugins/auth-backend-module-oauth2-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-oauth2-provider/src/resolvers.ts @@ -20,6 +20,7 @@ import { PassportProfile, SignInInfo, } from '@backstage/plugin-auth-node'; +import { z } from 'zod'; /** * Available sign-in resolvers for the oauth2 auth provider. @@ -31,7 +32,12 @@ export namespace oauth2SignInResolvers { * Looks up the user by matching their oauth2 username to the entity name. */ export const usernameMatchingUserEntityName = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async ( info: SignInInfo>, ctx, @@ -43,7 +49,17 @@ export namespace oauth2SignInResolvers { throw new Error(`Oauth2 user profile does not contain a username`); } - return ctx.signInWithCatalogUser({ entityRef: { name: id } }); + return ctx.signInWithCatalogUser( + { + entityRef: { name: id }, + }, + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: id } } + : undefined, + }, + ); }; }, }); diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md index a4ed6a4989..4804679702 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md @@ -1,5 +1,76 @@ # @backstage/plugin-auth-backend-module-oauth2-proxy-provider +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + +## 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 + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + +## 0.2.8-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 + ## 0.2.8-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/catalog-info.yaml b/plugins/auth-backend-module-oauth2-proxy-provider/catalog-info.yaml index c26202c97d..7518c1b534 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-oauth2-proxy-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/package.json b/plugins/auth-backend-module-oauth2-proxy-provider/package.json index c1df361682..d039316140 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-next.1", + "version": "0.2.11-next.0", "description": "The oauth2-proxy-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -36,7 +36,8 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", - "jose": "^5.0.0" + "jose": "^5.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/report.api.md b/plugins/auth-backend-module-oauth2-proxy-provider/report.api.md index aa360bc9c5..2efdc52635 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/report.api.md +++ b/plugins/auth-backend-module-oauth2-proxy-provider/report.api.md @@ -6,6 +6,7 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; import { IncomingHttpHeaders } from 'http'; import { ProxyAuthenticator } from '@backstage/plugin-auth-node'; +import { SignInResolverFactory } from '@backstage/plugin-auth-node'; // @public (undocumented) const authModuleOauth2ProxyProvider: BackendFeature; @@ -30,4 +31,16 @@ export type OAuth2ProxyResult = { headers: IncomingHttpHeaders; getHeader(name: string): string | undefined; }; + +// @public (undocumented) +export namespace oauth2ProxySignInResolvers { + const // (undocumented) + forwardedUserMatchingUserEntityName: SignInResolverFactory< + OAuth2ProxyResult, + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined + >; +} ``` diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/src/index.ts b/plugins/auth-backend-module-oauth2-proxy-provider/src/index.ts index b804f666c7..34617d4325 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/src/index.ts +++ b/plugins/auth-backend-module-oauth2-proxy-provider/src/index.ts @@ -20,6 +20,7 @@ * @packageDocumentation */ export { authModuleOauth2ProxyProvider as default } from './module'; +export { oauth2ProxySignInResolvers } from './resolvers'; export { oauth2ProxyAuthenticator, OAUTH2_PROXY_JWT_HEADER, diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/src/resolvers.ts b/plugins/auth-backend-module-oauth2-proxy-provider/src/resolvers.ts index 99ca8bc156..8ac639b3c0 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-oauth2-proxy-provider/src/resolvers.ts @@ -19,6 +19,7 @@ import { SignInInfo, } from '@backstage/plugin-auth-node'; import { OAuth2ProxyResult } from './types'; +import { z } from 'zod'; /** * @public @@ -26,15 +27,29 @@ import { OAuth2ProxyResult } from './types'; export namespace oauth2ProxySignInResolvers { export const forwardedUserMatchingUserEntityName = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async (info: SignInInfo, ctx) => { const name = info.result.getHeader('x-forwarded-user'); if (!name) { throw new Error('Request did not contain a user'); } - return ctx.signInWithCatalogUser({ - entityRef: { name }, - }); + + return ctx.signInWithCatalogUser( + { + entityRef: { name }, + }, + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name } } + : undefined, + }, + ); }; }, }); diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md index 33520a8bde..75b97535c7 100644 --- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md @@ -1,5 +1,91 @@ # @backstage/plugin-auth-backend-module-oidc-provider +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.3-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/types@1.2.1 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-auth-backend@0.25.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-backend@0.25.2-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-backend@0.25.1 + +## 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 + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## 0.4.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.0-next.2 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + ## 0.4.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-oidc-provider/catalog-info.yaml b/plugins/auth-backend-module-oidc-provider/catalog-info.yaml index 896738898b..207ee0dd8f 100644 --- a/plugins/auth-backend-module-oidc-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-oidc-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-oidc-provider/config.d.ts b/plugins/auth-backend-module-oidc-provider/config.d.ts index 8a1df804e6..c11b9319ec 100644 --- a/plugins/auth-backend-module-oidc-provider/config.d.ts +++ b/plugins/auth-backend-module-oidc-provider/config.d.ts @@ -38,8 +38,16 @@ export interface Config { | { resolver: 'emailLocalPartMatchingUserEntityName'; allowedDomains?: string[]; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'emailMatchingUserEntityProfileEmail'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'preferredUsernameMatchingUserEntityName'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; } - | { resolver: 'emailMatchingUserEntityProfileEmail' } >; }; sessionDuration?: HumanDuration | string; diff --git a/plugins/auth-backend-module-oidc-provider/knip-report.md b/plugins/auth-backend-module-oidc-provider/knip-report.md index 0d36158781..a7256f0ead 100644 --- a/plugins/auth-backend-module-oidc-provider/knip-report.md +++ b/plugins/auth-backend-module-oidc-provider/knip-report.md @@ -1,10 +1,11 @@ # Knip report -## Unused dependencies (1) +## Unused dependencies (2) | Name | Location | Severity | | :------- | :----------- | :------- | | passport | package.json | error | +| zod | package.json | error | ## Unused devDependencies (3) diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json index 65c1d8f208..7f5d39c611 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-next.1", + "version": "0.4.6-next.0", "description": "The oidc-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -41,7 +41,8 @@ "@backstage/types": "workspace:^", "express": "^4.18.2", "openid-client": "^5.5.0", - "passport": "^0.7.0" + "passport": "^0.7.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/auth-backend-module-oidc-provider/report.api.md b/plugins/auth-backend-module-oidc-provider/report.api.md index 93c000c2ee..11cde22db1 100644 --- a/plugins/auth-backend-module-oidc-provider/report.api.md +++ b/plugins/auth-backend-module-oidc-provider/report.api.md @@ -41,12 +41,17 @@ export namespace oidcSignInResolvers { unknown, | { allowedDomains?: string[] | undefined; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; } | undefined >; const emailMatchingUserEntityProfileEmail: SignInResolverFactory< unknown, - unknown + | { + allowedDomains?: string[] | undefined; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; } ``` diff --git a/plugins/auth-backend-module-oidc-provider/src/module.ts b/plugins/auth-backend-module-oidc-provider/src/module.ts index 5680cd8603..5e4daf20c3 100644 --- a/plugins/auth-backend-module-oidc-provider/src/module.ts +++ b/plugins/auth-backend-module-oidc-provider/src/module.ts @@ -16,7 +16,6 @@ import { createBackendModule } from '@backstage/backend-plugin-api'; import { authProvidersExtensionPoint, - commonSignInResolvers, createOAuthProviderFactory, } from '@backstage/plugin-auth-node'; import { oidcAuthenticator } from './authenticator'; @@ -38,7 +37,6 @@ export const authModuleOidcProvider = createBackendModule({ authenticator: oidcAuthenticator, signInResolverFactories: { ...oidcSignInResolvers, - ...commonSignInResolvers, }, }), }); diff --git a/plugins/auth-backend-module-okta-provider/CHANGELOG.md b/plugins/auth-backend-module-okta-provider/CHANGELOG.md index 3f7838609e..0837abe37c 100644 --- a/plugins/auth-backend-module-okta-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-okta-provider/CHANGELOG.md @@ -1,5 +1,78 @@ # @backstage/plugin-auth-backend-module-okta-provider +## 0.2.6-next.1 + +### Patch Changes + +- 94476d2: Updated dependency `@davidzemon/passport-okta-oauth` to `^0.0.7`. + +## 0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.2.4 + +### Patch Changes + +- b25977f: Updated dependency `@davidzemon/passport-okta-oauth` to `^0.0.6`. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 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 + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.2.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + ## 0.2.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-okta-provider/catalog-info.yaml b/plugins/auth-backend-module-okta-provider/catalog-info.yaml index 7a5536edcf..e12e80b4ff 100644 --- a/plugins/auth-backend-module-okta-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-okta-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-okta-provider/config.d.ts b/plugins/auth-backend-module-okta-provider/config.d.ts index 3f27e805ec..f475a1d693 100644 --- a/plugins/auth-backend-module-okta-provider/config.d.ts +++ b/plugins/auth-backend-module-okta-provider/config.d.ts @@ -34,12 +34,19 @@ export interface Config { additionalScopes?: string | string[]; signIn?: { resolvers: Array< - | { resolver: 'emailMatchingUserEntityAnnotation' } + | { + resolver: 'emailMatchingUserEntityAnnotation'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } | { resolver: 'emailLocalPartMatchingUserEntityName'; allowedDomains?: string[]; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'emailMatchingUserEntityProfileEmail'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; } - | { resolver: 'emailMatchingUserEntityProfileEmail' } >; }; sessionDuration?: HumanDuration | string; diff --git a/plugins/auth-backend-module-okta-provider/knip-report.md b/plugins/auth-backend-module-okta-provider/knip-report.md index 6624c86073..e761ead581 100644 --- a/plugins/auth-backend-module-okta-provider/knip-report.md +++ b/plugins/auth-backend-module-okta-provider/knip-report.md @@ -7,3 +7,9 @@ | passport | package.json | error | | express | package.json | error | +## Unlisted dependencies (1) + +| Name | Location | Severity | +| :-------------- | :------------- | :------- | +| passport-oauth2 | src/types.d.ts | error | + diff --git a/plugins/auth-backend-module-okta-provider/package.json b/plugins/auth-backend-module-okta-provider/package.json index 70e528d0b1..031220e3d5 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-next.1", + "version": "0.2.6-next.1", "description": "The okta-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -36,9 +36,10 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", - "@davidzemon/passport-okta-oauth": "^0.0.5", + "@davidzemon/passport-okta-oauth": "^0.0.7", "express": "^4.18.2", - "passport": "^0.7.0" + "passport": "^0.7.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/auth-backend-module-okta-provider/report.api.md b/plugins/auth-backend-module-okta-provider/report.api.md index a142265100..796e0f20a6 100644 --- a/plugins/auth-backend-module-okta-provider/report.api.md +++ b/plugins/auth-backend-module-okta-provider/report.api.md @@ -24,7 +24,10 @@ export const oktaAuthenticator: OAuthAuthenticator< export namespace oktaSignInResolvers { const emailMatchingUserEntityAnnotation: SignInResolverFactory< OAuthAuthenticatorResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; } ``` diff --git a/plugins/auth-backend-module-okta-provider/src/resolvers.ts b/plugins/auth-backend-module-okta-provider/src/resolvers.ts index 8bc3f38f17..cdb37dbaae 100644 --- a/plugins/auth-backend-module-okta-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-okta-provider/src/resolvers.ts @@ -20,6 +20,7 @@ import { PassportProfile, SignInInfo, } from '@backstage/plugin-auth-node'; +import { z } from 'zod'; /** * Available sign-in resolvers for the Okta auth provider. @@ -32,7 +33,12 @@ export namespace oktaSignInResolvers { */ export const emailMatchingUserEntityAnnotation = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async ( info: SignInInfo>, ctx, @@ -43,11 +49,19 @@ export namespace oktaSignInResolvers { throw new Error('Okta profile contained no email'); } - return ctx.signInWithCatalogUser({ - annotations: { - 'okta.com/email': profile.email, + return ctx.signInWithCatalogUser( + { + annotations: { + 'okta.com/email': profile.email, + }, }, - }); + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: profile.email } } + : undefined, + }, + ); }; }, }); diff --git a/plugins/auth-backend-module-okta-provider/src/types.d.ts b/plugins/auth-backend-module-okta-provider/src/types.d.ts index 58cfb74173..7ca3645e3e 100644 --- a/plugins/auth-backend-module-okta-provider/src/types.d.ts +++ b/plugins/auth-backend-module-okta-provider/src/types.d.ts @@ -23,3 +23,87 @@ declare module 'passport-okta2' { authenticate(this: StrategyCreated, req: Request, options?: any): any; } } + +// NOTE(freben): This entire block is here because version 0.0.6 of this library +// accidentally didn't include types. It did, however, include a scope fix that +// is interesting for Backstage to get in. +// See https://github.com/backstage/backstage/pull/29529 +// See https://github.com/DavidZemon/passport-okta-oauth/pull/1 +declare module '@davidzemon/passport-okta-oauth' { + import OAuth2Strategy = require('passport-oauth2'); + + type OktaStrategyOptions = UniqueOktaStrategyOptions & + Omit< + import('passport-oauth2')._StrategyOptionsBase, + 'authorizationURL' | 'tokenURL' + >; + type UniqueOktaStrategyOptions = { + /** + * audience is the Okta Domain, e.g. `https://example.okta.com`, + * `https://example.oktapreview.com` + */ + audience: string; + /** + * authServerID is the authorization server ID. If it is defined, the token + * URL might be something like `https://example.okta.com/oauth2/authServerID/v1/token` + */ + authServerID: string | undefined; + /** + * idp is the Identity Provider (id). This is an optional field. it's a 20 character + * alphanumeric string, e.g. `qOp8aaJmCEhvep5Il6ZJ` (generated example) + */ + idp: string | undefined; + /** + * With this option enabled, `req` will be passed as the first argument to the + * verify callback. + */ + passReqToCallback: boolean | undefined; + /** + * Set this to 'code' + */ + response_type: 'code'; + }; + + class Strategy extends OAuth2Strategy { + /** + * @param {OktaStrategyOptions | undefined} options + * @param {import("passport-oauth2").VerifyFunction | import("passport-oauth2").VerifyFunctionWithRequest} verify + */ + constructor( + options: OktaStrategyOptions | undefined, + verify: + | import('passport-oauth2').VerifyFunction + | import('passport-oauth2').VerifyFunctionWithRequest, + ); + _userInfoUrl: string; + _idp: string; + _state: any; + /** + * Retrieve user profile from Okta. + * Further references at http://developer.okta.com/docs/api/resources/oidc.html#get-user-information + * + * This function constructs a normalized profile, with the following properties: + * + * - `provider` always set to `okta` + * - `id` + * - `username` + * - `displayName` + * + * @param {String} accessToken + * @param {Function} done + * @api protected + */ + userProfile(accessToken: string, done: Function): void; + /** + * Return extra Okta-specific parameters to be included in the authorization + * request. + * + * @param {Object} option + * @return {Object} + * @api protected + */ + authorizationParams(option: any): any; + } + + export { Strategy }; +} diff --git a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md index 410f2eda91..f7487cc35f 100644 --- a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/plugin-auth-backend-module-onelogin-provider +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + +## 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 + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.3.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + ## 0.3.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-onelogin-provider/catalog-info.yaml b/plugins/auth-backend-module-onelogin-provider/catalog-info.yaml index 6988215b59..9995800641 100644 --- a/plugins/auth-backend-module-onelogin-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-onelogin-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-onelogin-provider/config.d.ts b/plugins/auth-backend-module-onelogin-provider/config.d.ts index 58f471bafb..e8fd6f6749 100644 --- a/plugins/auth-backend-module-onelogin-provider/config.d.ts +++ b/plugins/auth-backend-module-onelogin-provider/config.d.ts @@ -31,12 +31,19 @@ export interface Config { callbackUrl?: string; signIn?: { resolvers: Array< - | { resolver: 'usernameMatchingUserEntityName' } + | { + resolver: 'usernameMatchingUserEntityName'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } | { resolver: 'emailLocalPartMatchingUserEntityName'; allowedDomains?: string[]; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'emailMatchingUserEntityProfileEmail'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; } - | { resolver: 'emailMatchingUserEntityProfileEmail' } >; }; sessionDuration?: HumanDuration | string; diff --git a/plugins/auth-backend-module-onelogin-provider/package.json b/plugins/auth-backend-module-onelogin-provider/package.json index 1e9efc21ea..08a9dfe2f4 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-next.1", + "version": "0.3.6-next.0", "description": "The onelogin-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -38,7 +38,8 @@ "@backstage/plugin-auth-node": "workspace:^", "express": "^4.18.2", "passport": "^0.7.0", - "passport-onelogin-oauth": "^0.0.1" + "passport-onelogin-oauth": "^0.0.1", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/auth-backend-module-onelogin-provider/report.api.md b/plugins/auth-backend-module-onelogin-provider/report.api.md index 1991f00e0b..df3345f5ed 100644 --- a/plugins/auth-backend-module-onelogin-provider/report.api.md +++ b/plugins/auth-backend-module-onelogin-provider/report.api.md @@ -24,7 +24,10 @@ export const oneLoginAuthenticator: OAuthAuthenticator< export namespace oneLoginSignInResolvers { const usernameMatchingUserEntityName: SignInResolverFactory< OAuthAuthenticatorResult, - unknown + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; } ``` diff --git a/plugins/auth-backend-module-onelogin-provider/src/resolvers.ts b/plugins/auth-backend-module-onelogin-provider/src/resolvers.ts index 93bfd758cb..56710472be 100644 --- a/plugins/auth-backend-module-onelogin-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-onelogin-provider/src/resolvers.ts @@ -20,6 +20,7 @@ import { PassportProfile, SignInInfo, } from '@backstage/plugin-auth-node'; +import { z } from 'zod'; /** * Available sign-in resolvers for the OneLogin auth provider. @@ -31,7 +32,12 @@ export namespace oneLoginSignInResolvers { * Looks up the user by matching their OneLogin username to the entity name. */ export const usernameMatchingUserEntityName = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async ( info: SignInInfo>, ctx, @@ -43,7 +49,17 @@ export namespace oneLoginSignInResolvers { throw new Error(`OneLogin user profile does not contain a username`); } - return ctx.signInWithCatalogUser({ entityRef: { name: id } }); + return ctx.signInWithCatalogUser( + { + entityRef: { name: id }, + }, + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: id } } + : undefined, + }, + ); }; }, }); diff --git a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md index 7c1ee827b6..cb7cab3f2e 100644 --- a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md @@ -1,5 +1,81 @@ # @backstage/plugin-auth-backend-module-pinniped-provider +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/types@1.2.1 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## 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 + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## 0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + ## 0.3.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-pinniped-provider/catalog-info.yaml b/plugins/auth-backend-module-pinniped-provider/catalog-info.yaml index 9d1ef1c299..5fa819c9d9 100644 --- a/plugins/auth-backend-module-pinniped-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-pinniped-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-pinniped-provider/package.json b/plugins/auth-backend-module-pinniped-provider/package.json index 940443cf7e..bf391ed8f5 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-next.1", + "version": "0.3.6-next.0", "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 fc085d8c31..8a5230cfc3 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md @@ -1,5 +1,78 @@ # @backstage/plugin-auth-backend-module-vmware-cloud-provider +## 0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + +## 0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + +## 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 + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + +## 0.5.3-next.2 + +### Patch Changes + +- 5cc1f7f: Introduce `dangerouslyAllowSignInWithoutUserInCatalog` auth resolver config. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + ## 0.5.3-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-vmware-cloud-provider/catalog-info.yaml b/plugins/auth-backend-module-vmware-cloud-provider/catalog-info.yaml index d40b1e4308..e35b58128d 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-vmware-cloud-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-vmware-cloud-provider/config.d.ts b/plugins/auth-backend-module-vmware-cloud-provider/config.d.ts index 9886d69a41..235d2ab0f1 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/config.d.ts +++ b/plugins/auth-backend-module-vmware-cloud-provider/config.d.ts @@ -32,8 +32,12 @@ export interface Config { | { resolver: 'emailLocalPartMatchingUserEntityName'; allowedDomains?: string[]; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; + } + | { + resolver: 'emailMatchingUserEntityProfileEmail'; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean; } - | { resolver: 'emailMatchingUserEntityProfileEmail' } >; }; sessionDuration?: HumanDuration | string; diff --git a/plugins/auth-backend-module-vmware-cloud-provider/package.json b/plugins/auth-backend-module-vmware-cloud-provider/package.json index bf209f69fd..3c7f9041a8 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-next.1", + "version": "0.5.6-next.0", "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 8f12cd8305..4678e0e204 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,147 @@ # @backstage/plugin-auth-backend +## 0.25.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.25.2 + +### Patch Changes + +- e88cb70: Small internal refactor to move out the `userInfo` database from the `tokenIssuer`. Also removes `exp` from being stored in `UserInfo` and it's now replaced with `created_at` and `updated_at` in the database instead. +- 207778c: Internal refactor of OIDC endpoints and `UserInfoDatabase` +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-catalog-node@1.17.2 + +## 0.25.2-next.1 + +### Patch Changes + +- e88cb70: Small internal refactor to move out the `userInfo` database from the `tokenIssuer`. Also removes `exp` from being stored in `UserInfo` and it's now replaced with `created_at` and `updated_at` in the database instead. +- 207778c: Internal refactor of OIDC endpoints and `UserInfoDatabase` + +## 0.25.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## 0.25.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @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.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 + +- 57221d9: **BREAKING**: Removed support for the old backend system, and removed all deprecated exports. + + If you were using one of the deprecated imports from this package, you will have to follow the instructions in their respective deprecation notices before upgrading. Most of the general utilities are available from `@backstage/plugin-auth-node`, and the specific auth providers are available from dedicated packages such as for example `@backstage/plugin-auth-backend-module-github-provider`. See [the auth docs](https://backstage.io/docs/auth/) for specific instructions. + +### Patch Changes + +- 0d606ac: Added the configuration flag `auth.omitIdentityTokenOwnershipClaim` that causes issued user tokens to no longer contain the `ent` claim that represents the ownership references of the user. + + The benefit of this new flag is that issued user tokens will be much smaller in + size, but they will no longer be self-contained. This means that any consumers + of the token that require access to the ownership claims now need to call the + `/api/auth/v1/userinfo` endpoint instead. Within the Backstage ecosystem this is + done automatically, as clients will still receive the full set of claims during + authentication, while plugin backends will need to use the `UserInfoService` + which already calls the user info endpoint if necessary. + + When enabling this flag, it is important that any custom sign-in resolvers directly return the result of the sign-in method. For example, the following would not work: + + ```ts + const { token } = await ctx.issueToken({ + claims: { sub: entityRef, ent: [entityRef] }, + }); + return { token }; // WARNING: This will not work with the flag enabled + ``` + + Instead, the sign-in resolver should directly return the result: + + ```ts + return ctx.issueToken({ + claims: { sub: entityRef, ent: [entityRef] }, + }); + ``` + +- 72d019d: Removed various typos +- ab53e6f: Added support for the new `dangerousEntityRefFallback` option for `signInWithCatalogUser` in `AuthResolverContext`. +- b128ed9: The `static` key store now issues tokens with the same structure as other key stores. Tokens now include the `typ` field in the header and the `uip` (user identity proof) in the payload. +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.25.0-next.2 + +### Patch Changes + +- ab53e6f: Added support for the new `dangerousEntityRefFallback` option for `signInWithCatalogUser` in `AuthResolverContext`. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.2 + ## 0.25.0-next.1 ### Patch Changes diff --git a/plugins/auth-backend/catalog-info.yaml b/plugins/auth-backend/catalog-info.yaml index 38ac5f0753..1fbb18685d 100644 --- a/plugins/auth-backend/catalog-info.yaml +++ b/plugins/auth-backend/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend/migrations/20250707164600_user_created_at.js b/plugins/auth-backend/migrations/20250707164600_user_created_at.js new file mode 100644 index 0000000000..eb78cb21b0 --- /dev/null +++ b/plugins/auth-backend/migrations/20250707164600_user_created_at.js @@ -0,0 +1,37 @@ +/* + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + await knex.schema.alterTable('user_info', table => { + table.renameColumn('exp', 'updated_at'); + table.timestamp('created_at').notNullable().defaultTo(knex.fn.now()); + }); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + await knex.schema.alterTable('user_info', table => { + table.dropColumn('created_at'); + table.renameColumn('updated_at', 'exp'); + }); +}; diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 3fce660aa5..59a663d988 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-next.1", + "version": "0.25.3-next.0", "description": "A Backstage backend plugin that handles authentication", "backstage": { "role": "backend-plugin", diff --git a/plugins/auth-backend/report.sql.md b/plugins/auth-backend/report.sql.md index 7eef7a33cb..b135414af6 100644 --- a/plugins/auth-backend/report.sql.md +++ b/plugins/auth-backend/report.sql.md @@ -33,11 +33,12 @@ ## Table `user_info` -| Column | Type | Nullable | Max Length | Default | -| ----------------- | -------------------------- | -------- | ---------- | ------- | -| `exp` | `timestamp with time zone` | false | - | - | -| `user_entity_ref` | `character varying` | false | 255 | - | -| `user_info` | `text` | false | - | - | +| Column | Type | Nullable | Max Length | Default | +| ----------------- | -------------------------- | -------- | ---------- | ------------------- | +| `created_at` | `timestamp with time zone` | false | - | `CURRENT_TIMESTAMP` | +| `updated_at` | `timestamp with time zone` | false | - | - | +| `user_entity_ref` | `character varying` | false | 255 | - | +| `user_info` | `text` | false | - | - | ### Indices diff --git a/plugins/auth-backend/src/authPlugin.test.ts b/plugins/auth-backend/src/authPlugin.test.ts index cd2b558c80..65d44614fe 100644 --- a/plugins/auth-backend/src/authPlugin.test.ts +++ b/plugins/auth-backend/src/authPlugin.test.ts @@ -86,6 +86,7 @@ describe('authPlugin', () => { }); const refreshRes = await request(server).post('/api/auth/guest/refresh'); + expect(refreshRes.status).toBe(200); expect(refreshRes.body).toMatchObject({ backstageIdentity: { @@ -110,7 +111,6 @@ describe('authPlugin', () => { claims: { sub: expectedIdentity.userEntityRef, ent: expectedIdentity.ownershipEntityRefs, - exp: expect.any(Number), }, }); }); @@ -160,7 +160,6 @@ describe('authPlugin', () => { claims: { sub: expectedIdentity.userEntityRef, ent: expectedIdentity.ownershipEntityRefs, - exp: expect.any(Number), }, }); }); diff --git a/plugins/auth-backend/src/identity/UserInfoDatabaseHandler.test.ts b/plugins/auth-backend/src/database/UserInfoDatabase.test.ts similarity index 83% rename from plugins/auth-backend/src/identity/UserInfoDatabaseHandler.test.ts rename to plugins/auth-backend/src/database/UserInfoDatabase.test.ts index 4d696aa8f2..edaeec450b 100644 --- a/plugins/auth-backend/src/identity/UserInfoDatabaseHandler.test.ts +++ b/plugins/auth-backend/src/database/UserInfoDatabase.test.ts @@ -17,7 +17,9 @@ import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils'; import { Knex } from 'knex'; -import { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler'; +import { UserInfoDatabase } from './UserInfoDatabase'; +import { AuthDatabase } from './AuthDatabase'; +import { DateTime } from 'luxon'; const migrationsDir = resolvePackagePath( '@backstage/plugin-auth-backend', @@ -26,7 +28,7 @@ const migrationsDir = resolvePackagePath( jest.setTimeout(60_000); -describe('UserInfoDatabaseHandler', () => { +describe('UserInfoDatabase', () => { const databases = TestDatabases.create(); async function createDatabaseHandler(databaseId: TestDatabaseId) { @@ -38,7 +40,11 @@ describe('UserInfoDatabaseHandler', () => { return { knex, - dbHandler: new UserInfoDatabaseHandler(knex), + dbHandler: await UserInfoDatabase.create({ + database: AuthDatabase.create({ + getClient: async () => knex, + }), + }), }; } @@ -46,7 +52,7 @@ describe('UserInfoDatabaseHandler', () => { 'should support database %p', databaseId => { let knex: Knex; - let dbHandler: UserInfoDatabaseHandler; + let dbHandler: UserInfoDatabase; beforeEach(async () => { ({ knex, dbHandler } = await createDatabaseHandler(databaseId)); @@ -57,7 +63,6 @@ describe('UserInfoDatabaseHandler', () => { claims: { sub: 'user:default/foo', ent: ['group:default/foo-group', 'group:default/bar'], - exp: 1234567890, }, }; @@ -66,10 +71,12 @@ describe('UserInfoDatabaseHandler', () => { const savedUserInfo = await knex('user_info') .where('user_entity_ref', 'user:default/foo') .first(); + expect(savedUserInfo).toEqual({ user_entity_ref: 'user:default/foo', user_info: JSON.stringify(userInfo), - exp: expect.anything(), + updated_at: expect.anything(), + created_at: expect.anything(), }); userInfo.claims.ent = ['group:default/group1', 'group:default/group2']; @@ -78,10 +85,12 @@ describe('UserInfoDatabaseHandler', () => { const updatedUserInfo = await knex('user_info') .where('user_entity_ref', 'user:default/foo') .first(); + expect(updatedUserInfo).toEqual({ user_entity_ref: 'user:default/foo', user_info: JSON.stringify(userInfo), - exp: expect.anything(), + updated_at: expect.anything(), + created_at: expect.anything(), }); }); @@ -90,14 +99,13 @@ describe('UserInfoDatabaseHandler', () => { claims: { sub: 'user:default/backstage-user', ent: ['group:default/group1', 'group:default/group2'], - exp: 1234567890, }, }; await knex('user_info').insert({ user_entity_ref: 'user:default/backstage-user', user_info: JSON.stringify(userInfo), - exp: knex.fn.now(), + updated_at: DateTime.now().toSQL({ includeOffset: false }), }); const savedUserInfo = await dbHandler.getUserInfo( diff --git a/plugins/auth-backend/src/identity/UserInfoDatabaseHandler.ts b/plugins/auth-backend/src/database/UserInfoDatabase.ts similarity index 75% rename from plugins/auth-backend/src/identity/UserInfoDatabaseHandler.ts rename to plugins/auth-backend/src/database/UserInfoDatabase.ts index c0b31de8e4..9377ab89f7 100644 --- a/plugins/auth-backend/src/identity/UserInfoDatabaseHandler.ts +++ b/plugins/auth-backend/src/database/UserInfoDatabase.ts @@ -17,31 +17,30 @@ import { DateTime } from 'luxon'; import { Knex } from 'knex'; -import { BackstageTokenPayload } from './TokenFactory'; +import { AuthDatabase } from './AuthDatabase'; +import { JsonObject } from '@backstage/types'; const TABLE = 'user_info'; type Row = { user_entity_ref: string; user_info: string; - exp: string; + updated_at: string; }; type UserInfo = { - claims: Omit; + claims: JsonObject; }; -export class UserInfoDatabaseHandler { - constructor(private readonly client: Knex) {} +export class UserInfoDatabase { + private constructor(private readonly client: Knex) {} async addUserInfo(userInfo: UserInfo): Promise { await this.client(TABLE) .insert({ user_entity_ref: userInfo.claims.sub as string, user_info: JSON.stringify(userInfo), - exp: DateTime.fromSeconds(userInfo.claims.exp as number, { - zone: 'utc', - }).toSQL({ includeOffset: false }), + updated_at: DateTime.utc().toSQL({ includeOffset: false }), }) .onConflict('user_entity_ref') .merge(); @@ -59,4 +58,9 @@ export class UserInfoDatabaseHandler { const userInfo = JSON.parse(info.user_info); return userInfo; } + + static async create(options: { database: AuthDatabase }) { + const client = await options.database.get(); + return new UserInfoDatabase(client); + } } diff --git a/plugins/auth-backend/src/identity/StaticTokenIssuer.test.ts b/plugins/auth-backend/src/identity/StaticTokenIssuer.test.ts index ef6f7bd148..ad08427e85 100644 --- a/plugins/auth-backend/src/identity/StaticTokenIssuer.test.ts +++ b/plugins/auth-backend/src/identity/StaticTokenIssuer.test.ts @@ -18,8 +18,6 @@ import { createLocalJWKSet, jwtVerify } from 'jose'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { StaticKeyStore } from './StaticKeyStore'; import { mockServices } from '@backstage/backend-test-utils'; -import { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler'; -import { omit } from 'lodash'; const logger = mockServices.logger.mock(); const entityRef = stringifyEntityRef({ @@ -29,10 +27,6 @@ const entityRef = stringifyEntityRef({ }); describe('StaticTokenIssuer', () => { - const mockUserInfoDatabaseHandler = { - addUserInfo: jest.fn().mockResolvedValue(undefined), - } as unknown as UserInfoDatabaseHandler; - const staticKeyStore = { listKeys: () => { return Promise.resolve({ @@ -85,7 +79,6 @@ describe('StaticTokenIssuer', () => { logger, issuer: 'my-issuer', sessionExpirationSeconds: keyDurationSeconds, - userInfoDatabaseHandler: mockUserInfoDatabaseHandler, }, staticKeyStore as unknown as StaticKeyStore, ); @@ -119,9 +112,6 @@ describe('StaticTokenIssuer', () => { expect(verifyResult.payload.exp).toBe( verifyResult.payload.iat! + keyDurationSeconds, ); - expect(mockUserInfoDatabaseHandler.addUserInfo).toHaveBeenCalledWith({ - claims: omit(verifyResult.payload, ['aud', 'iat', 'iss', 'uip']), - }); }); it('should issue valid tokens with omitted claims', async () => { @@ -131,7 +121,6 @@ describe('StaticTokenIssuer', () => { logger, issuer: 'my-issuer', sessionExpirationSeconds: keyDurationSeconds, - userInfoDatabaseHandler: mockUserInfoDatabaseHandler, omitClaimsFromToken: ['ent'], }, staticKeyStore as unknown as StaticKeyStore, @@ -165,11 +154,5 @@ describe('StaticTokenIssuer', () => { expect(verifyResult.payload.exp).toBe( verifyResult.payload.iat! + keyDurationSeconds, ); - expect(mockUserInfoDatabaseHandler.addUserInfo).toHaveBeenCalledWith({ - claims: { - ...omit(verifyResult.payload, ['aud', 'iat', 'iss', 'uip']), - ent: [entityRef], - }, - }); }); }); diff --git a/plugins/auth-backend/src/identity/StaticTokenIssuer.ts b/plugins/auth-backend/src/identity/StaticTokenIssuer.ts index 6875e47457..965eb5164c 100644 --- a/plugins/auth-backend/src/identity/StaticTokenIssuer.ts +++ b/plugins/auth-backend/src/identity/StaticTokenIssuer.ts @@ -22,7 +22,6 @@ import { BackstageSignInResult, TokenParams, } from '@backstage/plugin-auth-node'; -import { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler'; import { issueUserToken } from './issueUserToken'; export type Config = { @@ -42,7 +41,6 @@ export type Options = { * A list of claims to omit from issued tokens and only store in the user info database */ omitClaimsFromToken?: string[]; - userInfoDatabaseHandler: UserInfoDatabaseHandler; }; /** @@ -55,7 +53,6 @@ export class StaticTokenIssuer implements TokenIssuer { private readonly keyStore: StaticKeyStore; private readonly sessionExpirationSeconds: number; private readonly omitClaimsFromToken?: string[]; - private readonly userInfoDatabaseHandler: UserInfoDatabaseHandler; public constructor(options: Options, keyStore: StaticKeyStore) { this.issuer = options.issuer; @@ -63,10 +60,11 @@ export class StaticTokenIssuer implements TokenIssuer { this.sessionExpirationSeconds = options.sessionExpirationSeconds; this.keyStore = keyStore; this.omitClaimsFromToken = options.omitClaimsFromToken; - this.userInfoDatabaseHandler = options.userInfoDatabaseHandler; } - public async issueToken(params: TokenParams): Promise { + public async issueToken( + params: TokenParams & { claims: { ent: string[] } }, + ): Promise { const key = await this.getSigningKey(); return issueUserToken({ @@ -76,7 +74,6 @@ export class StaticTokenIssuer implements TokenIssuer { logger: this.logger, omitClaimsFromToken: this.omitClaimsFromToken, params, - userInfoDatabaseHandler: this.userInfoDatabaseHandler, }); } diff --git a/plugins/auth-backend/src/identity/TokenFactory.test.ts b/plugins/auth-backend/src/identity/TokenFactory.test.ts index 764b26eda4..e6cd888be6 100644 --- a/plugins/auth-backend/src/identity/TokenFactory.test.ts +++ b/plugins/auth-backend/src/identity/TokenFactory.test.ts @@ -21,10 +21,8 @@ import { decodeProtectedHeader, jwtVerify, } from 'jose'; -import { omit } from 'lodash'; import { MemoryKeyStore } from './MemoryKeyStore'; import { TokenFactory } from './TokenFactory'; -import { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler'; import { tokenTypes } from '@backstage/plugin-auth-node'; import { mockServices } from '@backstage/backend-test-utils'; @@ -45,10 +43,6 @@ const entityRef = stringifyEntityRef({ }); describe('TokenFactory', () => { - const mockUserInfoDatabaseHandler = { - addUserInfo: jest.fn().mockResolvedValue(undefined), - } as unknown as UserInfoDatabaseHandler; - it('should issue valid tokens signed by a listed key', async () => { const keyDurationSeconds = 5; const factory = new TokenFactory({ @@ -56,7 +50,6 @@ describe('TokenFactory', () => { keyStore: new MemoryKeyStore(), keyDurationSeconds, logger, - userInfoDatabaseHandler: mockUserInfoDatabaseHandler, }); await expect(factory.listPublicKeys()).resolves.toEqual({ keys: [] }); @@ -93,10 +86,6 @@ describe('TokenFactory', () => { verifyResult.payload.iat! + keyDurationSeconds, ); - expect(mockUserInfoDatabaseHandler.addUserInfo).toHaveBeenCalledWith({ - claims: omit(verifyResult.payload, ['aud', 'iat', 'iss', 'uip']), - }); - // Emulate the reconstruction of a limited user token const limitedUserToken = [ base64url.encode( @@ -139,14 +128,13 @@ describe('TokenFactory', () => { keyStore: new MemoryKeyStore(), keyDurationSeconds: 5, logger, - userInfoDatabaseHandler: mockUserInfoDatabaseHandler, }); const { token: token1 } = await factory.issueToken({ - claims: { sub: entityRef }, + claims: { sub: entityRef, ent: [entityRef] }, }); const { token: token2 } = await factory.issueToken({ - claims: { sub: entityRef }, + claims: { sub: entityRef, ent: [entityRef] }, }); expect(jwtKid(token1)).toBe(jwtKid(token2)); @@ -165,7 +153,7 @@ describe('TokenFactory', () => { }); const { token: token3 } = await factory.issueToken({ - claims: { sub: entityRef }, + claims: { sub: entityRef, ent: [entityRef] }, }); expect(jwtKid(token3)).not.toBe(jwtKid(token2)); @@ -185,12 +173,11 @@ describe('TokenFactory', () => { keyStore: new MemoryKeyStore(), keyDurationSeconds, logger, - userInfoDatabaseHandler: mockUserInfoDatabaseHandler, }); await expect(() => { return factory.issueToken({ - claims: { sub: 'UserId' }, + claims: { sub: 'UserId', ent: [entityRef] }, }); }).rejects.toThrow(); }); @@ -203,12 +190,11 @@ describe('TokenFactory', () => { keyDurationSeconds, logger, algorithm: '', - userInfoDatabaseHandler: mockUserInfoDatabaseHandler, }); await expect(() => { return factory.issueToken({ - claims: { sub: 'UserId' }, + claims: { sub: 'UserId', ent: [entityRef] }, }); }).rejects.toThrow(); }); @@ -219,7 +205,6 @@ describe('TokenFactory', () => { keyStore: new MemoryKeyStore(), keyDurationSeconds: 5, logger, - userInfoDatabaseHandler: mockUserInfoDatabaseHandler, }); await expect(() => { @@ -238,7 +223,6 @@ describe('TokenFactory', () => { keyStore: new MemoryKeyStore(), keyDurationSeconds, logger, - userInfoDatabaseHandler: mockUserInfoDatabaseHandler, }); const { token } = await factory.issueToken({ diff --git a/plugins/auth-backend/src/identity/TokenFactory.ts b/plugins/auth-backend/src/identity/TokenFactory.ts index 5a8a789d17..d94825e322 100644 --- a/plugins/auth-backend/src/identity/TokenFactory.ts +++ b/plugins/auth-backend/src/identity/TokenFactory.ts @@ -25,7 +25,6 @@ import { } from '@backstage/plugin-auth-node'; import { AnyJWK, KeyStore, TokenIssuer } from './types'; import { JsonValue } from '@backstage/types'; -import { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler'; import { issueUserToken } from './issueUserToken'; /** @@ -92,7 +91,6 @@ type Options = { * A list of claims to omit from issued tokens and only store in the user info database */ omitClaimsFromToken?: string[]; - userInfoDatabaseHandler: UserInfoDatabaseHandler; }; /** @@ -116,7 +114,6 @@ export class TokenFactory implements TokenIssuer { private readonly keyDurationSeconds: number; private readonly algorithm: string; private readonly omitClaimsFromToken?: string[]; - private readonly userInfoDatabaseHandler: UserInfoDatabaseHandler; private keyExpiry?: Date; private privateKeyPromise?: Promise; @@ -128,10 +125,11 @@ export class TokenFactory implements TokenIssuer { this.keyDurationSeconds = options.keyDurationSeconds; this.algorithm = options.algorithm ?? 'ES256'; this.omitClaimsFromToken = options.omitClaimsFromToken; - this.userInfoDatabaseHandler = options.userInfoDatabaseHandler; } - async issueToken(params: TokenParams): Promise { + async issueToken( + params: TokenParams & { claims: { ent: string[] } }, + ): Promise { const key = await this.getKey(); return issueUserToken({ @@ -141,7 +139,6 @@ export class TokenFactory implements TokenIssuer { logger: this.logger, omitClaimsFromToken: this.omitClaimsFromToken, params, - userInfoDatabaseHandler: this.userInfoDatabaseHandler, }); } diff --git a/plugins/auth-backend/src/identity/issueUserToken.ts b/plugins/auth-backend/src/identity/issueUserToken.ts index d411439e6e..8deeb96cc3 100644 --- a/plugins/auth-backend/src/identity/issueUserToken.ts +++ b/plugins/auth-backend/src/identity/issueUserToken.ts @@ -22,7 +22,6 @@ import { tokenTypes, } from '@backstage/plugin-auth-node'; import { omit } from 'lodash'; -import { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler'; import { LoggerService } from '@backstage/backend-plugin-api'; import { GeneralSign, importJWK, JWK, KeyLike, SignJWT } from 'jose'; import { BackstageTokenPayload } from './TokenFactory'; @@ -37,17 +36,15 @@ export async function issueUserToken({ logger, omitClaimsFromToken, params, - userInfoDatabaseHandler, }: { issuer: string; key: JWK; keyDurationSeconds: number; logger: LoggerService; omitClaimsFromToken?: string[]; - params: TokenParams; - userInfoDatabaseHandler: UserInfoDatabaseHandler; + params: TokenParams & { claims: { ent: string[] } }; }): Promise { - const { sub, ent = [sub], ...additionalClaims } = params.claims; + const { sub, ent, ...additionalClaims } = params.claims; const aud = tokenTypes.user.audClaim; const iat = Math.floor(Date.now() / MS_IN_S); const exp = iat + keyDurationSeconds; @@ -109,12 +106,6 @@ export async function issueUserToken({ ); } - // Store the user info in the database upon successful token - // issuance so that it can be retrieved later by limited user tokens - await userInfoDatabaseHandler.addUserInfo({ - claims: omit(claims, ['aud', 'iat', 'iss', 'uip']), - }); - return { token, identity: { diff --git a/plugins/auth-backend/src/identity/router.test.ts b/plugins/auth-backend/src/identity/router.test.ts deleted file mode 100644 index 9ede44f195..0000000000 --- a/plugins/auth-backend/src/identity/router.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - coreServices, - createBackendPlugin, -} from '@backstage/backend-plugin-api'; -import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; -import Router from 'express-promise-router'; -import request from 'supertest'; -import { bindOidcRouter } from './router'; -import { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler'; - -describe('bindOidcRouter', () => { - it('should return user info for full tokens', async () => { - const auth = mockServices.auth.mock(); - const mockUserInfoDatabaseHandler = { - getUserInfo: jest.fn().mockResolvedValue({ - claims: { - sub: 'k/ns:n', - ent: ['k/ns:a', 'k/ns:b'], - }, - }), - } as unknown as UserInfoDatabaseHandler; - - const { server } = await startTestBackend({ - features: [ - createBackendPlugin({ - pluginId: 'auth', - register(reg) { - reg.registerInit({ - deps: { httpRouter: coreServices.httpRouter }, - async init({ httpRouter }) { - const router = Router(); - bindOidcRouter(router, { - baseUrl: 'http://localhost:7000', - auth, - tokenIssuer: {} as any, - userInfoDatabaseHandler: mockUserInfoDatabaseHandler, - }); - httpRouter.use(router); - httpRouter.addAuthPolicy({ - path: '/', - allow: 'unauthenticated', - }); - }, - }); - }, - }), - ], - }); - - auth.authenticate.mockResolvedValueOnce({} as any); - auth.isPrincipal.mockReturnValueOnce(true); - - await request(server) - .get('/api/auth/v1/userinfo') - .set( - 'Authorization', - `Bearer h.${btoa( - JSON.stringify({ sub: 'k/ns:n', ent: ['k/ns:a', 'k/ns:b'] }), - )}.s`, - ) - .expect(200, { - claims: { - sub: 'k/ns:n', - ent: ['k/ns:a', 'k/ns:b'], - }, - }); - - expect(mockUserInfoDatabaseHandler.getUserInfo).toHaveBeenCalledWith( - 'k/ns:n', - ); - }); - - it('should return user info for limited tokens', async () => { - const auth = mockServices.auth.mock(); - const mockUserInfoDatabaseHandler = { - getUserInfo: jest.fn().mockResolvedValue({ - claims: { - sub: 'k/ns:n', - ent: ['k/ns:a', 'k/ns:b'], - }, - }), - } as unknown as UserInfoDatabaseHandler; - - const { server } = await startTestBackend({ - features: [ - createBackendPlugin({ - pluginId: 'auth', - register(reg) { - reg.registerInit({ - deps: { httpRouter: coreServices.httpRouter }, - async init({ httpRouter }) { - const router = Router(); - bindOidcRouter(router, { - baseUrl: 'http://localhost:7000', - auth, - tokenIssuer: {} as any, - userInfoDatabaseHandler: mockUserInfoDatabaseHandler, - }); - httpRouter.use(router); - httpRouter.addAuthPolicy({ - path: '/', - allow: 'unauthenticated', - }); - }, - }); - }, - }), - ], - }); - - auth.authenticate.mockResolvedValueOnce({} as any); - auth.isPrincipal.mockReturnValueOnce(true); - - await request(server) - .get('/api/auth/v1/userinfo') - .set( - 'Authorization', - `Bearer h.${btoa(JSON.stringify({ sub: 'k/ns:n' }))}.s`, - ) - .expect(200, { - claims: { - sub: 'k/ns:n', - ent: ['k/ns:a', 'k/ns:b'], - }, - }); - - expect(mockUserInfoDatabaseHandler.getUserInfo).toHaveBeenCalledWith( - 'k/ns:n', - ); - }); -}); diff --git a/plugins/auth-backend/src/identity/router.ts b/plugins/auth-backend/src/identity/router.ts deleted file mode 100644 index 3952ec64b5..0000000000 --- a/plugins/auth-backend/src/identity/router.ts +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express from 'express'; -import Router from 'express-promise-router'; -import { TokenIssuer } from './types'; -import { AuthService } from '@backstage/backend-plugin-api'; -import { decodeJwt } from 'jose'; -import { AuthenticationError, InputError } from '@backstage/errors'; -import { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler'; - -export function bindOidcRouter( - targetRouter: express.Router, - options: { - baseUrl: string; - auth: AuthService; - tokenIssuer: TokenIssuer; - userInfoDatabaseHandler: UserInfoDatabaseHandler; - }, -) { - const { baseUrl, auth, tokenIssuer, userInfoDatabaseHandler } = options; - - const router = Router(); - targetRouter.use(router); - - const config = { - issuer: baseUrl, - token_endpoint: `${baseUrl}/v1/token`, - userinfo_endpoint: `${baseUrl}/v1/userinfo`, - jwks_uri: `${baseUrl}/.well-known/jwks.json`, - response_types_supported: ['id_token'], - subject_types_supported: ['public'], - id_token_signing_alg_values_supported: [ - 'RS256', - 'RS384', - 'RS512', - 'ES256', - 'ES384', - 'ES512', - 'PS256', - 'PS384', - 'PS512', - 'EdDSA', - ], - scopes_supported: ['openid'], - token_endpoint_auth_methods_supported: [], - claims_supported: ['sub', 'ent'], - grant_types_supported: [], - }; - - router.get('/.well-known/openid-configuration', (_req, res) => { - res.json(config); - }); - - router.get('/.well-known/jwks.json', async (_req, res) => { - const { keys } = await tokenIssuer.listPublicKeys(); - res.json({ keys }); - }); - - router.get('/v1/token', (_req, res) => { - res.status(501).send('Not Implemented'); - }); - - // This endpoint doesn't use the regular HttpAuthService, since the contract - // is specifically for the header to be communicated in the Authorization - // header, regardless of token type - router.get('/v1/userinfo', async (req, res) => { - const matches = req.headers.authorization?.match(/^Bearer[ ]+(\S+)$/i); - const token = matches?.[1]; - if (!token) { - throw new AuthenticationError('No token provided'); - } - - const credentials = await auth.authenticate(token, { - allowLimitedAccess: true, - }); - if (!auth.isPrincipal(credentials, 'user')) { - throw new InputError( - 'Userinfo endpoint must be called with a token that represents a user principal', - ); - } - - const { sub: userEntityRef } = decodeJwt(token); - - if (typeof userEntityRef !== 'string') { - throw new Error('Invalid user token, user entity ref must be a string'); - } - - const userInfo = await userInfoDatabaseHandler.getUserInfo(userEntityRef); - if (!userInfo) { - res.status(404).send('User info not found'); - return; - } - - res.json(userInfo); - }); -} diff --git a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.test.ts b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.test.ts index 4426cb7815..d0323e7651 100644 --- a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.test.ts +++ b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.test.ts @@ -19,6 +19,7 @@ import { mockServices } from '@backstage/backend-test-utils'; import { TokenIssuer } from '../../identity/types'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; import { NotFoundError } from '@backstage/errors'; +import { UserInfoDatabase } from '../../database/UserInfoDatabase'; describe('CatalogAuthResolverContext', () => { beforeEach(() => { @@ -28,6 +29,16 @@ describe('CatalogAuthResolverContext', () => { const catalog = catalogServiceMock(); jest.spyOn(catalog, 'getEntities'); + const mockUserInfo = { + addUserInfo: jest.fn().mockResolvedValue(undefined), + getUserInfo: jest.fn().mockResolvedValue({ + claims: { + sub: 'user:default/user', + ent: ['user:default/user'], + }, + }), + } as unknown as jest.Mocked; + it('adds kind to filter when missing', async () => { const auth = mockServices.auth(); const context = CatalogAuthResolverContext.create({ @@ -35,6 +46,7 @@ describe('CatalogAuthResolverContext', () => { catalog, tokenIssuer: {} as TokenIssuer, auth, + userInfo: mockUserInfo, }); await expect( diff --git a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts index 5268833d14..269c7131dc 100644 --- a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts +++ b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts @@ -32,6 +32,7 @@ import { TokenParams, } from '@backstage/plugin-auth-node'; import { CatalogIdentityClient } from '../catalog/CatalogIdentityClient'; +import { UserInfoDatabase } from '../../database/UserInfoDatabase'; function getDefaultOwnershipEntityRefs(entity: Entity) { const membershipRefs = @@ -51,6 +52,7 @@ export class CatalogAuthResolverContext implements AuthResolverContext { tokenIssuer: TokenIssuer; auth: AuthService; ownershipResolver?: AuthOwnershipResolver; + userInfo: UserInfoDatabase; }): CatalogAuthResolverContext { const catalogIdentityClient = new CatalogIdentityClient({ catalog: options.catalog, @@ -63,6 +65,7 @@ export class CatalogAuthResolverContext implements AuthResolverContext { catalogIdentityClient, options.catalog, options.auth, + options.userInfo, options.ownershipResolver, ); } @@ -73,11 +76,29 @@ export class CatalogAuthResolverContext implements AuthResolverContext { public readonly catalogIdentityClient: CatalogIdentityClient, private readonly catalog: CatalogService, private readonly auth: AuthService, + private readonly userInfo: UserInfoDatabase, private readonly ownershipResolver?: AuthOwnershipResolver, ) {} async issueToken(params: TokenParams) { - return await this.tokenIssuer.issueToken(params); + const { sub, ent = [sub], ...additionalClaims } = params.claims; + const claims = { + sub, + ent, + ...additionalClaims, + }; + + const issuedToken = await this.tokenIssuer.issueToken({ + claims, + }); + + // Store the user info in the database upon successful token + // issuance so that it can be retrieved later by limited user tokens + await this.userInfo.addUserInfo({ + claims, + }); + + return issuedToken; } async findCatalogUser(query: AuthResolverCatalogUserQuery) { @@ -139,19 +160,54 @@ export class CatalogAuthResolverContext implements AuthResolverContext { return { entity: result }; } - async signInWithCatalogUser(query: AuthResolverCatalogUserQuery) { - const { entity } = await this.findCatalogUser(query); + async signInWithCatalogUser( + query: AuthResolverCatalogUserQuery, + options?: { + dangerousEntityRefFallback?: { + entityRef: + | string + | { + kind?: string; + namespace?: string; + name: string; + }; + }; + }, + ) { + try { + const { entity } = await this.findCatalogUser(query); - const { ownershipEntityRefs } = await this.resolveOwnershipEntityRefs( - entity, - ); + const { ownershipEntityRefs } = await this.resolveOwnershipEntityRefs( + entity, + ); - return await this.tokenIssuer.issueToken({ - claims: { - sub: stringifyEntityRef(entity), - ent: ownershipEntityRefs, - }, - }); + return await this.issueToken({ + claims: { + sub: stringifyEntityRef(entity), + ent: ownershipEntityRefs, + }, + }); + } catch (error) { + if ( + error?.name !== 'NotFoundError' || + !options?.dangerousEntityRefFallback + ) { + throw error; + } + const userEntityRef = stringifyEntityRef( + parseEntityRef(options.dangerousEntityRefFallback.entityRef, { + defaultKind: 'User', + defaultNamespace: DEFAULT_NAMESPACE, + }), + ); + + return await this.issueToken({ + claims: { + sub: userEntityRef, + ent: [userEntityRef], + }, + }); + } } async resolveOwnershipEntityRefs( diff --git a/plugins/auth-backend/src/migrations.test.ts b/plugins/auth-backend/src/migrations.test.ts index b4a6572a86..f9575c70fd 100644 --- a/plugins/auth-backend/src/migrations.test.ts +++ b/plugins/auth-backend/src/migrations.test.ts @@ -107,4 +107,83 @@ describe('migrations', () => { await knex.destroy(); }, ); + + it.each(databases.eachSupportedId())( + '20250707164600_user_created_at.js, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateUntilBefore(knex, '20250707164600_user_created_at.js'); + + if (knex.client.config.client.includes('sqlite')) { + // Sqlite doesn't support adding a column with non-constant default when table has data + // so we just test that the migration runs without errors + await migrateUpOnce(knex); + + return; + } + + const user_info = JSON.stringify({ + claims: { + ent: ['group:default/group1', 'group:default/group2'], + }, + }); + + await knex + .insert({ + user_entity_ref: 'user:default/backstage-user', + user_info, + exp: knex.fn.now(), + }) + .into('user_info'); + + const { exp } = await knex('user_info').first(); + + await migrateUpOnce(knex); + + const { created_at, updated_at } = await knex('user_info').first(); + + expect(updated_at).toEqual(exp); + expect(created_at).toBeDefined(); + + await knex + .insert({ + user_entity_ref: 'user:default/backstage-user', + user_info, + updated_at: knex.fn.now(), + }) + .into('user_info') + .onConflict(['user_entity_ref']) + .merge(); + + await knex + .insert({ + user_entity_ref: 'user:default/backstage-user-2', + user_info, + updated_at: knex.fn.now(), + }) + .into('user_info'); + + await expect( + knex('user_info').select('created_at', 'updated_at'), + ).resolves.toEqual([ + { + created_at: expect.any(Date), + updated_at: expect.any(Date), + }, + { + created_at: expect.any(Date), + updated_at: expect.any(Date), + }, + ]); + + await migrateDownOnce(knex); + + await expect(knex('user_info').select('exp')).resolves.toEqual([ + { exp: expect.any(Date) }, + { exp: expect.any(Date) }, + ]); + + await knex.destroy(); + }, + ); }); diff --git a/plugins/auth-backend/src/providers/router.ts b/plugins/auth-backend/src/providers/router.ts index d04347e7b3..8505c1f2bd 100644 --- a/plugins/auth-backend/src/providers/router.ts +++ b/plugins/auth-backend/src/providers/router.ts @@ -27,6 +27,7 @@ import Router from 'express-promise-router'; import { Minimatch } from 'minimatch'; import { CatalogAuthResolverContext } from '../lib/resolvers/CatalogAuthResolverContext'; import { TokenIssuer } from '../identity/types'; +import { UserInfoDatabase } from '../database/UserInfoDatabase'; export type ProviderFactories = { [s: string]: AuthProviderFactory }; @@ -40,6 +41,7 @@ export function bindProviderRouters( logger: LoggerService; auth: AuthService; tokenIssuer: TokenIssuer; + userInfo: UserInfoDatabase; ownershipResolver?: AuthOwnershipResolver; catalog: CatalogService; }, @@ -54,6 +56,7 @@ export function bindProviderRouters( tokenIssuer, catalog, ownershipResolver, + userInfo, } = options; const providersConfig = config.getOptionalConfig('auth.providers'); @@ -82,6 +85,7 @@ export function bindProviderRouters( tokenIssuer, auth, ownershipResolver, + userInfo, }), }); diff --git a/plugins/auth-backend/src/service/OidcRouter.test.ts b/plugins/auth-backend/src/service/OidcRouter.test.ts new file mode 100644 index 0000000000..dbc6c88f93 --- /dev/null +++ b/plugins/auth-backend/src/service/OidcRouter.test.ts @@ -0,0 +1,151 @@ +/* + * 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 { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import Router from 'express-promise-router'; +import request from 'supertest'; +import { OidcRouter } from './OidcRouter'; +import { UserInfoDatabase } from '../database/UserInfoDatabase'; + +describe('OidcRouter', () => { + describe('/v1/userinfo', () => { + it('should return user info for full tokens', async () => { + const auth = mockServices.auth.mock(); + const mockUserInfo = { + getUserInfo: jest.fn().mockResolvedValue({ + claims: { + sub: 'k/ns:n', + ent: ['k/ns:a', 'k/ns:b'], + }, + }), + } as unknown as UserInfoDatabase; + + const { server } = await startTestBackend({ + features: [ + createBackendPlugin({ + pluginId: 'auth', + register(reg) { + reg.registerInit({ + deps: { httpRouter: coreServices.httpRouter }, + async init({ httpRouter }) { + const router = Router(); + + router.use( + OidcRouter.create({ + auth, + tokenIssuer: {} as any, + baseUrl: 'http://localhost:7000', + userInfo: mockUserInfo, + }).getRouter(), + ); + httpRouter.use(router); + httpRouter.addAuthPolicy({ + path: '/', + allow: 'unauthenticated', + }); + }, + }); + }, + }), + ], + }); + + auth.authenticate.mockResolvedValueOnce({} as any); + auth.isPrincipal.mockReturnValueOnce(true); + + await request(server) + .get('/api/auth/v1/userinfo') + .set( + 'Authorization', + `Bearer h.${btoa( + JSON.stringify({ sub: 'k/ns:n', ent: ['k/ns:a', 'k/ns:b'] }), + )}.s`, + ) + .expect(200, { + claims: { + sub: 'k/ns:n', + ent: ['k/ns:a', 'k/ns:b'], + }, + }); + + expect(mockUserInfo.getUserInfo).toHaveBeenCalledWith('k/ns:n'); + }); + + it('should return user info for limited tokens', async () => { + const auth = mockServices.auth.mock(); + const mockUserInfo = { + getUserInfo: jest.fn().mockResolvedValue({ + claims: { + sub: 'k/ns:n', + ent: ['k/ns:a', 'k/ns:b'], + }, + }), + } as unknown as UserInfoDatabase; + + const { server } = await startTestBackend({ + features: [ + createBackendPlugin({ + pluginId: 'auth', + register(reg) { + reg.registerInit({ + deps: { httpRouter: coreServices.httpRouter }, + async init({ httpRouter }) { + const router = Router(); + + router.use( + OidcRouter.create({ + auth, + tokenIssuer: {} as any, + baseUrl: 'http://localhost:7000', + userInfo: mockUserInfo, + }).getRouter(), + ); + httpRouter.use(router); + httpRouter.addAuthPolicy({ + path: '/', + allow: 'unauthenticated', + }); + }, + }); + }, + }), + ], + }); + + auth.authenticate.mockResolvedValueOnce({} as any); + auth.isPrincipal.mockReturnValueOnce(true); + + await request(server) + .get('/api/auth/v1/userinfo') + .set( + 'Authorization', + `Bearer h.${btoa(JSON.stringify({ sub: 'k/ns:n' }))}.s`, + ) + .expect(200, { + claims: { + sub: 'k/ns:n', + ent: ['k/ns:a', 'k/ns:b'], + }, + }); + + expect(mockUserInfo.getUserInfo).toHaveBeenCalledWith('k/ns:n'); + }); + }); +}); diff --git a/plugins/auth-backend/src/service/OidcRouter.ts b/plugins/auth-backend/src/service/OidcRouter.ts new file mode 100644 index 0000000000..6ada071c44 --- /dev/null +++ b/plugins/auth-backend/src/service/OidcRouter.ts @@ -0,0 +1,73 @@ +/* + * 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 Router from 'express-promise-router'; +import { OidcService } from './OidcService'; +import { AuthenticationError } from '@backstage/errors'; +import { AuthService } from '@backstage/backend-plugin-api'; +import { TokenIssuer } from '../identity/types'; +import { UserInfoDatabase } from '../database/UserInfoDatabase'; + +export class OidcRouter { + private constructor(private readonly oidc: OidcService) {} + + static create(options: { + auth: AuthService; + tokenIssuer: TokenIssuer; + baseUrl: string; + userInfo: UserInfoDatabase; + }) { + return new OidcRouter(OidcService.create(options)); + } + + public getRouter() { + const router = Router(); + + router.get('/.well-known/openid-configuration', (_req, res) => { + res.json(this.oidc.getConfiguration()); + }); + + router.get('/.well-known/jwks.json', async (_req, res) => { + const { keys } = await this.oidc.listPublicKeys(); + res.json({ keys }); + }); + + router.get('/v1/token', (_req, res) => { + res.status(501).send('Not Implemented'); + }); + + // This endpoint doesn't use the regular HttpAuthoidc, since the contract + // is specifically for the header to be communicated in the Authorization + // header, regardless of token type + router.get('/v1/userinfo', async (req, res) => { + const matches = req.headers.authorization?.match(/^Bearer[ ]+(\S+)$/i); + const token = matches?.[1]; + if (!token) { + throw new AuthenticationError('No token provided'); + } + + const userInfo = await this.oidc.getUserInfo({ token }); + + if (!userInfo) { + res.status(404).send('User info not found'); + return; + } + + res.json(userInfo); + }); + + return router; + } +} diff --git a/plugins/auth-backend/src/service/OidcService.ts b/plugins/auth-backend/src/service/OidcService.ts new file mode 100644 index 0000000000..5024b2cc8c --- /dev/null +++ b/plugins/auth-backend/src/service/OidcService.ts @@ -0,0 +1,92 @@ +/* + * 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 { AuthService } from '@backstage/backend-plugin-api'; +import { TokenIssuer } from '../identity/types'; +import { UserInfoDatabase } from '../database/UserInfoDatabase'; +import { InputError } from '@backstage/errors'; +import { decodeJwt } from 'jose'; + +export class OidcService { + private constructor( + private readonly auth: AuthService, + private readonly tokenIssuer: TokenIssuer, + private readonly baseUrl: string, + private readonly userInfo: UserInfoDatabase, + ) {} + + static create(options: { + auth: AuthService; + tokenIssuer: TokenIssuer; + baseUrl: string; + userInfo: UserInfoDatabase; + }) { + return new OidcService( + options.auth, + options.tokenIssuer, + options.baseUrl, + options.userInfo, + ); + } + + public getConfiguration() { + return { + issuer: this.baseUrl, + token_endpoint: `${this.baseUrl}/v1/token`, + userinfo_endpoint: `${this.baseUrl}/v1/userinfo`, + jwks_uri: `${this.baseUrl}/.well-known/jwks.json`, + response_types_supported: ['id_token'], + subject_types_supported: ['public'], + id_token_signing_alg_values_supported: [ + 'RS256', + 'RS384', + 'RS512', + 'ES256', + 'ES384', + 'ES512', + 'PS256', + 'PS384', + 'PS512', + 'EdDSA', + ], + scopes_supported: ['openid'], + token_endpoint_auth_methods_supported: [], + claims_supported: ['sub', 'ent'], + grant_types_supported: [], + }; + } + + public async listPublicKeys() { + return await this.tokenIssuer.listPublicKeys(); + } + + public async getUserInfo({ token }: { token: string }) { + const credentials = await this.auth.authenticate(token, { + allowLimitedAccess: true, + }); + if (!this.auth.isPrincipal(credentials, 'user')) { + throw new InputError( + 'Userinfo endpoint must be called with a token that represents a user principal', + ); + } + + const { sub: userEntityRef } = decodeJwt(token); + + if (typeof userEntityRef !== 'string') { + throw new Error('Invalid user token, user entity ref must be a string'); + } + return await this.userInfo.getUserInfo(userEntityRef); + } +} diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index fd9373d5a3..5012c90106 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -27,10 +27,9 @@ import { import { AuthOwnershipResolver } from '@backstage/plugin-auth-node'; import { CatalogService } from '@backstage/plugin-catalog-node'; import { NotFoundError } from '@backstage/errors'; -import { bindOidcRouter } from '../identity/router'; import { KeyStores } from '../identity/KeyStores'; import { TokenFactory } from '../identity/TokenFactory'; -import { UserInfoDatabaseHandler } from '../identity/UserInfoDatabaseHandler'; +import { UserInfoDatabase } from '../database/UserInfoDatabase'; import session from 'express-session'; import connectSessionKnex from 'connect-session-knex'; import passport from 'passport'; @@ -40,6 +39,7 @@ import { TokenIssuer } from '../identity/types'; import { StaticTokenIssuer } from '../identity/StaticTokenIssuer'; import { StaticKeyStore } from '../identity/StaticKeyStore'; import { bindProviderRouters, ProviderFactories } from '../providers/router'; +import { OidcRouter } from './OidcRouter'; interface RouterOptions { logger: LoggerService; @@ -60,7 +60,7 @@ export async function createRouter( logger, config, discovery, - database, + database: db, tokenFactoryAlgorithm, providerFactories = {}, } = options; @@ -70,16 +70,16 @@ export async function createRouter( const appUrl = config.getString('app.baseUrl'); const authUrl = await discovery.getExternalBaseUrl('auth'); const backstageTokenExpiration = readBackstageTokenExpiration(config); - const authDb = AuthDatabase.create(database); + const database = AuthDatabase.create(db); const keyStore = await KeyStores.fromConfig(config, { logger, - database: authDb, + database, }); - const userInfoDatabaseHandler = new UserInfoDatabaseHandler( - await authDb.get(), - ); + const userInfo = await UserInfoDatabase.create({ + database, + }); const omitClaimsFromToken = config.getOptionalBoolean( 'auth.omitIdentityTokenOwnershipClaim', @@ -94,7 +94,6 @@ export async function createRouter( logger: logger.child({ component: 'token-factory' }), issuer: authUrl, sessionExpirationSeconds: backstageTokenExpiration, - userInfoDatabaseHandler, omitClaimsFromToken, }, keyStore as StaticKeyStore, @@ -108,7 +107,6 @@ export async function createRouter( algorithm: tokenFactoryAlgorithm ?? config.getOptionalString('auth.identityTokenAlgorithm'), - userInfoDatabaseHandler, omitClaimsFromToken, }); } @@ -126,7 +124,7 @@ export async function createRouter( cookie: { secure: enforceCookieSSL ? 'auto' : false }, store: new KnexSessionStore({ createtable: false, - knex: await authDb.get(), + knex: await database.get(), }), }), ); @@ -146,15 +144,18 @@ export async function createRouter( tokenIssuer, ...options, auth: options.auth, + userInfo, }); - bindOidcRouter(router, { + const oidcRouter = OidcRouter.create({ auth: options.auth, tokenIssuer, baseUrl: authUrl, - userInfoDatabaseHandler, + userInfo, }); + router.use(oidcRouter.getRouter()); + // Gives a more helpful error message than a plain 404 router.use('/:provider/', req => { const { provider } = req.params; diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index 7fd817c2aa..af42151b67 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,167 @@ # @backstage/plugin-auth-node +## 0.6.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.6.5 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + +## 0.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + +## 0.6.4 + +### Patch Changes + +- 0169b23: Internal tweak to avoid circular dependencies +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/backend-plugin-api@1.4.0 + - @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.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 + +- 332e934: Added the `identity` property to `BackstageSignInResult`. + + The `prepareBackstageIdentityResponse` function will now also forward the `identity` to the response if present in the provided sign-in result. + +- ab53e6f: Added a new `dangerousEntityRefFallback` option to the `signInWithCatalogUser` method in `AuthResolverContext`. The option will cause the provided entity reference to be used as a fallback in case the user is not found in the catalog. It is up to the caller to provide the fallback entity reference. + + Auth providers that include pre-defined sign-in resolvers are encouraged to define a flag named `dangerouslyAllowSignInWithoutUserInCatalog` in their config, which in turn enables use of the `dangerousEntityRefFallback` option. For example: + + ```ts + export const usernameMatchingUserEntityName = createSignInResolverFactory({ + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { + return async ( + info: SignInInfo>, + ctx, + ) => { + const { username } = info.result.fullProfile; + if (!username) { + throw new Error('User profile does not contain a username'); + } + + return ctx.signInWithCatalogUser( + { entityRef: { name: username } }, + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: username } } + : undefined, + }, + ); + }; + }, + }); + ``` + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.6.3-next.2 + +### Patch Changes + +- ab53e6f: Added a new `dangerousEntityRefFallback` option to the `signInWithCatalogUser` method in `AuthResolverContext`. The option will cause the provided entity reference to be used as a fallback in case the user is not found in the catalog. It is up to the caller to provide the fallback entity reference. + + Auth providers that include pre-defined sign-in resolvers are encouraged to define a flag named `dangerouslyAllowSignInWithoutUserInCatalog` in their config, which in turn enables use of the `dangerousEntityRefFallback` option. For example: + + ```ts + export const usernameMatchingUserEntityName = createSignInResolverFactory({ + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { + return async ( + info: SignInInfo>, + ctx, + ) => { + const { username } = info.result.fullProfile; + if (!username) { + throw new Error('User profile does not contain a username'); + } + + return ctx.signInWithCatalogUser( + { entityRef: { name: username } }, + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: username } } + : undefined, + }, + ); + }; + }, + }); + ``` + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.6.3-next.1 ### Patch Changes diff --git a/plugins/auth-node/catalog-info.yaml b/plugins/auth-node/catalog-info.yaml index 4b23642810..936aae901e 100644 --- a/plugins/auth-node/catalog-info.yaml +++ b/plugins/auth-node/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 744bff38ef..4485b1b249 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-next.1", + "version": "0.6.6-next.0", "backstage": { "role": "node-library", "pluginId": "auth", diff --git a/plugins/auth-node/report.api.md b/plugins/auth-node/report.api.md index ad0d85030c..69e953cf13 100644 --- a/plugins/auth-node/report.api.md +++ b/plugins/auth-node/report.api.md @@ -3,8 +3,6 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackstageIdentityResponse as BackstageIdentityResponse_2 } from '@backstage/plugin-auth-node'; -import { BackstageSignInResult as BackstageSignInResult_2 } from '@backstage/plugin-auth-node'; import { Config } from '@backstage/config'; import { DiscoveryService } from '@backstage/backend-plugin-api'; import { Entity } from '@backstage/catalog-model'; @@ -110,6 +108,17 @@ export type AuthResolverContext = { }>; signInWithCatalogUser( query: AuthResolverCatalogUserQuery, + options?: { + dangerousEntityRefFallback?: { + entityRef: + | string + | { + kind?: string; + namespace?: string; + name: string; + }; + }; + }, ): Promise; resolveOwnershipEntityRefs(entity: Entity): Promise<{ ownershipEntityRefs: string[]; @@ -146,12 +155,17 @@ export type ClientAuthResponse = { export namespace commonSignInResolvers { const emailMatchingUserEntityProfileEmail: SignInResolverFactory< unknown, - unknown + | { + allowedDomains?: string[] | undefined; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined >; const emailLocalPartMatchingUserEntityName: SignInResolverFactory< unknown, | { allowedDomains?: string[] | undefined; + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; } | undefined >; @@ -559,8 +573,8 @@ export type PassportProfile = Profile & { // @public export function prepareBackstageIdentityResponse( - result: BackstageSignInResult_2, -): BackstageIdentityResponse_2; + result: BackstageSignInResult, +): BackstageIdentityResponse; // @public export type ProfileInfo = { diff --git a/plugins/auth-node/src/identity/prepareBackstageIdentityResponse.ts b/plugins/auth-node/src/identity/prepareBackstageIdentityResponse.ts index a1de4e365c..aa827cef3f 100644 --- a/plugins/auth-node/src/identity/prepareBackstageIdentityResponse.ts +++ b/plugins/auth-node/src/identity/prepareBackstageIdentityResponse.ts @@ -15,10 +15,7 @@ */ import { InputError } from '@backstage/errors'; -import { - BackstageIdentityResponse, - BackstageSignInResult, -} from '@backstage/plugin-auth-node'; +import { BackstageIdentityResponse, BackstageSignInResult } from '../types'; function parseJwtPayload(token: string) { const [_header, payload, _signature] = token.split('.'); diff --git a/plugins/auth-node/src/sign-in/commonSignInResolvers.ts b/plugins/auth-node/src/sign-in/commonSignInResolvers.ts index 6f0fc7fdb2..2b7442f746 100644 --- a/plugins/auth-node/src/sign-in/commonSignInResolvers.ts +++ b/plugins/auth-node/src/sign-in/commonSignInResolvers.ts @@ -35,7 +35,13 @@ export namespace commonSignInResolvers { */ export const emailMatchingUserEntityProfileEmail = createSignInResolverFactory({ - create() { + optionsSchema: z + .object({ + allowedDomains: z.array(z.string()).optional(), + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { return async (info, ctx) => { const { profile } = info; @@ -59,11 +65,19 @@ export namespace commonSignInResolvers { const [_, name, _plus, domain] = m; const noPlusEmail = `${name}${domain}`; - return ctx.signInWithCatalogUser({ - filter: { - 'spec.profile.email': noPlusEmail, + return ctx.signInWithCatalogUser( + { + filter: { + 'spec.profile.email': noPlusEmail, + }, }, - }); + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: noPlusEmail } } + : undefined, + }, + ); } } // Email had no plus addressing or is missing in the catalog, forward failure @@ -82,6 +96,7 @@ export namespace commonSignInResolvers { optionsSchema: z .object({ allowedDomains: z.array(z.string()).optional(), + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), }) .optional(), create(options = {}) { @@ -102,10 +117,15 @@ export namespace commonSignInResolvers { 'Sign-in user email is not from an allowed domain', ); } - - return ctx.signInWithCatalogUser({ - entityRef: { name: localPart }, - }); + return ctx.signInWithCatalogUser( + { entityRef: { name: localPart } }, + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: localPart } } + : undefined, + }, + ); }; }, }); diff --git a/plugins/auth-node/src/types.ts b/plugins/auth-node/src/types.ts index a7202d7748..8d6b8f34fb 100644 --- a/plugins/auth-node/src/types.ts +++ b/plugins/auth-node/src/types.ts @@ -162,10 +162,26 @@ export type AuthResolverContext = { * Finds a single user in the catalog using the provided query, and then * issues an identity for that user using default ownership resolution. * + * If the user is not found, an optional `dangerousEntityRefFallback` + * entity ref can be provided to allow sign-in to proceed by issuing an + * identity based on the given ref. This bypasses the requirement for the + * user to exist in the catalog and should be used with caution. + * * See {@link AuthResolverCatalogUserQuery} for details. */ signInWithCatalogUser( query: AuthResolverCatalogUserQuery, + options?: { + dangerousEntityRefFallback?: { + entityRef: + | string + | { + kind?: string; + namespace?: string; + name: string; + }; + }; + }, ): Promise; /** diff --git a/plugins/auth-react/CHANGELOG.md b/plugins/auth-react/CHANGELOG.md index 0a1043daa1..f7df9d4b11 100644 --- a/plugins/auth-react/CHANGELOG.md +++ b/plugins/auth-react/CHANGELOG.md @@ -1,5 +1,67 @@ # @backstage/plugin-auth-react +## 0.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + +## 0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + +## 0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## 0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + +## 0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + +## 0.1.15 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + ## 0.1.15-next.1 ### Patch Changes diff --git a/plugins/auth-react/catalog-info.yaml b/plugins/auth-react/catalog-info.yaml index b75aa32c56..1118130d74 100644 --- a/plugins/auth-react/catalog-info.yaml +++ b/plugins/auth-react/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-react/package.json b/plugins/auth-react/package.json index 3a04afbf51..813399f751 100644 --- a/plugins/auth-react/package.json +++ b/plugins/auth-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-react", - "version": "0.1.15-next.1", + "version": "0.1.18-next.0", "description": "Web library for the auth plugin", "backstage": { "role": "web-library", diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md index 5cff146b61..637250335d 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,50 @@ # @backstage/plugin-bitbucket-cloud-common +## 0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + +## 0.3.0 + +### Minor Changes + +- 5850717: Update Bitbucket Cloud schema and models. + + The latest schema was fetched from Bitbucket Cloud and stored locally. + Based on the updated schema, the models got regenerated. + + **BREAKING:** + + Due to the schema changes, the model update includes one breaking change: + + - `Account.username` was removed. + + Additionally, there were a couple of compatible changes including the addition of + `BaseCommit.committer` and others. + +### Patch Changes + +- 57ad208: Add support for `repo:updated` events as `Events.RepoUpdatedEvent`. +- Updated dependencies + - @backstage/integration@1.17.0 + +## 0.3.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + ## 0.3.0-next.2 ### Patch Changes diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index 7b336e3455..60b0b10970 100644 --- a/plugins/bitbucket-cloud-common/package.json +++ b/plugins/bitbucket-cloud-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bitbucket-cloud-common", - "version": "0.3.0-next.2", + "version": "0.3.1", "description": "Common functionalities for bitbucket-cloud plugins", "backstage": { "role": "common-library", diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 600a4f7e93..8592e6a82a 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,157 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.4.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-kubernetes-common@0.9.6 + +## 0.4.13 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-defaults@0.11.1 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-kubernetes-common@0.9.6 + +## 0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/integration@1.17.1-next.1 + - @backstage/integration-aws-node@0.1.17-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-kubernetes-common@0.9.6-next.0 + +## 0.4.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.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/plugin-catalog-common@1.1.4 + - @backstage/plugin-kubernetes-common@0.9.5 + +## 0.4.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @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-catalog-node@1.17.1-next.1 + - @backstage/plugin-kubernetes-common@0.9.5 + +## 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 + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-defaults@0.10.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/integration-aws-node@0.1.16 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.4.11-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + ## 0.4.11-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 157fe665b0..adafaa86f4 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-next.2", + "version": "0.4.14-next.0", "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 9ce4ec3f4e..7fd6a18314 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,108 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.3.8-next.0 + +### Patch Changes + +- b3aa80e: `host` should be optional in `config` schema + +## 0.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + +## 0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## 0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + +## 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 + +- be82d83: visualstudio.com domains are now supported along with dev.azure.com +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.3.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + ## 0.3.5-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/config.d.ts b/plugins/catalog-backend-module-azure/config.d.ts index f6c3bab068..50af529907 100644 --- a/plugins/catalog-backend-module-azure/config.d.ts +++ b/plugins/catalog-backend-module-azure/config.d.ts @@ -28,9 +28,9 @@ export interface Config { azureDevOps?: { [name: string]: { /** - * (Optional) The DevOps host; leave empty for `dev.azure.com`, otherwise set to your self-hosted instance host. + * (Optional) The DevOps host; defaults to `dev.azure.com` if left empty, otherwise set to your self-hosted instance host. */ - host: string; + host?: string; /** * (Required) Your organization slug. */ diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 5fc27e581d..19c33e8334 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-next.2", + "version": "0.3.8-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-azure/src/lib/azure.test.ts b/plugins/catalog-backend-module-azure/src/lib/azure.test.ts index 4839b625d2..f033a85113 100644 --- a/plugins/catalog-backend-module-azure/src/lib/azure.test.ts +++ b/plugins/catalog-backend-module-azure/src/lib/azure.test.ts @@ -392,4 +392,97 @@ describe('azure', () => { ), ).resolves.toHaveLength(totalCount); }); + + it('can search using visualstudio.com domain', async () => { + const response: CodeSearchResponse = { + count: 1, + results: [ + { + fileName: 'catalog-info.yaml', + path: '/catalog-info.yaml', + repository: { + name: 'backstage', + }, + project: { + name: '*', + }, + }, + ], + }; + + server.use( + rest.post( + `https://almsearch.dev.azure.com/shopify/_apis/search/codesearchresults`, + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Basic OkFCQw=='); + expect(req.body).toEqual({ + searchText: 'path:/catalog-info.yaml repo:* proj:engineering', + $orderBy: [ + { + field: 'path', + sortOrder: 'ASC', + }, + ], + $skip: 0, + $top: 1000, + }); + return res(ctx.json(response)); + }, + ), + ); + + const { credentialsProvider, azureConfig } = createFixture( + 'backstage.visualstudio.com', + 'ABC', + ); + + await expect( + codeSearch( + credentialsProvider, + azureConfig, + 'shopify', + 'engineering', + '', + '/catalog-info.yaml', + '', + ), + ).resolves.toEqual(response.results); + }); + + it('identifies both dev.azure.com and visualstudio.com domains as cloud', async () => { + const domains = [ + { host: 'dev.azure.com', expectedCloud: true }, + { host: 'example.visualstudio.com', expectedCloud: true }, + { host: 'on-premise.company.com', expectedCloud: false }, + ]; + + for (const { host, expectedCloud } of domains) { + const mockResponse = { count: 0, results: [] }; + + const expectedBaseUrl = expectedCloud + ? 'https://almsearch.dev.azure.com' + : `https://${host}`; + + server.use( + rest.post( + `${expectedBaseUrl}/test-org/_apis/search/codesearchresults`, + (_req, res, ctx) => { + return res(ctx.json(mockResponse)); + }, + ), + ); + + const { credentialsProvider, azureConfig } = createFixture(host, 'TOKEN'); + + await codeSearch( + credentialsProvider, + azureConfig, + 'test-org', + 'test-project', + '', + '/test-path', + '', + ); + } + }); }); diff --git a/plugins/catalog-backend-module-azure/src/lib/azure.ts b/plugins/catalog-backend-module-azure/src/lib/azure.ts index 0b0d89c15b..7e9bf203ea 100644 --- a/plugins/catalog-backend-module-azure/src/lib/azure.ts +++ b/plugins/catalog-backend-module-azure/src/lib/azure.ts @@ -46,7 +46,18 @@ interface CodeSearchRequest { }; } -const isCloud = (host: string) => host === 'dev.azure.com'; +const isCloud = (host: string) => { + if (host === 'dev.azure.com') { + return true; + } + + if (host.endsWith('.visualstudio.com')) { + return true; + } + + return false; +}; + const PAGE_SIZE = 1000; // codeSearch returns all files that matches the given search path. diff --git a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md index 3bffb1d42e..df0da3a397 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,98 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## 0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-openapi-utils@0.5.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-node@1.17.2 + +## 0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/backend-openapi-utils@0.5.5-next.0 + +## 0.5.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/backend-openapi-utils@0.5.4 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 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 + +- d385854: Do not swallow errors; instead allow them to bubble up to the task scheduler for better tracking and logging. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/backend-openapi-utils@0.5.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.5.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.5.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-node@1.17.0-next.2 + ## 0.5.2-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index de8dc971e5..6a96b778c4 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-next.1", + "version": "0.5.5-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index f562d20651..ef37f7415d 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,164 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.5.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.1 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.5.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + +## 0.5.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.5.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-bitbucket-cloud-common@0.3.1-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.5.0 + +### Minor Changes + +- 8a150bf: **BREAKING**: `BitbucketCloudEntityProvider` now accepts a `CatalogService` instead of a `CatalogApi`. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +## 0.5.0-next.2 + +### Minor Changes + +- 8a150bf: **BREAKING**: `BitbucketCloudEntityProvider` now accepts a `CatalogService` instead of a `CatalogApi`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 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 + +- 3bce578: Support Bitbucket Cloud's `repo:updated` events at `BitbucketCloudEntityProvider`. + + To make use of the new event type, you have to configure your webhook or add a new ones + that delivers this event type to Backstage similar to `repo:push` before. + + Only `repo:updated` events that modify a repository's URL (e.g., due to a name change) + will cause changes (removing the "old", adding the "new" repository). + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.11 + +## 0.4.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.4.8-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/knip-report.md b/plugins/catalog-backend-module-bitbucket-cloud/knip-report.md index cb5315967a..12b22ec44f 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/knip-report.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/knip-report.md @@ -1,5 +1,11 @@ # Knip report +## Unused dependencies (1) + +| Name | Location | Severity | +| :------------------------ | :----------- | :------- | +| @backstage/catalog-client | package.json | error | + ## Unused devDependencies (1) | Name | Location | Severity | diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index 8d73b5fd74..2e18aab8b3 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-next.2", + "version": "0.5.2-next.0", "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 ecd451bd5a..4dff9ab044 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,136 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.5.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.5.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + +## 0.5.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.5.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.5.0 + +### Minor Changes + +- eb8b3aa: Add validateLocationsExist option to avoid generating locations for catalog-info.yaml files that do not exist in the source repository. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +## 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 + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.11 + +## 0.4.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.4.1-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/config.d.ts b/plugins/catalog-backend-module-bitbucket-server/config.d.ts index 0f6889efa7..d57a15a736 100644 --- a/plugins/catalog-backend-module-bitbucket-server/config.d.ts +++ b/plugins/catalog-backend-module-bitbucket-server/config.d.ts @@ -51,6 +51,11 @@ export interface Config { */ skipArchivedRepos?: boolean; }; + /** + * (Optional) Whether to validate locations that exist before emitting them. + * Default: `false`. + */ + validateLocationsExist?: boolean; /** * (Optional) TaskScheduleDefinition for the refresh. */ @@ -83,6 +88,11 @@ export interface Config { */ skipArchivedRepos?: boolean; }; + /** + * (Optional) Whether to validate locations that exist before emitting them. + * Default: `false`. + */ + validateLocationsExist?: boolean; /** * (Optional) TaskScheduleDefinition for the refresh. */ diff --git a/plugins/catalog-backend-module-bitbucket-server/knip-report.md b/plugins/catalog-backend-module-bitbucket-server/knip-report.md index cb5315967a..12b22ec44f 100644 --- a/plugins/catalog-backend-module-bitbucket-server/knip-report.md +++ b/plugins/catalog-backend-module-bitbucket-server/knip-report.md @@ -1,5 +1,11 @@ # Knip report +## Unused dependencies (1) + +| Name | Location | Severity | +| :------------------------ | :----------- | :------- | +| @backstage/catalog-client | package.json | error | + ## Unused devDependencies (1) | Name | Location | Severity | diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index 9dd5e16e55..6fb28d6084 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-next.2", + "version": "0.5.2-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-bitbucket-server/src/lib/BitbucketServerClient.ts b/plugins/catalog-backend-module-bitbucket-server/src/lib/BitbucketServerClient.ts index fa0c455b04..7bf6480f33 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/lib/BitbucketServerClient.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/lib/BitbucketServerClient.ts @@ -70,8 +70,11 @@ export class BitbucketServerClient { repo: string; path: string; }): Promise { + const normalizedPath = options.path.startsWith('/') + ? options.path.substring(1) + : options.path; return fetch( - `${this.config.apiBaseUrl}/projects/${options.projectKey}/repos/${options.repo}/raw/${options.path}`, + `${this.config.apiBaseUrl}/projects/${options.projectKey}/repos/${options.repo}/raw/${normalizedPath}`, getBitbucketServerRequestOptions(this.config), ); } diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts index ba4e3e0411..790bef7939 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts @@ -668,6 +668,97 @@ describe('BitbucketServerEntityProvider', () => { }); }); + it('do not add locations when validateLocationsExist and catalog-info does not exist', async () => { + server.use( + rest.get( + `https://${host}/rest/api/1.0/projects/project-test/repos/repo-test/raw/catalog-info.yaml`, + (_, res, ctx) => { + return res(ctx.status(404)); + }, + ), + rest.get( + `https://${host}/rest/api/1.0/projects/other-project/repos/other-repo/raw/catalog-info.yaml`, + (_, res, ctx) => { + return res(ctx.status(200)); + }, + ), + ); + const config = new ConfigReader({ + integrations: { + bitbucketServer: [ + { + host: host, + }, + ], + }, + catalog: { + providers: { + bitbucketServer: { + mainProvider: { + host: host, + validateLocationsExist: true, + }, + }, + }, + }, + }); + const schedule = new PersistingTaskRunner(); + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + const provider = BitbucketServerEntityProvider.fromConfig(config, { + logger, + schedule, + })[0]; + expect(provider.getProviderName()).toEqual( + 'bitbucketServer-provider:mainProvider', + ); + + setupStubs( + [ + { key: 'project-test', repos: [{ name: 'repo-test' }] }, + { key: 'other-project', repos: [{ name: 'other-repo' }] }, + ], + `https://${host}`, + 'master', + ); + await provider.connect(entityProviderConnection); + + const taskDef = schedule.getTasks()[0]; + expect(taskDef.id).toEqual('bitbucketServer-provider:mainProvider:refresh'); + await (taskDef.fn as () => Promise)(); + + const expectedEntities = [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': `url:https://${host}/projects/other-project/repos/other-repo/browse/catalog-info.yaml`, + 'backstage.io/managed-by-origin-location': `url:https://${host}/projects/other-project/repos/other-repo/browse/catalog-info.yaml`, + 'bitbucket.org/default-branch': 'master', + }, + name: 'generated-d8d4944c30c2906dfee172ddda9537f9893b2c0f', + }, + spec: { + presence: 'optional', + target: `https://${host}/projects/other-project/repos/other-repo/browse/catalog-info.yaml`, + type: 'url', + }, + }, + locationKey: 'bitbucketServer-provider:mainProvider', + }, + ]; + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: expectedEntities, + }); + }); + it('Multiple location entities to deferred entities', async () => { const schedule = new PersistingTaskRunner(); const config = new ConfigReader({ diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts index b462f8756a..4b02632892 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts @@ -253,6 +253,37 @@ export class BitbucketServerEntityProvider implements EntityProvider { if (this.config?.filters?.skipArchivedRepos && repository.archived) { continue; } + if (this.config.validateLocationsExist) { + try { + const response = await client.getFile({ + projectKey: project.key, + repo: repository.slug, + path: this.config.catalogPath, + }); + if (!response.ok) { + if (response.status === 404) { + this.logger.debug( + `Skipping repository ${repository.slug} in project ${project.key} because the catalog file does not exist.`, + ); + } else { + this.logger.warn( + `Unexpected response code ${ + response.status + } while fetching the catalog file from repository ${ + repository.slug + } in project ${ + project.key + }. Response details: ${JSON.stringify(response)}`, + ); + } + continue; + } + } catch (error) { + this.logger.error( + `An error occurred while fetching the catalog file from repository ${repository.slug} in project ${project.key}: ${error}`, + ); + } + } for await (const entity of this.parser({ client, logger: this.logger, diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.test.ts index 755fa4b58a..cd39f0659b 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.test.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.test.ts @@ -49,6 +49,8 @@ describe('readProviderConfigs', () => { repoSlug: undefined, skipArchivedRepos: undefined, }, + schedule: undefined, + validateLocationsExist: false, }); }); @@ -89,6 +91,8 @@ describe('readProviderConfigs', () => { repoSlug: undefined, skipArchivedRepos: undefined, }, + schedule: undefined, + validateLocationsExist: false, }); expect(providerConfigs[1]).toEqual({ id: 'secondaryProvider', @@ -99,6 +103,8 @@ describe('readProviderConfigs', () => { repoSlug: undefined, skipArchivedRepos: undefined, }, + schedule: undefined, + validateLocationsExist: false, }); expect(providerConfigs[2]).toEqual({ id: 'thirdProvider', @@ -109,6 +115,7 @@ describe('readProviderConfigs', () => { repoSlug: undefined, skipArchivedRepos: undefined, }, + validateLocationsExist: false, schedule: { frequency: { minutes: 30 }, timeout: { @@ -147,6 +154,36 @@ describe('readProviderConfigs', () => { repoSlug: /.*/, skipArchivedRepos: true, }, + schedule: undefined, + validateLocationsExist: false, + }); + }); + + it('single simple provider config with validateLocationsExist', () => { + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketServer: { + host: 'bitbucket.mycompany.com', + validateLocationsExist: true, + }, + }, + }, + }); + const providerConfigs = readProviderConfigs(config); + + expect(providerConfigs).toHaveLength(1); + expect(providerConfigs[0]).toEqual({ + id: 'default', + catalogPath: '/catalog-info.yaml', + host: 'bitbucket.mycompany.com', + filters: { + projectKey: undefined, + repoSlug: undefined, + skipArchivedRepos: undefined, + }, + schedule: undefined, + validateLocationsExist: true, }); }); }); diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts index 3b921b6bab..1a224536a7 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts @@ -32,6 +32,7 @@ export type BitbucketServerEntityProviderConfig = { repoSlug?: RegExp; skipArchivedRepos?: boolean; }; + validateLocationsExist: boolean; schedule?: SchedulerServiceTaskScheduleDefinition; }; @@ -66,7 +67,8 @@ function readProviderConfig( const skipArchivedReposFlag = config.getOptionalBoolean( 'filters.skipArchivedRepos', ); - + const validateLocationsExistFlag = + config?.getOptionalBoolean('validateLocationsExist') ?? false; const schedule = config.has('schedule') ? readSchedulerServiceTaskScheduleDefinitionFromConfig( config.getConfig('schedule'), @@ -82,6 +84,7 @@ function readProviderConfig( repoSlug: repoSlugPattern ? new RegExp(repoSlugPattern) : undefined, skipArchivedRepos: skipArchivedReposFlag, }, + validateLocationsExist: validateLocationsExistFlag, schedule, }; } diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md index 3ccb395b27..4942805de8 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,90 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/plugin-kubernetes-common@0.9.6 + +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-kubernetes-common@0.9.6 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-kubernetes-common@0.9.6-next.0 + +## 0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/plugin-kubernetes-common@0.9.5 + +## 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 + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/config@1.3.2 + +## 0.3.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + ## 0.3.8-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index 8b70f03bf0..efdc2911e1 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-next.1", + "version": "0.3.11-next.0", "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 e207e24dcf..1a64c55387 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,107 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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.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 + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.3.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + ## 0.3.2-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 9d176a1a9d..60bdd0ec44 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-next.2", + "version": "0.3.5-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-gitea/.eslintrc.js b/plugins/catalog-backend-module-gitea/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/catalog-backend-module-gitea/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/catalog-backend-module-gitea/CHANGELOG.md b/plugins/catalog-backend-module-gitea/CHANGELOG.md new file mode 100644 index 0000000000..61b1851616 --- /dev/null +++ b/plugins/catalog-backend-module-gitea/CHANGELOG.md @@ -0,0 +1,111 @@ +# @backstage/plugin-catalog-backend-module-gitea + +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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.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 + +- e4dabc6: add new gitea provider module + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.1.0-next.0 + +### Minor Changes + +- e4dabc6: add new gitea provider module + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 diff --git a/plugins/catalog-backend-module-gitea/README.md b/plugins/catalog-backend-module-gitea/README.md new file mode 100644 index 0000000000..6b93c9c90a --- /dev/null +++ b/plugins/catalog-backend-module-gitea/README.md @@ -0,0 +1,8 @@ +# Catalog Backend Module for Gitea + +This is an extension module to the plugin-catalog-backend plugin, providing extensions targeted at Gitea integrations. + +## Getting started + +See [Backstage documentation](https://backstage.io/docs/integrations/gitea/discovery.md) +for details on how to install and configure the plugin. diff --git a/plugins/catalog-backend-module-gitea/catalog-info.yaml b/plugins/catalog-backend-module-gitea/catalog-info.yaml new file mode 100644 index 0000000000..eb580d570b --- /dev/null +++ b/plugins/catalog-backend-module-gitea/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-catalog-backend-module-gitea + title: '@backstage/plugin-catalog-backend-module-gitea' + description: The gitea backend module for the catalog plugin provide by intive.com +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/catalog-backend-module-gitea/config.d.ts b/plugins/catalog-backend-module-gitea/config.d.ts new file mode 100644 index 0000000000..bbb1a0ee4f --- /dev/null +++ b/plugins/catalog-backend-module-gitea/config.d.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; + +export interface Config { + catalog?: { + /** + * List of provider-specific options and attributes + */ + providers?: { + /** + * GiteaEntityProvider configuration + * + * Maps provider id with configuration. + */ + gitea?: { + [name: string]: { + /** + * (Required) The host of the Gitea integration to use. + */ + host: string; + /** + * (Required) Name of your organization account/workspace. + */ + organization: string; + /** + * (Optional) Branch. + * The branch where the provider will try to find entities. Uses the default to "main". + */ + branch?: string; + /** + * (Optional) Path where the catalog YAML manifest file is expected in the repository. + * Defaults to "catalog-info.yaml". + */ + catalogPath?: string; + /** + * (Optional) TaskScheduleDefinition for the discovery. + */ + schedule?: SchedulerServiceTaskScheduleDefinition; + }; + }; + }; + }; +} diff --git a/plugins/catalog-backend-module-gitea/knip-report.md b/plugins/catalog-backend-module-gitea/knip-report.md new file mode 100644 index 0000000000..2661c35327 --- /dev/null +++ b/plugins/catalog-backend-module-gitea/knip-report.md @@ -0,0 +1,2 @@ +# Knip report + diff --git a/plugins/catalog-backend-module-gitea/package.json b/plugins/catalog-backend-module-gitea/package.json new file mode 100644 index 0000000000..9c8a4e7725 --- /dev/null +++ b/plugins/catalog-backend-module-gitea/package.json @@ -0,0 +1,49 @@ +{ + "name": "@backstage/plugin-catalog-backend-module-gitea", + "version": "0.1.3-next.0", + "license": "Apache-2.0", + "description": "The gitea backend module for the catalog plugin.", + "main": "src/index.ts", + "types": "src/index.ts", + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/catalog-backend-module-gitea" + }, + "backstage": { + "role": "backend-plugin-module", + "pluginId": "catalog", + "pluginPackage": "@backstage/plugin-catalog-backend" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/integration": "workspace:^", + "@backstage/plugin-catalog-common": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", + "p-limit": "^3.0.2", + "uuid": "^11.0.0" + }, + "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/catalog-backend-module-gitea/report.api.md b/plugins/catalog-backend-module-gitea/report.api.md new file mode 100644 index 0000000000..1aae80271a --- /dev/null +++ b/plugins/catalog-backend-module-gitea/report.api.md @@ -0,0 +1,36 @@ +## API Report File for "@backstage/plugin-catalog-backend-module-gitea" + +> 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 { Config } from '@backstage/config'; +import { EntityProvider } from '@backstage/plugin-catalog-node'; +import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; +import { LoggerService } from '@backstage/backend-plugin-api'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; + +// @public (undocumented) +const catalogModuleGitea: BackendFeature; +export default catalogModuleGitea; + +// @public (undocumented) +export class GiteaEntityProvider implements EntityProvider { + // (undocumented) + connect(connection: EntityProviderConnection): Promise; + // (undocumented) + static fromConfig( + configRoot: Config, + options: { + logger: LoggerService; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; + }, + ): GiteaEntityProvider[]; + // (undocumented) + getProviderName(): string; + // (undocumented) + refresh(logger: LoggerService): Promise; +} +``` diff --git a/plugins/catalog-backend-module-gitea/src/index.ts b/plugins/catalog-backend-module-gitea/src/index.ts new file mode 100644 index 0000000000..b92ced7fcf --- /dev/null +++ b/plugins/catalog-backend-module-gitea/src/index.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * The gitea backend module for the catalog plugin. + * + * @packageDocumentation + */ + +export { catalogModuleGitea as default } from './module'; +export { GiteaEntityProvider } from './providers/GiteaEntityProvider'; diff --git a/plugins/catalog-backend-module-gitea/src/module.test.ts b/plugins/catalog-backend-module-gitea/src/module.test.ts new file mode 100644 index 0000000000..0ce2d4319a --- /dev/null +++ b/plugins/catalog-backend-module-gitea/src/module.test.ts @@ -0,0 +1,86 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { catalogModuleGitea } from './module'; +import { GiteaEntityProvider } from './providers/GiteaEntityProvider'; + +describe('catalogModuleGitea', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = mockServices.scheduler.mock({ + createScheduledTaskRunner(schedule) { + usedSchedule = schedule; + return { run: runner }; + }, + }); + + const config = { + catalog: { + providers: { + gitea: { + test: { + host: 'g.com', + organization: 'org', + branch: 'main', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + }, + integrations: { + gitea: [ + { + host: 'g.com', + baseUrl: 'https://g.com/gitea', + gitilesBaseUrl: 'https:/g.com/gitiles', + }, + ], + }, + }; + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + features: [ + catalogModuleGitea, + mockServices.rootConfig.factory({ data: config }), + mockServices.logger.factory(), + scheduler.factory, + ], + }); + + expect(usedSchedule?.frequency).toEqual({ months: 1 }); + expect(usedSchedule?.timeout).toEqual({ minutes: 3 }); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'gitea-provider:test', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-gitea/src/module.ts b/plugins/catalog-backend-module-gitea/src/module.ts new file mode 100644 index 0000000000..91e019c6fb --- /dev/null +++ b/plugins/catalog-backend-module-gitea/src/module.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, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; + +import { GiteaEntityProvider } from './providers/GiteaEntityProvider'; +/** + * @public + */ +export const catalogModuleGitea = createBackendModule({ + pluginId: 'catalog', + moduleId: 'gitea', + register(reg) { + reg.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + config: coreServices.rootConfig, + logger: coreServices.logger, + scheduler: coreServices.scheduler, + }, + + async init({ catalog, config, logger, scheduler }) { + const providers = GiteaEntityProvider.fromConfig(config, { + logger, + scheduler, + }); + catalog.addEntityProvider(providers); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-gitea/src/providers/GiteaEntityProvider.test.ts b/plugins/catalog-backend-module-gitea/src/providers/GiteaEntityProvider.test.ts new file mode 100644 index 0000000000..d6a31899f1 --- /dev/null +++ b/plugins/catalog-backend-module-gitea/src/providers/GiteaEntityProvider.test.ts @@ -0,0 +1,472 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { mockServices } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; +import { GiteaEntityProvider } from './GiteaEntityProvider'; +import * as uuid from 'uuid'; +import { readGiteaConfigs } from './config'; +import { getGiteaApiUrl } from './core'; +import { GiteaIntegration } from '@backstage/integration'; + +jest.mock('./config'); +jest.mock('./core'); +jest.mock('uuid'); + +describe('GiteaEntityProvider', () => { + const logger = mockServices.logger.mock(); + + const mockScheduler = { + createScheduledTaskRunner: jest.fn(), + triggerTask: jest.fn(), + scheduleTask: jest.fn(), + getScheduledTasks: jest.fn(), + }; + const mockTaskRunner = { + run: jest.fn(), + }; + + beforeEach(() => { + jest.resetAllMocks(); + (readGiteaConfigs as jest.Mock).mockReturnValue([ + { + id: 'test-provider', + host: 'gitea.example.com', + organization: 'test-org', + catalogPath: 'catalog-info.yaml', + branch: 'main', + schedule: { + frequency: { minutes: 30 }, + timeout: { minutes: 3 }, + }, + }, + ]); + (getGiteaApiUrl as jest.Mock).mockReturnValue( + 'https://gitea.example.com/api/v1/', + ); + mockScheduler.createScheduledTaskRunner.mockReturnValue(mockTaskRunner); + (uuid.v4 as jest.Mock).mockReturnValue('test-uuid'); + + // Mock global fetch + global.fetch = jest.fn(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('fromConfig', () => { + it('should create providers from config', () => { + const config = new ConfigReader({ + integrations: { + gitea: [ + { + host: 'gitea.example.com', + token: 'test-token', + }, + ], + }, + catalog: { + providers: { + gitea: { + 'test-provider': { + host: 'gitea.example.com', + organization: 'test-org', + }, + }, + }, + }, + }); + + const providers = GiteaEntityProvider.fromConfig(config, { + logger, + scheduler: mockScheduler, + }); + + expect(providers).toHaveLength(1); + expect(providers[0].getProviderName()).toBe( + 'gitea-provider:test-provider', + ); + }); + + it('should throw if no schedule or scheduler is provided', () => { + const config = new ConfigReader({ + integrations: { + gitea: [ + { + host: 'gitea.example.com', + token: 'test-token', + }, + ], + }, + catalog: { + providers: { + gitea: { + 'test-provider': { + host: 'gitea.example.com', + organization: 'test-org', + }, + }, + }, + }, + }); + + expect(() => + GiteaEntityProvider.fromConfig(config, { + logger, + } as any), + ).toThrow('Either schedule or scheduler must be provided.'); + }); + + it('should throw if no matching integration is found', () => { + const config = new ConfigReader({ + integrations: { + gitea: [ + { + host: 'wrong-host.example.com', + token: 'test-token', + }, + ], + }, + catalog: { + providers: { + gitea: { + 'test-provider': { + host: 'gitea.example.com', + organization: 'test-org', + }, + }, + }, + }, + }); + + expect(() => + GiteaEntityProvider.fromConfig(config, { + logger, + scheduler: mockScheduler, + }), + ).toThrow( + 'No Gitea integration found that matches host gitea.example.com', + ); + }); + + it('should throw if no schedule is provided in code or config', () => { + const config = new ConfigReader({ + integrations: { + gitea: [ + { + host: 'gitea.example.com', + token: 'test-token', + }, + ], + }, + catalog: { + providers: { + gitea: { + 'test-provider': { + host: 'gitea.example.com', + organization: 'test-org', + schedule: undefined, + }, + }, + }, + }, + }); + + (readGiteaConfigs as jest.Mock).mockReturnValue([ + { + id: 'test-provider', + host: 'gitea.example.com', + organization: 'test-org', + catalogPath: 'catalog-info.yaml', + branch: 'main', + schedule: undefined, + }, + ]); + + expect(() => + GiteaEntityProvider.fromConfig(config, { + logger, + scheduler: mockScheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for Gitea-provider:test-provider.', + ); + }); + }); + + describe('getProviderName', () => { + it('should return the correct provider name', () => { + const config = new ConfigReader({ + integrations: { + gitea: [ + { + host: 'gitea.example.com', + token: 'test-token', + }, + ], + }, + catalog: { + providers: { + gitea: { + 'test-provider': { + host: 'gitea.example.com', + organization: 'test-org', + }, + }, + }, + }, + }); + + const providers = GiteaEntityProvider.fromConfig(config, { + logger, + scheduler: mockScheduler, + }); + + expect(providers[0].getProviderName()).toBe( + 'gitea-provider:test-provider', + ); + }); + }); + + describe('connect', () => { + let provider: GiteaEntityProvider; + const mockConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + + beforeEach(() => { + const config = new ConfigReader({ + integrations: { + gitea: [ + { + host: 'gitea.example.com', + token: 'test-token', + }, + ], + }, + catalog: { + providers: { + gitea: { + 'test-provider': { + host: 'gitea.example.com', + organization: 'test-org', + }, + }, + }, + }, + }); + + provider = GiteaEntityProvider.fromConfig(config, { + logger, + scheduler: mockScheduler, + })[0]; + }); + + it('should connect and schedule a refresh', async () => { + await provider.connect(mockConnection); + + expect(mockTaskRunner.run).toHaveBeenCalledWith({ + id: 'gitea-provider:test-provider:refresh', + fn: expect.any(Function), + }); + }); + }); + + describe('refresh', () => { + let provider: GiteaEntityProvider; + let integration: GiteaIntegration; + const mockConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + + beforeEach(() => { + // Need to access the provider internals for refresh tests + integration = { + config: { + host: 'gitea.example.com', + token: 'test-token', + }, + } as unknown as GiteaIntegration; + + // Direct instantiation for testing the refresh method + provider = new (GiteaEntityProvider as any)( + { + id: 'test-provider', + host: 'gitea.example.com', + organization: 'test-org', + catalogPath: 'catalog-info.yaml', + branch: 'main', + }, + integration, + logger, + mockTaskRunner, + ); + }); + + it('should throw if not connected', async () => { + await expect(provider.refresh(logger)).rejects.toThrow( + 'Gitea discovery connection not initialized', + ); + }); + + it('should handle API errors gracefully', async () => { + await provider.connect(mockConnection); + + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: false, + status: 500, + }); + + await expect(provider.refresh(logger)).rejects.toThrow( + 'Failed to list Gitea projects for organization test-org', + ); + }); + + it('should handle empty results', async () => { + await provider.connect(mockConnection); + + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => [], + }); + + await provider.refresh(logger); + + expect(mockConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: [], + }); + }); + + it('should handle pagination and filter repos with catalog files', async () => { + await provider.connect(mockConnection); + + // Page 1 response + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => [ + { + name: 'repo1', + html_url: 'https://gitea.example.com/test-org/repo1', + empty: false, + }, + { + name: 'repo2', + html_url: 'https://gitea.example.com/test-org/repo2', + empty: false, + }, + { + name: 'empty-repo', + html_url: 'https://gitea.example.com/test-org/empty-repo', + empty: true, + }, + ], + }); + + // Page 2 response + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => [ + { + name: 'repo3', + html_url: 'https://gitea.example.com/test-org/repo3', + empty: false, + }, + ], + }); + + // Empty page 3 to terminate pagination + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => [], + }); + + // Catalog check for repo1 - has catalog file + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + }); + + // Catalog check for repo2 - no catalog file + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: false, + }); + + // Catalog check for repo3 - has catalog file + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + }); + + await provider.refresh(logger); + + expect(mockConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: [ + expect.objectContaining({ + locationKey: 'gitea-provider:test-provider', + entity: expect.anything(), + }), + expect.objectContaining({ + locationKey: 'gitea-provider:test-provider', + entity: expect.anything(), + }), + ], + }); + + // Verify we made requests for all repos + expect(global.fetch).toHaveBeenCalledTimes(6); + }); + + it('should handle errors when applying mutations', async () => { + await provider.connect(mockConnection); + + // Mock repository data + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => [ + { + name: 'repo1', + html_url: 'https://gitea.example.com/test-org/repo1', + empty: false, + }, + ], + }); + + // Empty page 2 + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => [], + }); + + // Catalog check - has catalog file + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + }); + + // Mock mutation failure + (mockConnection.applyMutation as jest.Mock).mockRejectedValueOnce( + new Error('Mutation failed'), + ); + + // Should not throw but log the error + await provider.refresh(logger); + + expect(mockConnection.applyMutation).toHaveBeenCalled(); + }); + }); +}); diff --git a/plugins/catalog-backend-module-gitea/src/providers/GiteaEntityProvider.ts b/plugins/catalog-backend-module-gitea/src/providers/GiteaEntityProvider.ts new file mode 100644 index 0000000000..2f9475af26 --- /dev/null +++ b/plugins/catalog-backend-module-gitea/src/providers/GiteaEntityProvider.ts @@ -0,0 +1,266 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as uuid from 'uuid'; +import { Config } from '@backstage/config'; +import { InputError } from '@backstage/errors'; +import { + EntityProvider, + EntityProviderConnection, + locationSpecToLocationEntity, +} from '@backstage/plugin-catalog-node'; +import { LocationSpec } from '@backstage/plugin-catalog-common'; +import { + GiteaIntegration, + getGiteaRequestOptions, + ScmIntegrations, +} from '@backstage/integration'; +import { getGiteaApiUrl } from './core'; + +import { readGiteaConfigs } from './config'; +import { GiteaProjectQueryResult, GiteaProviderConfig } from './types'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; + +/** @public */ +export class GiteaEntityProvider implements EntityProvider { + private readonly config: GiteaProviderConfig; + private readonly integration: GiteaIntegration; + private readonly logger: LoggerService; + private readonly scheduleFn: () => Promise; + private connection?: EntityProviderConnection; + + static fromConfig( + configRoot: Config, + options: { + logger: LoggerService; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; + }, + ): GiteaEntityProvider[] { + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + + const providerConfigs = readGiteaConfigs(configRoot); + const integrations = ScmIntegrations.fromConfig(configRoot).gitea; + const providers: GiteaEntityProvider[] = []; + + providerConfigs.forEach(providerConfig => { + const integration = integrations.byHost(providerConfig.host); + if (!integration) { + throw new InputError( + `No Gitea integration found that matches host ${providerConfig.host}`, + ); + } + + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for Gitea-provider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + + providers.push( + new GiteaEntityProvider( + providerConfig, + integration, + options.logger, + taskRunner, + ), + ); + }); + + return providers; + } + + private constructor( + config: GiteaProviderConfig, + integration: GiteaIntegration, + logger: LoggerService, + taskRunner: SchedulerServiceTaskRunner, + ) { + this.config = config; + this.integration = integration; + this.logger = logger.child({ + target: this.getProviderName(), + }); + this.scheduleFn = this.createScheduleFn(taskRunner); + } + + getProviderName(): string { + return `gitea-provider:${this.config.id}`; + } + + async connect(connection: EntityProviderConnection): Promise { + this.connection = connection; + await this.scheduleFn(); + } + + private async parseGiteaJsonResponse( + response: Response, + ): Promise { + if (!response.ok) { + throw new Error( + `Failed to fetch Gitea repositories, status: ${response.status}`, + ); + } + try { + return await response.json(); + } catch (error) { + throw new Error(`Failed to parse Gitea API response: ${error}`); + } + } + + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { + return async () => { + const taskId = `${this.getProviderName()}:refresh`; + return taskRunner.run({ + id: taskId, + fn: async () => { + const logger = this.logger.child({ + class: GiteaEntityProvider.prototype.constructor.name, + taskId, + taskInstanceId: uuid.v4(), + }); + + try { + await this.refresh(logger); + } catch (error) { + logger.error( + `${this.getProviderName()} refresh failed, ${error}`, + error as Error, + ); + } + }, + }); + }; + } + + async refresh(logger: LoggerService): Promise { + if (!this.connection) { + throw new Error('Gitea discovery connection not initialized'); + } + let allRepos: any[] = []; + + try { + // Fetch all repos for an organization + const OrgRepoApiUrl = `${getGiteaApiUrl(this.integration.config)}orgs/${ + this.config.organization + }/repos`; + let page = 1; + let hasMoreData = true; + + while (hasMoreData) { + const url = `${OrgRepoApiUrl}?page=${page}`; + const response = await fetch(url, { + method: 'GET', + ...getGiteaRequestOptions(this.integration.config), + }); + + if (!response.ok) { + throw new Error( + `Failed to fetch page ${page} of repos, status: ${response.status}`, + ); + } + + const projectsPage = (await this.parseGiteaJsonResponse( + response, + )) as GiteaProjectQueryResult; + + if (!Array.isArray(projectsPage) || projectsPage.length === 0) { + hasMoreData = false; + } else { + allRepos = allRepos.concat(projectsPage); + page++; + } + } + } catch (e) { + throw new Error( + `Failed to list Gitea projects for organization ${this.config.organization}, ${e}`, + ); + } + + // Filter for repos that have catalog-info.yaml at root + const { default: pLimit } = await import('p-limit'); + const limit = pLimit(5); + const validRepos: string[] = []; + + await Promise.all( + allRepos.map(repo => + limit(async () => { + if (repo.empty) { + logger.warn(`Repo ${repo.html_url} is empty, skipping`); + return; + } + const contentsApiUrl = `${getGiteaApiUrl( + this.integration.config, + )}repos/${this.config.organization}/${repo.name}/contents/${ + this.config.catalogPath + }`; + + const res = await fetch(contentsApiUrl, { + method: 'GET', + ...getGiteaRequestOptions(this.integration.config), + }); + if (res.ok) { + validRepos.push(repo.html_url); + } else { + logger.warn( + `Repo ${repo.html_url} does not contain a catalog-info.yaml file`, + ); + return; + } + }), + ), + ); + // Create location specs for each valid repo + const locations = await Promise.all( + validRepos.map(repo => limit(() => this.createLocationSpec(repo))), + ); + // Apply the locations to the catalog + try { + const providerName: string = this.getProviderName(); + const entities = locations.map(location => ({ + locationKey: providerName, + entity: locationSpecToLocationEntity({ location }), + })); + await this.connection.applyMutation({ + type: 'full', + entities: entities, + }); + } catch (e) { + logger.error(`Failed to apply mutation: ${e}`); + } + logger.info( + `Found ${locations.length} locations from ${allRepos.length} repos`, + ); + } + + private async createLocationSpec(repo: string): Promise { + return { + type: 'url', + target: `${repo}/src/branch/${this.config.branch}/${this.config.catalogPath}`, + }; + } +} diff --git a/plugins/catalog-backend-module-gitea/src/providers/config.test.ts b/plugins/catalog-backend-module-gitea/src/providers/config.test.ts new file mode 100644 index 0000000000..5efece4e5a --- /dev/null +++ b/plugins/catalog-backend-module-gitea/src/providers/config.test.ts @@ -0,0 +1,125 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { readGiteaConfigs } from './config'; +import { ConfigReader } from '@backstage/config'; +import { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api'; + +jest.mock('@backstage/backend-plugin-api', () => ({ + ...jest.requireActual('@backstage/backend-plugin-api'), + readSchedulerServiceTaskScheduleDefinitionFromConfig: jest.fn(), +})); +jest.mock('p-limit'); + +describe('readGiteaConfigs', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('returns empty array when no gitea provider config is present', () => { + const config = new ConfigReader({}); + + const result = readGiteaConfigs(config); + + expect(result).toEqual([]); + }); + + it('parses multiple gitea provider configurations correctly', () => { + ( + readSchedulerServiceTaskScheduleDefinitionFromConfig as jest.Mock + ).mockReturnValue({ + frequency: { minutes: 5 }, + timeout: { minutes: 3 }, + }); + + const config = new ConfigReader({ + catalog: { + providers: { + gitea: { + myProvider: { + host: 'gitea.example.com', + organization: 'example-org', + branch: 'dev', + catalogPath: 'custom-path.yaml', + schedule: { + frequency: { minutes: 5 }, + timeout: { minutes: 3 }, + }, + }, + }, + }, + }, + }); + + const result = readGiteaConfigs(config); + + expect(result).toEqual([ + { + id: 'myProvider', + host: 'gitea.example.com', + organization: 'example-org', + branch: 'dev', + catalogPath: 'custom-path.yaml', + schedule: { + frequency: { minutes: 5 }, + timeout: { minutes: 3 }, + }, + }, + ]); + }); + + it('applies defaults for optional fields', () => { + const config = new ConfigReader({ + catalog: { + providers: { + gitea: { + default: { + host: 'gitea.com', + organization: 'default-org', + }, + }, + }, + }, + }); + + const result = readGiteaConfigs(config); + + expect(result[0]).toMatchObject({ + id: 'default', + host: 'gitea.com', + organization: 'default-org', + branch: 'main', + catalogPath: 'catalog-info.yaml', + schedule: undefined, + }); + }); + + it('throws if required fields are missing', () => { + const config = new ConfigReader({ + catalog: { + providers: { + gitea: { + bad: {}, + }, + }, + }, + }); + + expect(() => readGiteaConfigs(config)).toThrow( + /Missing required config value/, + ); + }); +}); diff --git a/plugins/catalog-backend-module-gitea/src/providers/config.ts b/plugins/catalog-backend-module-gitea/src/providers/config.ts new file mode 100644 index 0000000000..5863e76bf0 --- /dev/null +++ b/plugins/catalog-backend-module-gitea/src/providers/config.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { GiteaProviderConfig } from './types'; + +function readGiteaConfig(id: string, config: Config): GiteaProviderConfig { + const branch = config.getOptionalString('branch') ?? 'main'; + const catalogPath = + config.getOptionalString('catalogPath') ?? 'catalog-info.yaml'; + const host = config.getString('host'); + const organization = config.getString('organization'); + + const schedule = config.has('schedule') + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) + : undefined; + + return { + branch, + catalogPath, + host, + id, + schedule, + organization, + }; +} + +export function readGiteaConfigs(config: Config): GiteaProviderConfig[] { + const configs: GiteaProviderConfig[] = []; + + const providerConfigs = config.getOptionalConfig('catalog.providers.gitea'); + + if (!providerConfigs) { + return configs; + } + + for (const id of providerConfigs.keys()) { + configs.push(readGiteaConfig(id, providerConfigs.getConfig(id))); + } + + return configs; +} diff --git a/plugins/catalog-backend-module-gitea/src/providers/core.test.ts b/plugins/catalog-backend-module-gitea/src/providers/core.test.ts new file mode 100644 index 0000000000..883dab7edc --- /dev/null +++ b/plugins/catalog-backend-module-gitea/src/providers/core.test.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getGiteaApiUrl } from './core'; +import { GiteaIntegrationConfig } from '@backstage/integration'; + +describe('getGiteaApiUrl', () => { + it('should return the correct Gitea API base URL', () => { + const config: GiteaIntegrationConfig = { + host: 'gitea.example.com', + baseUrl: 'https://gitea.example.com', + }; + + const result = getGiteaApiUrl(config); + + expect(result).toBe('https://gitea.example.com/api/v1/'); + }); + + it('should handle trailing slash in baseUrl correctly', () => { + const config: GiteaIntegrationConfig = { + host: 'gitea.example.com', + baseUrl: 'https://gitea.example.com/', + }; + + const result = getGiteaApiUrl(config); + + expect(result).toBe('https://gitea.example.com//api/v1/'); + }); +}); diff --git a/plugins/scaffolder-backend/src/lib/templating/index.ts b/plugins/catalog-backend-module-gitea/src/providers/core.ts similarity index 66% rename from plugins/scaffolder-backend/src/lib/templating/index.ts rename to plugins/catalog-backend-module-gitea/src/providers/core.ts index 1f9e5eb855..c994f3a714 100644 --- a/plugins/scaffolder-backend/src/lib/templating/index.ts +++ b/plugins/catalog-backend-module-gitea/src/providers/core.ts @@ -14,4 +14,13 @@ * limitations under the License. */ -export type { TemplateFilter, TemplateGlobal } from './SecureTemplater'; +import { GiteaIntegrationConfig } from '@backstage/integration'; +/** + * Return the url to query available repos using the Gitea API. + * + * @param config - A Gitea provider config. + * @public + */ +export function getGiteaApiUrl(config: GiteaIntegrationConfig) { + return `${config.baseUrl}/api/v1/`; +} diff --git a/plugins/scaffolder-backend/src/lib/index.ts b/plugins/catalog-backend-module-gitea/src/providers/index.ts similarity index 93% rename from plugins/scaffolder-backend/src/lib/index.ts rename to plugins/catalog-backend-module-gitea/src/providers/index.ts index 20096f9c03..9914fda397 100644 --- a/plugins/scaffolder-backend/src/lib/index.ts +++ b/plugins/catalog-backend-module-gitea/src/providers/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export * from './templating'; +export { getGiteaApiUrl } from './core'; diff --git a/plugins/catalog-backend-module-gitea/src/providers/types.ts b/plugins/catalog-backend-module-gitea/src/providers/types.ts new file mode 100644 index 0000000000..0bbd09f9e7 --- /dev/null +++ b/plugins/catalog-backend-module-gitea/src/providers/types.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; + +export type GiteaProjectInfo = { + id: string; + name: string; + parent?: string; + state?: string; + branch?: string; +}; + +export type GiteaProjectQueryResult = Record; + +export type GiteaProviderConfig = { + host: string; + id: string; + branch?: string; + catalogPath?: string; + organization?: string; + schedule?: SchedulerServiceTaskScheduleDefinition; +}; diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index 14fb414c48..d55b1b10ff 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,115 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-catalog-backend-module-github@0.10.2-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + +## 0.3.12 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-backend-module-github@0.10.1 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + +## 0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-backend-module-github@0.10.1-next.1 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.10.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/config@1.3.2 + +## 0.3.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-catalog-backend-module-github@0.10.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/plugin-events-node@0.4.12 + +## 0.3.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend-module-github@0.9.1-next.2 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 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 + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/plugin-catalog-backend-module-github@0.9.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-events-node@0.4.11 + +## 0.3.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.9.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.3.10-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index ce47411b0a..7daefca9ce 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-next.2", + "version": "0.3.13-next.0", "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 51e1498de5..e62f9de17b 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,169 @@ # @backstage/plugin-catalog-backend-module-github +## 0.10.2-next.1 + +### Patch Changes + +- f6c64d1: Fix GitHub catalog entity provider branch matching on event processing. +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.1-next.1 + +## 0.10.2-next.0 + +### Patch Changes + +- d421b16: This change introduces single user versions of the user group resolution code in the multi org provider that will not page through all membership when updating a single user. +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-catalog-backend@3.0.1-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.10.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0 + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + +## 0.10.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-backend@3.0.0-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.10.0 + +### Minor Changes + +- 7c0dfb0: GitHub organization now matches in a case-insensitive manner when processing events. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-catalog-backend@2.1.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +## 0.9.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.1-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 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 + +- ff335e5: **BREAKING** The `GithubLocationAnalyzer` now requires the `AuthService` and the `CatalogService` when being constructed and the `TokenManger` has been removed. + +### Patch Changes + +- ee9f59f: Added filter to include archived repositories +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-backend@2.0.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.11 + +## 0.9.0-next.3 + +### Patch Changes + +- ee9f59f: Added filter to include archived repositories +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.0-next.3 + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.9.0-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/config.d.ts b/plugins/catalog-backend-module-github/config.d.ts index e595903068..01d194013c 100644 --- a/plugins/catalog-backend-module-github/config.d.ts +++ b/plugins/catalog-backend-module-github/config.d.ts @@ -121,6 +121,11 @@ export interface Config { * (Optional) GitHub repository visibility filter. */ visibility?: Array<'private' | 'internal' | 'public'>; + /** + * (Optional) Whether to include archived repositories. + * Default: `false`. + */ + allowArchived?: boolean; }; /** * (Optional) TaskScheduleDefinition for the refresh. @@ -194,6 +199,11 @@ export interface Config { * (Optional) GitHub repository visibility filter. */ visibility?: Array<'private' | 'internal' | 'public'>; + /** + * (Optional) Whether to include archived repositories. + * Default: `false`. + */ + allowArchived?: boolean; }; /** * (Optional) TaskScheduleDefinition for the refresh. diff --git a/plugins/catalog-backend-module-github/knip-report.md b/plugins/catalog-backend-module-github/knip-report.md index 1768d98608..c200862961 100644 --- a/plugins/catalog-backend-module-github/knip-report.md +++ b/plugins/catalog-backend-module-github/knip-report.md @@ -1,10 +1,11 @@ # Knip report -## Unused dependencies (1) +## Unused dependencies (2) | Name | Location | Severity | | :-------------------------------- | :----------- | :------- | | @backstage/plugin-catalog-backend | package.json | error | +| @backstage/catalog-client | package.json | error | ## Unused devDependencies (1) diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index bb2e436804..f56ebd618e 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-next.2", + "version": "0.10.2-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-github/src/lib/github.test.ts b/plugins/catalog-backend-module-github/src/lib/github.test.ts index ef52007f50..09eeba3cc0 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.test.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.test.ts @@ -36,6 +36,7 @@ import { createRemoveEntitiesOperation, createReplaceEntitiesOperation, createGraphqlClient, + getOrganizationTeamsForUser, } from './github'; import { Octokit } from '@octokit/core'; import { throttling } from '@octokit/plugin-throttling'; @@ -53,6 +54,79 @@ describe('github', () => { const graphql = graphqlOctokit.defaults({}); + describe('getOrganizationTeamsForUser', () => { + const org = 'my-org'; + const userLogin = 'testuser'; + + it('returns teams for a user', async () => { + server.use( + graphqlMsw.query('teams', () => + HttpResponse.json({ + data: { + organization: { + teams: { + pageInfo: { hasNextPage: false, endCursor: null }, + nodes: [ + { + slug: 'team1', + combinedSlug: 'my-org/team1', + name: 'Team 1', + description: 'desc', + avatarUrl: '', + editTeamUrl: '', + parentTeam: null, + }, + ], + }, + }, + }, + }), + ), + ); + + const mockTransformer = jest.fn().mockImplementation(async team => ({ + kind: 'Group', + metadata: { name: team.slug }, + })); + + const { teams } = await getOrganizationTeamsForUser( + graphql as any, + org, + userLogin, + mockTransformer as any, + ); + expect(Array.isArray(teams)).toBe(true); + expect(teams[0]).toEqual({ kind: 'Group', metadata: { name: 'team1' } }); + expect(mockTransformer).toHaveBeenCalled(); + }); + + it('returns an empty array if no teams found', async () => { + server.use( + graphqlMsw.query('teams', () => + HttpResponse.json({ + data: { + organization: { + teams: { + pageInfo: { hasNextPage: false, endCursor: null }, + nodes: [], + }, + }, + }, + }), + ), + ); + const mockTransformer = jest.fn().mockResolvedValue(undefined); + const { teams } = await getOrganizationTeamsForUser( + graphql as any, + org, + userLogin, + mockTransformer as any, + ); + expect(Array.isArray(teams)).toBe(true); + expect(teams.length).toBe(0); + }); + }); + describe('getOrganizationUsers using defaultUserMapper', () => { it('reads members', async () => { const input: QueryResponse = { diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index 6d29782bad..5d51dd35cd 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -346,6 +346,59 @@ export async function getOrganizationTeamsFromUsers( return { teams }; } +export async function getOrganizationTeamsForUser( + client: typeof graphql, + org: string, + userLogin: string, + teamTransformer: TeamTransformer, +): Promise<{ teams: Entity[] }> { + const query = ` + query teams($org: String!, $cursor: String, $userLogins: [String!] = "") { + organization(login: $org) { + teams(first: 100, after: $cursor, userLogins: $userLogins) { + pageInfo { + hasNextPage + endCursor + } + nodes { + slug + combinedSlug + name + description + avatarUrl + editTeamUrl + parentTeam { + slug + } + } + } + } +}`; + + const materialisedTeams = async ( + item: GithubTeamResponse, + ctx: TransformerContext, + ): Promise => { + const team: GithubTeam = { + ...item, + members: [{ login: userLogin }], + }; + + return await teamTransformer(team, ctx); + }; + + const teams = await queryWithPaging( + client, + query, + org, + r => r.organization?.teams, + materialisedTeams, + { org, userLogins: [userLogin] }, + ); + + return { teams }; +} + export async function getOrganizationsFromUser( client: typeof graphql, user: string, diff --git a/plugins/catalog-backend-module-github/src/lib/index.ts b/plugins/catalog-backend-module-github/src/lib/index.ts index 14f78fdeee..f5da0f1c2b 100644 --- a/plugins/catalog-backend-module-github/src/lib/index.ts +++ b/plugins/catalog-backend-module-github/src/lib/index.ts @@ -30,5 +30,9 @@ export { defaultOrganizationTeamTransformer, type TransformerContext, } from './defaultTransformers'; -export { assignGroupsToUsers, buildOrgHierarchy } from './org'; +export { + assignGroupsToUser, + assignGroupsToUsers, + buildOrgHierarchy, +} from './org'; export { parseGithubOrgUrl } from './util'; diff --git a/plugins/catalog-backend-module-github/src/lib/org.test.ts b/plugins/catalog-backend-module-github/src/lib/org.test.ts index 1cdc55a4da..a2e379adc7 100644 --- a/plugins/catalog-backend-module-github/src/lib/org.test.ts +++ b/plugins/catalog-backend-module-github/src/lib/org.test.ts @@ -15,7 +15,12 @@ */ import { GroupEntity, UserEntity } from '@backstage/catalog-model'; -import { assignGroupsToUsers, buildMemberOf, buildOrgHierarchy } from './org'; +import { + assignGroupsToUsers, + assignGroupsToUser, + buildMemberOf, + buildOrgHierarchy, +} from './org'; function u(name: string, memberOf: string[] = []): UserEntity { return { @@ -115,6 +120,42 @@ describe('assignGroupsToUsers', () => { }); }); +describe('assignGroupsToUser', () => { + it('should assign the correct groups to a user', () => { + const user: UserEntity = u('u1'); + const groups: GroupEntity[] = [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { name: 'g1' }, + spec: { type: 'team', children: [], members: ['default/u1'] }, + }, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { name: 'g2' }, + spec: { type: 'team', children: [], members: ['u2'] }, + }, + ]; + assignGroupsToUser(user, groups); + expect(user.spec.memberOf).toEqual(['g1']); + }); + + it('should not assign any groups if user is not a member', () => { + const user: UserEntity = u('u3'); + const groups: GroupEntity[] = [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { name: 'g1' }, + spec: { type: 'team', children: [], members: ['u1'] }, + }, + ]; + assignGroupsToUser(user, groups); + expect(user.spec.memberOf).toEqual([]); + }); +}); + describe('buildMemberOf', () => { it('fills indirect member of groups', () => { const a = g('a', undefined, []); diff --git a/plugins/catalog-backend-module-github/src/lib/org.ts b/plugins/catalog-backend-module-github/src/lib/org.ts index 4336f2d49c..3a0a30e340 100644 --- a/plugins/catalog-backend-module-github/src/lib/org.ts +++ b/plugins/catalog-backend-module-github/src/lib/org.ts @@ -91,6 +91,29 @@ export function assignGroupsToUsers( } } +// Assign all relevant groups to a single user if the user is a member of each group. +export function assignGroupsToUser(user: UserEntity, groups: GroupEntity[]) { + const userRef = stringifyEntityRef(user); + for (const group of groups) { + const groupKey = + group.metadata.namespace && group.metadata.namespace !== DEFAULT_NAMESPACE + ? `${group.metadata.namespace}/${group.metadata.name}` + : group.metadata.name; + const memberRefs = + group.spec.members?.map(m => + stringifyEntityRef(parseEntityRef(m, { defaultKind: 'user' })), + ) || []; + if (memberRefs.includes(userRef)) { + if (!user.spec.memberOf) { + user.spec.memberOf = []; + } + if (!user.spec.memberOf.includes(groupKey)) { + user.spec.memberOf.push(groupKey); + } + } + } +} + // Ensure that users have their transitive group memberships. Requires that // the groups were previously processed with buildOrgHierarchy() export function buildMemberOf(groups: GroupEntity[], users: UserEntity[]) { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts index b7df19c296..b9f69b5754 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts @@ -940,6 +940,54 @@ describe('GithubEntityProvider', () => { expect(entityProviderConnection.refresh).toHaveBeenCalledTimes(0); expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); }); + + it('should process when orgs case-insensitive match', async () => { + const config = createSingleProviderConfig({ + providerConfig: { + organization: 'test-org', + }, + }); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createPushEvent({ org: 'Test-Org' }); + + await provider.onEvent(event); + + expect(entityProviderConnection.refresh).toHaveBeenCalledTimes(1); + }); + + it('should fail with incorrect branch matching', async () => { + const config = createSingleProviderConfig({ + providerConfig: { + catalogPath: '**/catalog-info.yaml', + }, + }); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createPushEvent({ + catalogFile: { + action: 'added', + path: 'folder1/folder2/folder3/catalog-info.yaml', + }, + ref: 'refs/heads/my-main-branch', + }); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); + }); }); describe('on repository event', () => { @@ -1189,6 +1237,36 @@ describe('GithubEntityProvider', () => { 0, ); }); + + it('applies update when orgs case-insensitive match', async () => { + const config = createSingleProviderConfig({ + providerConfig: { + organization: 'test-org', + filters: { + topic: { + include: ['backstage-include'], + }, + }, + }, + }); + + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('edited'); + event.eventPayload.organization!.login = 'Test-Org'; + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 1, + ); + }); }); describe('on repository privatized event', () => { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index 8087149e28..440ccaadf5 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -281,15 +281,16 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { } private matchesFilters(repositories: Repository[]): Repository[] { - const repositoryFilter = this.config.filters?.repository; - const topicFilters = this.config.filters?.topic; - const allowForks = this.config.filters?.allowForks ?? true; - const visibilities = this.config.filters?.visibility ?? []; + const repositoryFilter = this.config.filters.repository; + const topicFilters = this.config.filters.topic; + const allowForks = this.config.filters.allowForks; + const visibilities = this.config.filters.visibility ?? []; + const allowArchived = this.config.filters.allowArchived; return repositories.filter(r => { const repoTopics: string[] = r.repositoryTopics; return ( - !r.isArchived && + (allowArchived || !r.isArchived) && (!repositoryFilter || repositoryFilter.test(r.name)) && satisfiesTopicFilter(repoTopics, topicFilters) && satisfiesForkFilter(allowForks, r.isFork) && @@ -301,7 +302,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { private createLocationUrl(repository: Repository): string { const branch = - this.config.filters?.branch || repository.defaultBranchRef || '-'; + this.config.filters.branch || repository.defaultBranchRef || '-'; const catalogFile = this.config.catalogPath.startsWith('/') ? this.config.catalogPath.substring(1) : this.config.catalogPath; @@ -347,12 +348,11 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { } private async onPush(event: PushEvent) { - const organizations = await this.getOrganizations(); + const organizations = await this.getOrganizations(); // TODO: make sure they are lowercase + const eventOrganization = + event.organization?.login.toLocaleLowerCase('en-US'); - if ( - !event.organization?.login || - !organizations.includes(event.organization.login) - ) { + if (!eventOrganization || !organizations.includes(eventOrganization)) { this.logger.debug( `skipping push event from organization ${event.organization?.login}`, ); @@ -364,9 +364,9 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { this.logger.debug(`handle github:push event for ${repoName} - ${repoUrl}`); const branch = - this.config.filters?.branch || event.repository.default_branch; + this.config.filters.branch || event.repository.default_branch; - if (!event.ref.includes(branch)) { + if (event.ref !== `refs/heads/${branch}`) { this.logger.debug(`skipping push event from ref ${event.ref}`); return; } @@ -435,12 +435,11 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { } private async onRepoChange(event: RepositoryEvent) { - const organizations = await this.getOrganizations(); + const organizations = await this.getOrganizations(); // TODO: make sure they are lowercase + const eventOrganization = + event.organization?.login.toLocaleLowerCase('en-US'); - if ( - !event.organization?.login || - !organizations.includes(event.organization.login) - ) { + if (!eventOrganization || !organizations.includes(eventOrganization)) { this.logger.debug( `skipping repository event from organization ${event.organization?.login}`, ); @@ -503,6 +502,8 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { * @param event - The repository archived event. */ private async onRepoArchived(event: RepositoryArchivedEvent) { + if (this.config.filters.allowArchived) return; + const repository = this.createRepoFromEvent(event); await this.removeEntitiesForRepo(repository); this.logger.debug( @@ -584,7 +585,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { * * Creates new entities for the repository if it matches the filters. * - * @param event - The repository unarchived event. + * @param event - The repository transferred event. */ private async onRepoTransferred(event: RepositoryTransferredEvent) { const repository = this.createRepoFromEvent(event); diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.test.ts index bddb4d27cd..28a3f5e7dd 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.test.ts @@ -92,6 +92,12 @@ describe('readProviderConfigs', () => { visibility: ['public', 'internal'], }, }, + providerWithArchiveFilter: { + organization: 'test-org6', + filters: { + allowArchived: true, + }, + }, providerWithHost: { organization: 'test-org1', host: 'ghe.internal.com', @@ -111,7 +117,7 @@ describe('readProviderConfigs', () => { }); const providerConfigs = readProviderConfigs(config); - expect(providerConfigs).toHaveLength(9); + expect(providerConfigs).toHaveLength(10); expect(providerConfigs[0]).toEqual({ id: 'providerOrganizationOnly', organization: 'test-org1', @@ -126,6 +132,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, visibility: undefined, + allowArchived: false, }, schedule: DEFAULT_GITHUB_ENTITY_PROVIDER_CONFIG_SCHEDULE, validateLocationsExist: false, @@ -144,6 +151,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, visibility: undefined, + allowArchived: false, }, schedule: DEFAULT_GITHUB_ENTITY_PROVIDER_CONFIG_SCHEDULE, validateLocationsExist: false, @@ -162,6 +170,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, visibility: undefined, + allowArchived: false, }, schedule: DEFAULT_GITHUB_ENTITY_PROVIDER_CONFIG_SCHEDULE, validateLocationsExist: false, @@ -180,6 +189,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, visibility: undefined, + allowArchived: false, }, schedule: DEFAULT_GITHUB_ENTITY_PROVIDER_CONFIG_SCHEDULE, validateLocationsExist: false, @@ -198,6 +208,7 @@ describe('readProviderConfigs', () => { exclude: ['backstage-exclude'], }, visibility: undefined, + allowArchived: false, }, schedule: DEFAULT_GITHUB_ENTITY_PROVIDER_CONFIG_SCHEDULE, validateLocationsExist: false, @@ -216,6 +227,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, visibility: undefined, + allowArchived: false, }, schedule: DEFAULT_GITHUB_ENTITY_PROVIDER_CONFIG_SCHEDULE, validateLocationsExist: false, @@ -234,11 +246,31 @@ describe('readProviderConfigs', () => { exclude: undefined, }, visibility: ['public', 'internal'], + allowArchived: false, }, schedule: DEFAULT_GITHUB_ENTITY_PROVIDER_CONFIG_SCHEDULE, validateLocationsExist: false, }); expect(providerConfigs[7]).toEqual({ + id: 'providerWithArchiveFilter', + organization: 'test-org6', + catalogPath: '/catalog-info.yaml', + host: 'github.com', + filters: { + repository: undefined, + branch: undefined, + allowForks: true, + topic: { + include: undefined, + exclude: undefined, + }, + visibility: undefined, + allowArchived: true, + }, + schedule: DEFAULT_GITHUB_ENTITY_PROVIDER_CONFIG_SCHEDULE, + validateLocationsExist: false, + }); + expect(providerConfigs[8]).toEqual({ id: 'providerWithHost', organization: 'test-org1', catalogPath: '/catalog-info.yaml', @@ -252,11 +284,12 @@ describe('readProviderConfigs', () => { exclude: undefined, }, visibility: undefined, + allowArchived: false, }, validateLocationsExist: false, schedule: DEFAULT_GITHUB_ENTITY_PROVIDER_CONFIG_SCHEDULE, }); - expect(providerConfigs[8]).toEqual({ + expect(providerConfigs[9]).toEqual({ id: 'providerWithSchedule', organization: 'test-org1', catalogPath: '/catalog-info.yaml', @@ -270,6 +303,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, visibility: undefined, + allowArchived: false, }, schedule: { frequency: { minutes: 30 }, diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts index eefbcff5d7..e82bc498ec 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts @@ -38,12 +38,13 @@ export type GithubEntityProviderConfig = { organization?: string; app?: number; host: string; - filters?: { + filters: { repository?: RegExp; branch?: string; topic?: GithubTopicFilters; - allowForks?: boolean; + allowForks: boolean; visibility?: string[]; + allowArchived: boolean; }; validateLocationsExist: boolean; schedule?: SchedulerServiceTaskScheduleDefinition; @@ -99,6 +100,8 @@ function readProviderConfig( const topicFilterExclude = config?.getOptionalStringArray( 'filters.topic.exclude', ); + const allowArchived = + config.getOptionalBoolean('filters.allowArchived') ?? false; const validateLocationsExist = config?.getOptionalBoolean('validateLocationsExist') ?? false; @@ -142,6 +145,7 @@ function readProviderConfig( exclude: topicFilterExclude, }, visibility: visibilityFilterInclude, + allowArchived, }, schedule, validateLocationsExist, diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts index 2186b11f50..1aa1af46d7 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts @@ -2306,23 +2306,7 @@ describe('GithubMultiOrgEntityProvider', () => { organization: { teams: { pageInfo: { hasNextPage: false }, - nodes: [ - { - slug: 'team', - combinedSlug: 'orgA/team', - name: 'TeamA', - description: 'The one and only team', - avatarUrl: 'http://example.com/team.jpeg', - editTeamUrl: 'https://example.com', - parentTeam: { - slug: 'parent', - }, - members: { - pageInfo: { hasNextPage: false }, - nodes: [], - }, - }, - ], + nodes: [], }, }, }) diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts index 62b997d7e2..a1c6a17209 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts @@ -60,6 +60,7 @@ import { defaultOrganizationTeamTransformer, defaultUserTransformer, getOrganizationTeams, + assignGroupsToUser, getOrganizationUsers, GithubTeam, TeamTransformer, @@ -73,6 +74,7 @@ import { import { getOrganizationsFromUser, getOrganizationTeam, + getOrganizationTeamsForUser, getOrganizationTeamsFromUsers, } from '../lib/github'; import { splitTeamSlug } from '../lib/util'; @@ -556,15 +558,15 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { headers: orgHeaders, }); - const { teams } = await getOrganizationTeamsFromUsers( + const { teams } = await getOrganizationTeamsForUser( orgClient, userOrg, - [login], + login, this.defaultMultiOrgTeamTransformer.bind(this), ); if (isUserEntity(user) && areGroupEntities(teams)) { - assignGroupsToUsers([user], teams); + assignGroupsToUser(user, teams); } } } @@ -799,15 +801,15 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { headers: orgHeaders, }); - const { teams } = await getOrganizationTeamsFromUsers( + const { teams } = await getOrganizationTeamsForUser( orgClient, userOrg, - [login], + login, this.defaultMultiOrgTeamTransformer.bind(this), ); if (areGroupEntities(teams)) { - assignGroupsToUsers([user], teams); + assignGroupsToUser(user, teams); } } diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md index 7d5701a31d..427da4a408 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,105 @@ # @backstage/plugin-catalog-backend-module-gitlab-org +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.2-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + +## 0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.1 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.1-next.1 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.7.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.7.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.7.0-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 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 + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.6.6 + - @backstage/plugin-events-node@0.4.11 + +## 0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/plugin-catalog-backend-module-gitlab@0.6.6-next.3 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.2.9-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index 44015c6c16..e9c816c4a6 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-next.2", + "version": "0.2.12-next.0", "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 76e67018cf..5cae7ddcba 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,152 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.7.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.7.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-defaults@0.11.1 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + +## 0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.7.0 + +### Minor Changes + +- 42bb3b8: **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. + +### Patch Changes + +- 57a0bad: Implement retry for GitLab API calls to handle rate limiting +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +## 0.7.0-next.2 + +### Minor Changes + +- 42bb3b8: **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. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @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-catalog-node@1.17.1-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 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 + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-defaults@0.10.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.11 + +## 0.6.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.6.6-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index e129b83cd9..2000a19792 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-next.2", + "version": "0.7.2-next.0", "description": "A Backstage catalog backend module that helps integrate towards GitLab", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts index d7ce338d6d..d75d0e224e 100644 --- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts @@ -72,7 +72,7 @@ function setupFakeServer( assertion(req); } - if (req.headers.get('private-token') !== 'test-token') { + if (req.headers.get('authorization') !== 'Bearer test-token') { return res(ctx.status(401), ctx.json({})); } const page = req.url.searchParams.get('page'); @@ -104,8 +104,11 @@ function setupFakeServer( rest.head( `${API_URL}/projects/:project_path/repository/files/:file_path`, (req, res, ctx) => { - if (req.headers.get('private-token') !== 'test-token') { - return res(ctx.status(401), ctx.json({})); + if (req.headers.get('authorization') !== 'Bearer test-token') { + return res( + ctx.status(401), + ctx.json({ message: '401 Unauthorized' }), + ); } const ref = req.url.searchParams.get('ref'); 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 98efe68781..4d1ac222c6 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,164 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-catalog-backend@3.0.1-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.1 + +## 0.7.2 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- e2dd095: Fixed bug in `IncrementalIngestionEngine` by adding `burstLength` check when a burst completes +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0 + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-defaults@0.11.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + +## 0.7.2-next.1 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- e2dd095: Fixed bug in `IncrementalIngestionEngine` by adding `burstLength` check when a burst completes +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-backend@3.0.0-next.1 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.7.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0-next.0 + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + +## 0.7.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-catalog-backend@2.1.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + - @backstage/plugin-permission-common@0.9.0 + +## 0.7.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/plugin-catalog-backend@2.0.1-next.2 + - @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-node@1.17.1-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-permission-common@0.9.0 + +## 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 + +- 10f693c: **BREAKING** Removed support for the legacy backend, please [migrate to the new backend system](https://github.com/backstage/backstage/tree/v1.38.0/plugins/catalog-backend-module-incremental-ingestion#installation). Also, if you were importing from the `/alpha` export of this package, you should remove the `/alpha` part. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.0 + - @backstage/backend-defaults@0.10.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## 0.7.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/plugin-catalog-backend@2.0.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 0.7.0-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 60b3873e0b..5212ee811c 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-next.2", + "version": "0.7.3-next.0", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-incremental-ingestion/report.api.md b/plugins/catalog-backend-module-incremental-ingestion/report.api.md index b1fac447b2..fa69a0987c 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/report.api.md +++ b/plugins/catalog-backend-module-incremental-ingestion/report.api.md @@ -8,8 +8,6 @@ import type { DeferredEntity } from '@backstage/plugin-catalog-node'; import { EventParams } from '@backstage/plugin-events-node'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { HumanDuration } from '@backstage/types'; -import { IncrementalEntityProvider as IncrementalEntityProvider_2 } from '@backstage/plugin-catalog-backend-module-incremental-ingestion'; -import { IncrementalEntityProviderOptions as IncrementalEntityProviderOptions_2 } from '@backstage/plugin-catalog-backend-module-incremental-ingestion'; // @public const catalogModuleIncrementalIngestionEntityProvider: BackendFeature; @@ -68,8 +66,8 @@ export interface IncrementalEntityProviderOptions { // @public export interface IncrementalIngestionProviderExtensionPoint { addProvider(config: { - options: IncrementalEntityProviderOptions_2; - provider: IncrementalEntityProvider_2; + options: IncrementalEntityProviderOptions; + provider: IncrementalEntityProvider; }): void; } diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/engine/IncrementalIngestionEngine.test.ts b/plugins/catalog-backend-module-incremental-ingestion/src/engine/IncrementalIngestionEngine.test.ts new file mode 100644 index 0000000000..4bfe0666da --- /dev/null +++ b/plugins/catalog-backend-module-incremental-ingestion/src/engine/IncrementalIngestionEngine.test.ts @@ -0,0 +1,211 @@ +/* + * 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 { IncrementalIngestionEngine } from './IncrementalIngestionEngine'; +import { IterationEngineOptions } from '../types'; +import { performance } from 'perf_hooks'; + +jest.setTimeout(60_000); + +describe('IncrementalIngestionEngine - Burst Length', () => { + const createMockProvider = () => ({ + getProviderName: jest.fn().mockReturnValue('test-provider'), + next: jest.fn(), + around: jest.fn(), + }); + + const createMockManager = () => + ({ + getLastMark: jest.fn().mockResolvedValue(null), + createMark: jest.fn().mockResolvedValue(undefined), + createMarkEntities: jest.fn().mockResolvedValue(undefined), + computeRemoved: jest.fn().mockResolvedValue({ total: 0, removed: [] }), + } as any); + + const createMockConnection = () => + ({ + applyMutation: jest.fn().mockResolvedValue(undefined), + refresh: jest.fn().mockResolvedValue(undefined), + } as any); + + const createMockLogger = () => + ({ + info: jest.fn(), + debug: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + child: jest.fn().mockReturnThis(), + } as any); + + it('should respect burst length and stop burst when time limit exceeded', async () => { + const mockProvider = createMockProvider(); + const mockManager = createMockManager(); + const mockConnection = createMockConnection(); + const mockLogger = createMockLogger(); + + const options: IterationEngineOptions = { + provider: mockProvider, + manager: mockManager, + connection: mockConnection, + burstLength: { milliseconds: 100 }, // Short burst length for testing + restLength: { minutes: 1 }, + logger: mockLogger, + ready: Promise.resolve(), + }; + + const engine = new IncrementalIngestionEngine(options); + + let callCount = 0; + mockProvider.around.mockImplementation(async fn => { + await fn({}); + }); + + // Mock provider.next to return multiple batches that never complete + // Each call takes some time to simulate real processing + mockProvider.next.mockImplementation(async () => { + callCount++; + // Add a small delay to ensure we exceed burst length + await new Promise(resolve => setTimeout(resolve, 30)); + return { + done: false, + entities: [ + { + entity: { + kind: 'Component', + metadata: { name: `test-component-${callCount}` }, + }, + }, + ], + cursor: `cursor-${callCount}`, + }; + }); + + const signal = new AbortController().signal; + const start = performance.now(); + + const result = await engine.ingestOneBurst('test-ingestion', signal); + + const duration = performance.now() - start; + + // Verify that the burst was stopped due to time limit, not completion + expect(result).toBe(false); + expect(duration).toBeGreaterThanOrEqual(100); + expect(duration).toBeLessThan(200); + expect(mockProvider.next).toHaveBeenCalledTimes(callCount); + + expect(callCount).toBeGreaterThan(1); + }); + + it('should complete burst normally when provider returns done before burst length', async () => { + const mockProvider = createMockProvider(); + const mockManager = createMockManager(); + const mockConnection = createMockConnection(); + const mockLogger = createMockLogger(); + + const options: IterationEngineOptions = { + provider: mockProvider, + manager: mockManager, + connection: mockConnection, + burstLength: { seconds: 10 }, + restLength: { minutes: 1 }, + logger: mockLogger, + ready: Promise.resolve(), + }; + + const engine = new IncrementalIngestionEngine(options); + + mockProvider.around.mockImplementation(async fn => { + await fn({}); + }); + + // Mock provider.next to return done after first call + mockProvider.next.mockResolvedValueOnce({ + done: true, + entities: [ + { + entity: { + kind: 'Component', + metadata: { name: 'test-component-1' }, + }, + }, + ], + cursor: 'final-cursor', + }); + + const signal = new AbortController().signal; + const start = performance.now(); + const result = await engine.ingestOneBurst('test-ingestion', signal); + const duration = performance.now() - start; + + expect(result).toBe(true); + expect(mockProvider.next).toHaveBeenCalledTimes(1); + expect(duration).toBeLessThan(100); // Should complete quickly since provider returns done immediately + }); + + it('should stop burst when time limit is reached', async () => { + const mockProvider = createMockProvider(); + const mockManager = createMockManager(); + const mockConnection = createMockConnection(); + const mockLogger = createMockLogger(); + + const options: IterationEngineOptions = { + provider: mockProvider, + manager: mockManager, + connection: mockConnection, + burstLength: { milliseconds: 80 }, + restLength: { minutes: 1 }, + logger: mockLogger, + ready: Promise.resolve(), + }; + + const engine = new IncrementalIngestionEngine(options); + + let callCount = 0; + mockProvider.around.mockImplementation(async fn => { + await fn({}); + }); + + mockProvider.next.mockImplementation(async () => { + callCount++; + await new Promise(resolve => setTimeout(resolve, 30)); + return { + done: false, + entities: [ + { + entity: { + kind: 'Component', + metadata: { name: `test-component-${callCount}` }, + }, + }, + ], + cursor: `cursor-${callCount}`, + }; + }); + + const signal = new AbortController().signal; + const start = performance.now(); + + const result = await engine.ingestOneBurst('test-ingestion', signal); + + const duration = performance.now() - start; + + expect(result).toBe(false); + expect(mockProvider.next).toHaveBeenCalledTimes(3); + expect(callCount).toBe(3); + expect(duration).toBeGreaterThanOrEqual(90); + expect(duration).toBeLessThan(120); + }); +}); diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/engine/IncrementalIngestionEngine.ts b/plugins/catalog-backend-module-incremental-ingestion/src/engine/IncrementalIngestionEngine.ts index e005d29292..95510e78e4 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/engine/IncrementalIngestionEngine.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/engine/IncrementalIngestionEngine.ts @@ -27,6 +27,7 @@ import { HumanDuration } from '@backstage/types'; export class IncrementalIngestionEngine implements IterationEngine { private readonly restLength: Duration; + private readonly burstLength: Duration; private readonly backoff: HumanDuration[]; private readonly lastStarted: Gauge; private readonly lastCompleted: Gauge; @@ -38,6 +39,7 @@ export class IncrementalIngestionEngine implements IterationEngine { this.manager = options.manager; this.restLength = Duration.fromObject(options.restLength); + this.burstLength = Duration.fromObject(options.burstLength); this.backoff = options.backoff ?? [ { minutes: 1 }, { minutes: 5 }, @@ -247,6 +249,14 @@ export class IncrementalIngestionEngine implements IterationEngine { }); if (signal.aborted || next.done) { break; + } else if ( + performance.now() - start > + this.burstLength.as('milliseconds') + ) { + this.options.logger.info( + `incremental-engine: Ingestion '${id}' burst ending after ${this.burstLength.toHuman()}.`, + ); + break; } else { next = await this.options.provider.next(context, next.cursor); count++; diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.ts b/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.ts index 9e931c11fd..8c18f09798 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.ts @@ -20,12 +20,12 @@ import { createExtensionPoint, } from '@backstage/backend-plugin-api'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { WrapperProviders } from './WrapperProviders'; +import { eventsServiceRef } from '@backstage/plugin-events-node'; import { IncrementalEntityProvider, IncrementalEntityProviderOptions, -} from '@backstage/plugin-catalog-backend-module-incremental-ingestion'; -import { WrapperProviders } from './WrapperProviders'; -import { eventsServiceRef } from '@backstage/plugin-events-node'; +} from '../types'; /** * @public diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/types.ts b/plugins/catalog-backend-module-incremental-ingestion/src/types.ts index 75aecf8fa5..04a73bd364 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/types.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/types.ts @@ -188,6 +188,7 @@ export interface IterationEngineOptions { manager: IncrementalIngestionDatabaseManager; provider: IncrementalEntityProvider; restLength: HumanDuration; + burstLength: HumanDuration; ready: Promise; backoff?: IncrementalEntityProviderOptions['backoff']; rejectRemovalsAbovePercentage?: number; diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 431a672828..f4c1e746db 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,139 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.11.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.11.7 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + +## 0.11.7-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## 0.11.6 + +### Patch Changes + +- 087d51d: Export LDAP vendor types and instances for testing custom transformers +- f07b0ad: Added the ability to configure disabling one side of the relations tree with LDAP. + + Groups have a `member` attribute and users have a `memberOf` attribute, however these can drift out of sync in some LDAP installations, leaving weird states in the Catalog as we collate these results together and deduplicate them. + + You can chose to optionally disable one side of these relationships, or even both by setting the respective mapping to `null` in your `app-config.yaml` for your groups and/or users: + + ```yaml + catalog: + providers: + ldapOrg: + default: + 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: + # this ensures that outgoing memberships from users is ignored + memberOf: null + groups: + - dn: ou=access,ou=groups,ou=example,dc=example,dc=net + options: + filter: (&(objectClass=some-group-class)(!(groupType=email))) + map: + description: l + set: + metadata.customField: 'hello' + map: + # this ensures that outgoing memberships from groups is ignored + members: null + ``` + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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.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 + +- e253d1d: Improves error reporting for missing metadata.name in LDAP catalog provider. +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.11.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + ## 0.11.5-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/config.d.ts b/plugins/catalog-backend-module-ldap/config.d.ts index 8bd8623891..f9b49eabee 100644 --- a/plugins/catalog-backend-module-ldap/config.d.ts +++ b/plugins/catalog-backend-module-ldap/config.d.ts @@ -141,7 +141,7 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.memberOf field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; }; } | Array<{ @@ -221,7 +221,7 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.memberOf field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; }; }>; @@ -311,12 +311,12 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.parent field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; /** * The name of the attribute that shall be used for the values of * the spec.children field of the entity. Defaults to "member". */ - members?: string; + members?: string | null; }; } | Array<{ @@ -401,12 +401,12 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.parent field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; /** * The name of the attribute that shall be used for the values of * the spec.children field of the entity. Defaults to "member". */ - members?: string; + members?: string | null; }; }>; /** @@ -556,7 +556,7 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.memberOf field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; }; } | Array<{ @@ -588,6 +588,7 @@ export interface Config { pagePause?: boolean; }; }; + /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -636,7 +637,7 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.memberOf field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; }; }>; @@ -726,12 +727,12 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.parent field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; /** * The name of the attribute that shall be used for the values of * the spec.children field of the entity. Defaults to "member". */ - members?: string; + members?: string | null; }; } | Array<{ @@ -816,12 +817,12 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.parent field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; /** * The name of the attribute that shall be used for the values of * the spec.children field of the entity. Defaults to "member". */ - members?: string; + members?: string | null; }; }>; @@ -926,7 +927,6 @@ export interface Config { * paths. * * This can be useful for example if you want to hard code a - * namespace or similar on the generated entities. */ set?: { [key: string]: JsonValue }; /** @@ -969,7 +969,7 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.memberOf field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; }; }; @@ -1006,6 +1006,7 @@ export interface Config { }; }; /** + * @default false * JSON paths (on a.b.c form) and hard coded values to set on those * paths. * @@ -1058,12 +1059,12 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.parent field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; /** * The name of the attribute that shall be used for the values of * the spec.children field of the entity. Defaults to "member". */ - members?: string; + members?: string | null; }; }; /** diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 4d9888ac6d..ef2d266a79 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-next.2", + "version": "0.11.8-next.0", "description": "A Backstage catalog backend module that helps integrate towards LDAP", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-ldap/report.api.md b/plugins/catalog-backend-module-ldap/report.api.md index 16546cb773..1bcf59affb 100644 --- a/plugins/catalog-backend-module-ldap/report.api.md +++ b/plugins/catalog-backend-module-ldap/report.api.md @@ -12,7 +12,6 @@ import { EntityProvider } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { GroupEntity } from '@backstage/catalog-model'; -import { GroupTransformer as GroupTransformer_2 } from '@backstage/plugin-catalog-backend-module-ldap'; import { JsonValue } from '@backstage/types'; import { LocationSpec } from '@backstage/plugin-catalog-common'; import { LoggerService } from '@backstage/backend-plugin-api'; @@ -22,7 +21,6 @@ import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugi import { SearchEntry } from 'ldapjs'; import { SearchOptions } from 'ldapjs'; import { UserEntity } from '@backstage/catalog-model'; -import { UserTransformer as UserTransformer_2 } from '@backstage/plugin-catalog-backend-module-ldap'; // @public export type BindConfig = { @@ -63,8 +61,8 @@ export type GroupConfig = { displayName: string; email?: string; picture?: string; - memberOf: string; - members: string; + memberOf: string | null; + members: string | null; }; }; @@ -152,10 +150,10 @@ export type LdapOrgEntityProviderOptions = // @public export interface LdapOrgEntityProviderTransformsExtensionPoint { setGroupTransformer( - transformer: GroupTransformer_2 | Record, + transformer: GroupTransformer | Record, ): void; setUserTransformer( - transformer: UserTransformer_2 | Record, + transformer: UserTransformer | Record, ): void; } @@ -259,7 +257,7 @@ export type UserConfig = { displayName: string; email: string; picture?: string; - memberOf: string; + memberOf: string | null; }; }; @@ -275,4 +273,14 @@ export type VendorConfig = { dnAttributeName?: string; uuidAttributeName?: string; }; + +// @public +export const vendors: { + readonly activeDirectory: LdapVendor; + readonly aeDir: LdapVendor; + readonly freeIpa: LdapVendor; + readonly googleLdap: LdapVendor; + readonly lldap: LdapVendor; + readonly default: LdapVendor; +}; ``` diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.ts index 6fdf682b42..8c7fd0dda5 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.ts @@ -90,6 +90,7 @@ export type UserConfig = { // Only the scope, filter, attributes, and paged fields are supported. The // default is scope "one" and attributes "*" and "+". options: SearchOptions; + // JSON paths (on a.b.c form) and hard coded values to set on those paths set?: { [path: string]: JsonValue }; // Mappings from well known entity fields, to LDAP attribute names @@ -114,7 +115,7 @@ export type UserConfig = { picture?: string; // The name of the attribute that shall be used for the values of the // spec.memberOf field of the entity. Defaults to "memberOf". - memberOf: string; + memberOf: string | null; }; }; @@ -129,6 +130,7 @@ export type GroupConfig = { // The search options to use. // Only the scope, filter, attributes, and paged fields are supported. options: SearchOptions; + // JSON paths (on a.b.c form) and hard coded values to set on those paths set?: { [path: string]: JsonValue }; // Mappings from well known entity fields, to LDAP attribute names @@ -156,10 +158,10 @@ export type GroupConfig = { picture?: string; // The name of the attribute that shall be used for the values of the // spec.parent field of the entity. Defaults to "memberOf". - memberOf: string; + memberOf: string | null; // The name of the attribute that shall be used for the values of the // spec.children field of the entity. Defaults to "member". - members: string; + members: string | null; }; }; diff --git a/plugins/catalog-backend-module-ldap/src/ldap/index.ts b/plugins/catalog-backend-module-ldap/src/ldap/index.ts index 6d8fbd06d6..800ebec316 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/index.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/index.ts @@ -13,6 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { + ActiveDirectoryVendor, + AEDirVendor, + DefaultLdapVendor, + FreeIpaVendor, + GoogleLdapVendor, + LdapVendor, + LLDAPVendor, +} from './vendors'; export { LdapClient } from './client'; export { mapStringAttr } from './util'; @@ -37,3 +46,16 @@ export { readLdapOrg, } from './read'; export type { GroupTransformer, UserTransformer } from './types'; +/** + * An LDAP Vendor types. + * + * @public + */ +export const vendors = { + activeDirectory: ActiveDirectoryVendor as LdapVendor, + aeDir: AEDirVendor as LdapVendor, + freeIpa: FreeIpaVendor as LdapVendor, + googleLdap: GoogleLdapVendor as LdapVendor, + lldap: LLDAPVendor as LdapVendor, + default: DefaultLdapVendor as LdapVendor, +} as const; diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts index 4e4f4f7cfd..374cc2afd0 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts @@ -210,6 +210,53 @@ describe('readLdapUsers', () => { ); }); + it('should allow skipping memberOf', async () => { + client.getVendor.mockResolvedValue(DefaultLdapVendor); + client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => { + await fn(searchEntry({ memberOf: ['x', 'y', 'z'] })); + }); + + client.getVendor.mockResolvedValue(DefaultLdapVendor); + client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => { + await fn( + searchEntry({ + uid: ['uid-value'], + description: ['description-value'], + cn: ['cn-value'], + mail: ['mail-value'], + avatarUrl: ['avatarUrl-value'], + memberOf: ['x', 'y', 'z'], + customDN: ['dn-value'], + customUUID: ['uuid-value'], + }), + ); + }); + const config: UserConfig[] = [ + { + dn: 'ddd', + options: {}, + + map: { + rdn: 'uid', + name: 'uid', + description: 'description', + displayName: 'cn', + email: 'mail', + picture: 'avatarUrl', + memberOf: null, + }, + }, + ]; + + const vendorConfig: VendorConfig = { + dnAttributeName: 'customDN', + uuidAttributeName: 'customUUID', + }; + + const { userMemberOf } = await readLdapUsers(client, config, vendorConfig); + expect(userMemberOf.size).toBe(0); + }); + it('transfers all attributes from Microsoft Active Directory', async () => { client.getVendor.mockResolvedValue(ActiveDirectoryVendor); client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => { @@ -729,6 +776,51 @@ describe('readLdapGroups', () => { ); }); + it('should allow skipping members', async () => { + client.getVendor.mockResolvedValue(DefaultLdapVendor); + client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => { + await fn( + searchEntry({ + cn: ['cn-value'], + description: ['description-value'], + tt: ['type-value'], + mail: ['mail-value'], + avatarUrl: ['avatarUrl-value'], + memberOf: ['x', 'y', 'z'], + member: ['e', 'f', 'g'], + customDN: ['dn-value'], + customUUID: ['uuid-value'], + }), + ); + }); + const config: GroupConfig[] = [ + { + dn: 'ddd', + options: {}, + map: { + rdn: 'cn', + name: 'cn', + description: 'description', + displayName: 'cn', + email: 'mail', + picture: 'avatarUrl', + type: 'tt', + memberOf: 'memberOf', + members: null, + }, + }, + ]; + + const vendorConfig: VendorConfig = { + dnAttributeName: 'customDN', + uuidAttributeName: 'customUUID', + }; + + const { groupMember } = await readLdapGroups(client, config, vendorConfig); + + expect(groupMember.size).toBe(0); + }); + it('can process a list of GroupConfigs', async () => { client.getVendor.mockResolvedValue(DefaultLdapVendor); client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => { diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.ts index 9b9041913d..a3f3ce1e75 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.ts @@ -146,6 +146,7 @@ export async function readLdapUsers( mapReferencesAttr(user, vendor, map.memberOf, (myDn, vs) => { ensureItems(userMemberOf, myDn, vs); }); + entities.push(entity); }); } @@ -277,6 +278,7 @@ export async function readLdapGroups( mapReferencesAttr(entry, vendor, map.memberOf, (myDn, vs) => { ensureItems(groupMemberOf, myDn, vs); }); + mapReferencesAttr(entry, vendor, map.members, (myDn, vs) => { ensureItems(groupMember, myDn, vs); }); @@ -349,7 +351,7 @@ export async function readLdapOrg( function mapReferencesAttr( entry: SearchEntry, vendor: LdapVendor, - attributeName: string | undefined, + attributeName: string | undefined | null, setter: (sourceDn: string, targets: string[]) => void, ) { if (attributeName) { diff --git a/plugins/catalog-backend-module-ldap/src/module.ts b/plugins/catalog-backend-module-ldap/src/module.ts index ad76b133f1..dd47bf48da 100644 --- a/plugins/catalog-backend-module-ldap/src/module.ts +++ b/plugins/catalog-backend-module-ldap/src/module.ts @@ -20,11 +20,8 @@ import { createExtensionPoint, } from '@backstage/backend-plugin-api'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; -import { - GroupTransformer, - UserTransformer, -} from '@backstage/plugin-catalog-backend-module-ldap'; import { LdapOrgEntityProvider } from './processors'; +import { GroupTransformer, UserTransformer } from './ldap/types'; /** * Interface for {@link LdapOrgEntityProviderTransformsExtensionPoint}. diff --git a/plugins/catalog-backend-module-logs/CHANGELOG.md b/plugins/catalog-backend-module-logs/CHANGELOG.md index a7e0d37921..cb8ed569ef 100644 --- a/plugins/catalog-backend-module-logs/CHANGELOG.md +++ b/plugins/catalog-backend-module-logs/CHANGELOG.md @@ -1,5 +1,96 @@ # @backstage/plugin-catalog-backend-module-logs +## 0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.1-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + +## 0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## 0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-backend@3.0.0-next.1 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.0.0-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.1.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + +## 0.1.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.1-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 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 + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-events-node@0.4.11 + +## 0.1.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.1.10-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index a6f748e4ca..16722c3ed4 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-next.2", + "version": "0.1.13-next.0", "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 f4ea154223..318585b52e 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,97 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.7.2 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + +## 0.7.2-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## 0.7.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + +## 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 + +- 20c1ea7: Add new `userGroupMember.path`, `user.path` and, `group.path` option to each query type to allow more complex msgraph queries + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.7.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + ## 0.7.0-next.1 ### Minor Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index ae116ca435..849272cfb9 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-next.1", + "version": "0.7.3-next.0", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-msgraph/report.api.md b/plugins/catalog-backend-module-msgraph/report.api.md index 02d3949d04..c6687a638a 100644 --- a/plugins/catalog-backend-module-msgraph/report.api.md +++ b/plugins/catalog-backend-module-msgraph/report.api.md @@ -11,18 +11,14 @@ import { EntityProvider } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { GroupEntity } from '@backstage/catalog-model'; -import { GroupTransformer as GroupTransformer_2 } from '@backstage/plugin-catalog-backend-module-msgraph'; import { LocationSpec } from '@backstage/plugin-catalog-common'; import { LoggerService } from '@backstage/backend-plugin-api'; import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; -import { OrganizationTransformer as OrganizationTransformer_2 } from '@backstage/plugin-catalog-backend-module-msgraph'; -import { ProviderConfigTransformer as ProviderConfigTransformer_2 } from '@backstage/plugin-catalog-backend-module-msgraph'; import { SchedulerService } from '@backstage/backend-plugin-api'; import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { TokenCredential } from '@azure/identity'; import { UserEntity } from '@backstage/catalog-model'; -import { UserTransformer as UserTransformer_2 } from '@backstage/plugin-catalog-backend-module-msgraph'; // @public const catalogModuleMicrosoftGraphOrgEntityProvider: BackendFeature; @@ -184,20 +180,20 @@ export const microsoftGraphOrgEntityProviderTransformExtensionPoint: ExtensionPo // @public export interface MicrosoftGraphOrgEntityProviderTransformsExtensionPoint { setGroupTransformer( - transformer: GroupTransformer_2 | Record, + transformer: GroupTransformer | Record, ): void; setOrganizationTransformer( transformer: - | OrganizationTransformer_2 - | Record, + | OrganizationTransformer + | Record, ): void; setProviderConfigTransformer( transformer: - | ProviderConfigTransformer_2 - | Record, + | ProviderConfigTransformer + | Record, ): void; setUserTransformer( - transformer: UserTransformer_2 | Record, + transformer: UserTransformer | Record, ): void; } diff --git a/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.ts index 262c56cf3f..a11748f021 100644 --- a/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.ts @@ -25,7 +25,7 @@ import { OrganizationTransformer, ProviderConfigTransformer, UserTransformer, -} from '@backstage/plugin-catalog-backend-module-msgraph'; +} from '../microsoftGraph/types'; import { MicrosoftGraphOrgEntityProvider } from '../processors'; /** diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index c6b53d288d..73b5896da0 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,107 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + +## 0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/catalog-model@1.7.4 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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.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 + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.2.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + ## 0.2.10-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 5f0c106b0d..00b390c6ed 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-next.2", + "version": "0.2.13-next.0", "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 324a091dc4..42a880806b 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,95 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.2.12 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-node@1.17.2 + +## 0.2.12-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## 0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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.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 + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.2 + ## 0.2.10-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index fc830eb66e..336dc6aaaa 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-next.1", + "version": "0.2.13-next.0", "description": "A Backstage catalog backend module that helps integrate towards PuppetDB", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-puppetdb/src/puppet/read.ts b/plugins/catalog-backend-module-puppetdb/src/puppet/read.ts index 891b19691a..9527ddd774 100644 --- a/plugins/catalog-backend-module-puppetdb/src/puppet/read.ts +++ b/plugins/catalog-backend-module-puppetdb/src/puppet/read.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { PuppetDbEntityProviderConfig } from '../providers'; +import { PuppetDbEntityProviderConfig } from '../providers/PuppetDbEntityProviderConfig'; import { PuppetNode, ResourceTransformer } from './types'; import { ResourceEntity } from '@backstage/catalog-model'; import { defaultResourceTransformer } from './transformers'; diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md index af44065886..cd264b413d 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,97 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.7.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-scaffolder-common@1.6.0 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + +## 0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.6.0-next.1 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-scaffolder-common@1.5.12-next.0 + +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 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 + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 0.2.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + ## 0.2.8-next.1 ### 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 dc32af8cef..896c29efcc 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-next.1", + "version": "0.2.11-next.0", "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 a2341a7e40..a4966abfe0 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,107 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.6.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.9 + - @backstage/plugin-permission-common@0.9.1 + +## 0.6.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.9 + +## 0.6.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.9-next.0 + +## 0.6.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @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.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 + +- f453d5c: **BREAKING** Removed support for the legacy backend and removed references to `@backstage/backend-common`, please [migrate to the new backend system](https://backstage.io/docs/backend-system/building-plugins-and-modules/migrating) + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.8 + +## 0.6.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.8-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 0.6.0-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index 98e2ba8f08..11053d46c9 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-next.1", + "version": "0.6.3-next.0", "description": "Backstage Catalog module to view unprocessed entities", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 1f9802a3e8..c7f31db7a9 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,315 @@ # @backstage/plugin-catalog-backend +## 3.0.1-next.1 + +### Patch Changes + +- 1752be6: Attempt to circumvent event listener memory leak in compression middleware +- 9dd213c: Make the processing hash calculation not care about the order of the processors. + + This change does not affect the behavior of the catalog, but it will make the processing + hash calculation more robust against changes in the order of processors. This should lead to + more stable processing hashes, which in turn should lead to fewer unnecessary reprocessing + of entities. + + After deploying this fix, you may see a period of increased processing and stitching, but + this should stabilize over time as the processing hashes become more consistent. + +- fa6fa60: Fixed getLocationByEntity to use `original_value` instead of `value` when querying search table +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.0-next.1 + +## 3.0.1-next.0 + +### Patch Changes + +- 3a7dad9: Updated `better-sqlite3` to v12 +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.0-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-common@0.9.1 + +## 3.0.0 + +### Major Changes + +- 5127ebe: **BREAKING**: The default `catalog.stitchingStrategy` has been switched to `{ mode: 'deferred' }`. +- d675d96: **BREAKING**: The relations compatibility mode is no longer enabled by default, and the `disableRelationsCompatiblity` flag has been removed. To re-enable relations compatibility, the new `enableRelationsCompatibility` flag can be used instead. +- 2339363: **BREAKING:** The experimental `catalog.useUrlReadersSearch` configuration flag (introduced in v1.36) has been removed. + + The `UrlReaderProcessor` now always uses the `search` method of `UrlReaders`. Built-in `UrlReaderService` implementations have been updated accordingly. + If you use custom `UrlReaderService` implementations, you need to adapt their `search` method to correctly handle both specific URLs and potential + search patterns (see changes on built-in readers [in the original PR](https://github.com/backstage/backstage/pull/28379/files#diff-68b0452f173ee54bdd40f7b5e047a9cb8bb59200425622c212c217b76dac1d1b)). + + Previous behavior was to call the `search` method only if the parsed Git URL's filename contained a wildcard and use `readUrl` otherwise. Each `UrlReaderService` must implement this logic in the `search` method instead. + + This allows each `UrlReaderService` implementation to check whether it's a search URL (that contains a wildcard pattern) or not using logic that is specific to each provider. + +- 687bfc8: **BREAKING**: The default `catalog.orphanStrategy` has been switched to `'delete'`. +- 5de7a9d: **BREAKING**: The default `catalog.orphanProviderStrategy` has been switched to `'delete'`. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/integration@1.17.1 + - @backstage/backend-openapi-utils@0.5.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + +## 3.0.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/backend-openapi-utils@0.5.5-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 3.0.0-next.0 + +### Major Changes + +- 2339363: **BREAKING:** The experimental `catalog.useUrlReadersSearch` configuration flag (introduced in v1.36) has been removed. + + The `UrlReaderProcessor` now always uses the `search` method of `UrlReaders`. Built-in `UrlReaderService` implementations have been updated accordingly. + If you use custom `UrlReaderService` implementations, you need to adapt their `search` method to correctly handle both specific URLs and potential + search patterns (see changes on built-in readers [in the original PR](https://github.com/backstage/backstage/pull/28379/files#diff-68b0452f173ee54bdd40f7b5e047a9cb8bb59200425622c212c217b76dac1d1b)). + + Previous behavior was to call the `search` method only if the parsed Git URL's filename contained a wildcard and use `readUrl` otherwise. Each `UrlReaderService` must implement this logic in the `search` method instead. + + This allows each `UrlReaderService` implementation to check whether it's a search URL (that contains a wildcard pattern) or not using logic that is specific to each provider. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/backend-openapi-utils@0.5.4 + - @backstage/catalog-client@1.10.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-permission-common@0.9.0 + +## 2.1.0 + +### Minor Changes + +- 2e7adf0: Implement the action `get-catalog-entity` with the `ActionsRegistry` + +### Patch Changes + +- 2cac8b0: You can now specify an optional value when applying the `HAS_LABEL` permission rule, similar to the `HAS_ANNOTATION` permission rule. +- c83cd8b: Fixed some circular or otherwise unclear imports +- 4654a78: Update `refresh_state_references.id` to be a big int +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/backend-openapi-utils@0.5.4 + - @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 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1 + +## 2.0.1-next.2 + +### Patch Changes + +- 2cac8b0: You can now specify an optional value when applying the `HAS_LABEL` permission rule, similar to the `HAS_ANNOTATION` permission rule. +- Updated dependencies + - @backstage/backend-openapi-utils@0.5.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-node@1.17.1-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 + +## 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 + +- 90ab044: **BREAKING**: Removed all deprecated exports, and removed support for the old backend system. + + It also removes the `CodeOwnersProcessor` from the default set of processors, because it is expensive to run and has vague semantics. You need to update your backend to add it to the `catalogProcessingExtensionPoint` if you wish to continue using it. + + The following removed exports are available from `@backstage/plugin-catalog-node`: + + - `locationSpecToMetadataName` + - `locationSpecToLocationEntity` + - `processingResult` + - `EntitiesSearchFilter` + - `EntityFilter` + - `DeferredEntity` + - `EntityRelationSpec` + - `CatalogProcessor` + - `CatalogProcessorParser` + - `CatalogProcessorCache` + - `CatalogProcessorEmit` + - `CatalogProcessorLocationResult` + - `CatalogProcessorEntityResult` + - `CatalogProcessorRelationResult` + - `CatalogProcessorErrorResult` + - `CatalogProcessorRefreshKeysResult` + - `CatalogProcessorResult` + - `EntityProvider` + - `EntityProviderConnection` + - `EntityProviderMutation` + - `AnalyzeOptions` + - `LocationAnalyzer` + - `ScmLocationAnalyzer` + - `PlaceholderResolver` + - `PlaceholderResolverParams` + - `PlaceholderResolverRead` + - `PlaceholderResolverResolveUrl` + - `parseEntityYaml` + + The following removed exports are available from `@backstage/plugin-catalog-common`: + + - `LocationSpec` + - `AnalyzeLocationRequest` + - `AnalyzeLocationResponse` + - `AnalyzeLocationExistingEntity` + - `AnalyzeLocationGenerateEntity` + - `AnalyzeLocationEntityField` + + The following removed exports are instead implemented in the new backend system by `@backstage/plugin-search-backend-module-catalog`: + + - `defaultCatalogCollatorEntityTransformer` + - `CatalogCollatorEntityTransformer` + - `DefaultCatalogCollator` + + The following exports are removed without a direct replacement: + + - `DefaultCatalogCollatorFactory` + - `DefaultCatalogCollatorFactoryOptions` + - `LocationEntityProcessor` + - `LocationEntityProcessorOptions` + - `CatalogBuilder` + - `CatalogEnvironment` + - `CatalogPermissionRuleInput` + - `CatalogProcessingEngine` + - `createRandomProcessingInterval` + - `ProcessingIntervalFunction` + +### Minor Changes + +- 6c9b88e: **BREAKING ALPHA**: You can no longer import the catalog plugin from the `/alpha` export; please use the regular root default export instead. +- d88b922: Adds the ability to disable the default entity processors using a new boolean app config item `catalog.disableDefaultProcessors`. + +### Patch Changes + +- 0e710fc: This patch addresses an issue identified in Backstage when configured with a MySQL database. If an entity of type location + (e..all.yaml) has more than 70 referenced entities, clicking "Refresh" does not update the referenced entities as expected. This occurs because the TEXT type in MySQL has a limit of 65,535 bytes, which is insufficient to store all the referenced entities, causing the refresh operation to fail. +- 8e0f15f: "Added a note clarifying that `entity-fetch` audit events are not visible by default in the logs and are only displayed when the log severity level is adjusted." +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/catalog-client@1.10.0 + - @backstage/backend-openapi-utils@0.5.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.11 + +## 2.0.0-next.3 + +### Patch Changes + +- 8e0f15f: "Added a note clarifying that `entity-fetch` audit events are not visible by default in the logs and are only displayed when the log severity level is adjusted." +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-openapi-utils@0.5.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + ## 2.0.0-next.2 ### Patch Changes diff --git a/plugins/catalog-backend/README.md b/plugins/catalog-backend/README.md index 680adc0bcd..00b88cf060 100644 --- a/plugins/catalog-backend/README.md +++ b/plugins/catalog-backend/README.md @@ -94,6 +94,7 @@ The Catalog backend emits audit events for various operations. Events are groupe **Entity Events:** - **`entity-fetch`**: Retrieves entities. +- **Note:** By default, "low" severity audit events like `entity-fetch` aren't logged because they map to the "debug" level, while Backstage defaults to "info" level logging. To see `entity-fetch` events, update your `app-config.yaml` by setting `backend.auditor.severityLogLevelMappings.low: info`. See the [Auditor Service documentation](https://backstage.io/docs/backend-system/core-services/auditor/#severity-levels-and-default-mappings) for details on severity mappings. Filter on `queryType`. diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index 7e68c4bea9..6329bcb111 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -139,14 +139,14 @@ export interface Config { }>; /** - * Disables the compatibility layer for relations in returned entities that + * Enables the compatibility layer for relations in returned entities that * ensures that all relations objects have both `target` and `targetRef`. * - * Enabling this option significantly reduces the memory usage of the - * catalog, and slightly increases performance, but may break consumers that + * Enabling this option significantly increases the memory usage of the + * catalog, and slightly reduces performance, but may avoid breaking consumers that * rely on the existence of `target` in the relations objects. */ - disableRelationsCompatibility?: boolean; + enableRelationsCompatibility?: boolean; /** * Disables the default backstage processors. @@ -160,19 +160,19 @@ export interface Config { /** * The strategy to use for entities that are orphaned, i.e. no longer have * any other entities or providers referencing them. The default value is - * "keep". + * "delete". */ orphanStrategy?: 'keep' | 'delete'; /** * The strategy to use for entities that are referenced by providers that are orphaned, * i.e. entities with no providers currently configured in the catalog. The default value is - * "keep". + * "delete". */ orphanProviderStrategy?: 'keep' | 'delete'; /** - * The strategy to use when stitching together the final entities. + * The strategy to use when stitching together the final entities. The default mode is "deferred". */ stitchingStrategy?: | { @@ -218,19 +218,5 @@ export interface Config { * housing catalog-info files. */ processingInterval?: HumanDuration | false; - - /** - * Defines if the UrlReaderProcessor should always call the search method of the - * different UrlReaders. - * - * If set to false, the UrlReaderProcessor will use the legacy behavior that tries to - * parse a Git URL and calls search if there's wildcard patterns and readUrl otherwise. - * - * If set to true, the UrlReaderProcessor always call the search method and lets each UrlReader - * determine if it's a search pattern or not. - * - * This flag is temporary and will be enabled by default in future releases. - */ - useUrlReadersSearch?: boolean; }; } 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 3b548a9f67..3220ec5fd1 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-next.2", + "version": "3.0.1-next.1", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin", @@ -99,14 +99,13 @@ "@backstage/cli": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/repo-tools": "workspace:^", - "@backstage/test-utils": "workspace:^", "@types/core-js": "^2.5.4", "@types/express": "^4.17.6", "@types/git-url-parse": "^9.0.0", "@types/glob": "^8.0.0", "@types/lodash": "^4.14.151", "@types/supertest": "^2.0.8", - "better-sqlite3": "^11.0.0", + "better-sqlite3": "^12.0.0", "luxon": "^3.0.0", "msw": "^1.0.0", "supertest": "^7.0.0", 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.api.md b/plugins/catalog-backend/report.api.md index 9bb94b2dd5..6e5ca956bc 100644 --- a/plugins/catalog-backend/report.api.md +++ b/plugins/catalog-backend/report.api.md @@ -135,11 +135,7 @@ export function transformLegacyPolicyToProcessor( // @public (undocumented) export class UrlReaderProcessor implements CatalogProcessor { - constructor(options: { - reader: UrlReaderService; - logger: LoggerService; - config?: Config; - }); + constructor(options: { reader: UrlReaderService; logger: LoggerService }); // (undocumented) getProcessorName(): string; // (undocumented) 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/actions/createGetCatalogEntityAction.test.ts b/plugins/catalog-backend/src/actions/createGetCatalogEntityAction.test.ts new file mode 100644 index 0000000000..ed8aec264d --- /dev/null +++ b/plugins/catalog-backend/src/actions/createGetCatalogEntityAction.test.ts @@ -0,0 +1,78 @@ +/* + * 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 { createGetCatalogEntityAction } from './createGetCatalogEntityAction'; +import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; +import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; + +describe('createGetCatalogEntityAction', () => { + it('should throw an error if the entity is not found', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock(); + + createGetCatalogEntityAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:get-catalog-entity', + input: { name: 'test' }, + }), + ).rejects.toThrow(`No entity found with name "test"`); + }); + + it('should throw an error if theres multiple entities found', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock({ + entities: [ + { + kind: 'Component', + apiVersion: 'backstage.io/v1alpha1', + metadata: { + name: 'test', + namespace: 'default', + }, + spec: { + type: 'component', + }, + }, + { + kind: 'API', + apiVersion: 'backstage.io/v1alpha1', + metadata: { + name: 'test', + namespace: 'default', + }, + }, + ], + }); + + createGetCatalogEntityAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:get-catalog-entity', + input: { name: 'test' }, + }), + ).rejects.toThrow( + `Multiple entities found with name "test", please provide more specific filters. Entities found: "component:default/test", "api:default/test"`, + ); + }); +}); diff --git a/plugins/catalog-backend/src/actions/createGetCatalogEntityAction.ts b/plugins/catalog-backend/src/actions/createGetCatalogEntityAction.ts new file mode 100644 index 0000000000..da33064c08 --- /dev/null +++ b/plugins/catalog-backend/src/actions/createGetCatalogEntityAction.ts @@ -0,0 +1,95 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; +import { stringifyEntityRef } from '@backstage/catalog-model'; +import { InputError } from '@backstage/errors'; +import { CatalogService } from '@backstage/plugin-catalog-node'; + +export const createGetCatalogEntityAction = ({ + catalog, + actionsRegistry, +}: { + catalog: CatalogService; + actionsRegistry: ActionsRegistryService; +}) => { + actionsRegistry.register({ + name: 'get-catalog-entity', + title: 'Get Catalog Entity', + description: ` +This allows you to get a single entity from the software catalog. +Each entity in the software catalog has a unique name, kind, and namespace. The default namespace is "default". +Each entity is identified by a unique entity reference, which is a string of the form "kind:namespace/name". + `, + schema: { + input: z => + z.object({ + kind: z + .string() + .describe( + 'The kind of the entity to query. If the kind is unknown it can be omitted.', + ) + .optional(), + namespace: z + .string() + .describe( + 'The namespace of the entity to query. If the namespace is unknown it can be omitted.', + ) + .optional(), + name: z.string().describe('The name of the entity to query'), + }), + // TODO: is there a better way to do this? + output: z => z.object({}).passthrough(), + }, + action: async ({ input, credentials }) => { + const filter: Record = { 'metadata.name': input.name }; + + if (input.kind) { + filter.kind = input.kind; + } + + if (input.namespace) { + filter['metadata.namespace'] = input.namespace; + } + + const { items } = await catalog.queryEntities( + { filter }, + { + credentials, + }, + ); + + if (items.length === 0) { + throw new InputError(`No entity found with name "${input.name}"`); + } + + if (items.length > 1) { + throw new Error( + `Multiple entities found with name "${ + input.name + }", please provide more specific filters. Entities found: ${items + .map(item => `"${stringifyEntityRef(item)}"`) + .join(', ')}`, + ); + } + + const [entity] = items; + + return { + output: entity, + }; + }, + }); +}; 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/processing/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts index a3ef926b47..266b80f9a4 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts @@ -46,6 +46,11 @@ const tracer = trace.getTracer(TRACER_ID); export type ProgressTracker = ReturnType; +const stableStringifyArray = (arr: any[]) => { + const sorted = arr.map(stableStringify).sort(); + return `[${sorted.join(',')}]`; +}; + // NOTE(freben): Perhaps surprisingly, this class does not implement the // CatalogProcessingEngine type. That type is externally visible and its name is // the way it is for historic reasons. This class has no particular reason to @@ -226,10 +231,10 @@ export class DefaultCatalogProcessingEngine { hashBuilder = hashBuilder .update(stableStringify({ ...result.completedEntity })) - .update(stableStringify([...result.deferredEntities])) - .update(stableStringify([...result.relations])) - .update(stableStringify([...result.refreshKeys])) - .update(stableStringify([...parents])); + .update(stableStringifyArray([...result.deferredEntities])) + .update(stableStringifyArray([...result.relations])) + .update(stableStringifyArray([...result.refreshKeys])) + .update(stableStringifyArray([...parents])); } const resultHash = hashBuilder.digest('hex'); @@ -344,7 +349,7 @@ export class DefaultCatalogProcessingEngine { private startOrphanCleanup(): () => void { const orphanStrategy = - this.config.getOptionalString('catalog.orphanStrategy') ?? 'keep'; + this.config.getOptionalString('catalog.orphanStrategy') ?? 'delete'; if (orphanStrategy !== 'delete') { return () => {}; } diff --git a/plugins/catalog-backend/src/processors/UrlReaderProcessor.test.ts b/plugins/catalog-backend/src/processors/UrlReaderProcessor.test.ts index 2d7268eb33..0b17ecb19f 100644 --- a/plugins/catalog-backend/src/processors/UrlReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/processors/UrlReaderProcessor.test.ts @@ -30,8 +30,6 @@ import { import { defaultEntityDataParser } from '../util/parse'; import { UrlReaderProcessor } from './UrlReaderProcessor'; import { UrlReaders } from '@backstage/backend-defaults/urlReader'; -import { UrlReaderService } from '@backstage/backend-plugin-api'; -import { mockApis } from '@backstage/test-utils'; describe('UrlReaderProcessor', () => { const mockApiOrigin = 'http://localhost'; @@ -191,11 +189,11 @@ describe('UrlReaderProcessor', () => { expect(generated.location).toBe(spec); expect(generated.error.name).toBe('NotFoundError'); expect(generated.error.message).toBe( - `Unable to read url, NotFoundError: could not read ${mockApiOrigin}/component-notfound.yaml, 404 Not Found`, + `Unable to read url, no matching files found for ${mockApiOrigin}/component-notfound.yaml`, ); }); - it('uses search when there are globs', async () => { + it("uses reader' search method", async () => { const logger = mockServices.logger.mock(); const reader = mockServices.urlReader.mock({ @@ -206,38 +204,6 @@ describe('UrlReaderProcessor', () => { const emit = jest.fn(); - await processor.readLocation( - { type: 'url', target: 'https://github.com/a/b/blob/x/**/b.yaml' }, - false, - emit, - defaultEntityDataParser, - mockCache, - ); - - expect(reader.search).toHaveBeenCalledTimes(1); - }); - - it('uses search when catalog.useUrlReadersSearch flag is set to true', async () => { - const logger = mockServices.logger.mock(); - - const reader: jest.Mocked = { - readUrl: jest.fn(), - readTree: jest.fn(), - search: jest.fn().mockImplementation(async () => []), - }; - - const config = mockApis.config({ - data: { - catalog: { - useUrlReadersSearch: true, - }, - }, - }); - - const processor = new UrlReaderProcessor({ reader, logger, config }); - - const emit = jest.fn(); - await processor.readLocation( { type: 'url', target: 'https://github.com/a/b/blob/x/b.yaml' }, false, diff --git a/plugins/catalog-backend/src/processors/UrlReaderProcessor.ts b/plugins/catalog-backend/src/processors/UrlReaderProcessor.ts index bc383512be..4440a81bae 100644 --- a/plugins/catalog-backend/src/processors/UrlReaderProcessor.ts +++ b/plugins/catalog-backend/src/processors/UrlReaderProcessor.ts @@ -18,7 +18,6 @@ import { Entity } from '@backstage/catalog-model'; import { assertError } from '@backstage/errors'; import limiterFactory, { Limit } from 'p-limit'; import { LocationSpec } from '@backstage/plugin-catalog-common'; -import parseGitUrl from 'git-url-parse'; import { CatalogProcessor, CatalogProcessorCache, @@ -29,7 +28,6 @@ import { processingResult, } from '@backstage/plugin-catalog-node'; import { LoggerService, UrlReaderService } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; const CACHE_KEY = 'v1'; @@ -48,25 +46,14 @@ export class UrlReaderProcessor implements CatalogProcessor { // This limiter is used for only consuming a limited number of read streams // concurrently. #limiter: Limit; - #useUrlReadersSearch: boolean; constructor( private readonly options: { reader: UrlReaderService; logger: LoggerService; - config?: Config; }, ) { this.#limiter = limiterFactory(5); - - this.#useUrlReadersSearch = - this.options.config?.getOptionalBoolean('catalog.useUrlReadersSearch') || - false; - if (!this.#useUrlReadersSearch) { - this.options.logger.warn( - 'UrlReaderProcessor uses the legacy readUrl/search behavior which will be removed in a future release. Set catalog.useUrlReadersSearch to true to adopt the new behavior.', - ); - } } getProcessorName() { @@ -149,35 +136,13 @@ export class UrlReaderProcessor implements CatalogProcessor { location: string, etag?: string, ): Promise<{ response: { data: Buffer; url: string }[]; etag?: string }> { - // New behavior: always use the search method - if (this.#useUrlReadersSearch) { - const response = await this.options.reader.search(location, { etag }); + const response = await this.options.reader.search(location, { etag }); - const output = response.files.map(async file => ({ - url: file.url, - data: await this.#limiter(file.content), - })); + const output = response.files.map(async file => ({ + url: file.url, + data: await this.#limiter(file.content), + })); - return { response: await Promise.all(output), etag: response.etag }; - } - - // Old behavior: Does it contain globs? I.e. does it contain asterisks or question marks - // (no curly braces for now) - - const { filepath } = parseGitUrl(location); - if (filepath?.match(/[*?]/)) { - const response = await this.options.reader.search(location, { etag }); - const output = response.files.map(async file => ({ - url: file.url, - data: await this.#limiter(file.content), - })); - return { response: await Promise.all(output), etag: response.etag }; - } - - const data = await this.options.reader.readUrl(location, { etag }); - return { - response: [{ url: location, data: await data.buffer() }], - etag: data.etag, - }; + return { response: await Promise.all(output), etag: response.etag }; } } diff --git a/plugins/catalog-backend/src/providers/DefaultLocationStore.test.ts b/plugins/catalog-backend/src/providers/DefaultLocationStore.test.ts index 5e3f3e0b17..05e078723d 100644 --- a/plugins/catalog-backend/src/providers/DefaultLocationStore.test.ts +++ b/plugins/catalog-backend/src/providers/DefaultLocationStore.test.ts @@ -211,6 +211,7 @@ describe('DefaultLocationStore', () => { entity_id: entityId, key: `metadata.annotations.${ANNOTATION_ORIGIN_LOCATION}`, value: `url:https://example.com`, + original_value: `url:https://example.com`, }); await knex('locations').insert({ diff --git a/plugins/catalog-backend/src/providers/DefaultLocationStore.ts b/plugins/catalog-backend/src/providers/DefaultLocationStore.ts index 2b3459ecb1..8420cbb8c4 100644 --- a/plugins/catalog-backend/src/providers/DefaultLocationStore.ts +++ b/plugins/catalog-backend/src/providers/DefaultLocationStore.ts @@ -137,15 +137,15 @@ export class DefaultLocationStore implements LocationStore, EntityProvider { entity_id: entityRow.entity_id, key: `metadata.annotations.${ANNOTATION_ORIGIN_LOCATION}`, }) - .select('value') + .select('original_value') .limit(1); - if (!searchRow?.value) { + if (!searchRow?.original_value) { throw new NotFoundError( `found no origin annotation for ref ${entityRefString}`, ); } - const { type, target } = parseLocationRef(searchRow.value); + const { type, target } = parseLocationRef(searchRow.original_value); const [locationRow] = await this.db('locations') .where({ type, target }) .select() diff --git a/plugins/catalog-backend/src/schema/openapi/generated/router.ts b/plugins/catalog-backend/src/schema/openapi/generated/router.ts index 67eb1a699e..d32ef0a4de 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/router.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/router.ts @@ -18,7 +18,7 @@ // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** import { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage/backend-openapi-utils'; -import { EndpointMap } from './'; +import { EndpointMap } from './apis'; export const spec = { openapi: '3.0.3', diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index c9d75298c8..93a6a96eb0 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -356,7 +356,7 @@ export class CatalogBuilder { return [ new FileReaderProcessor(), - new UrlReaderProcessor({ reader, logger, config }), + new UrlReaderProcessor({ reader, logger }), new AnnotateLocationEntityProcessor({ integrations }), ]; } @@ -463,8 +463,8 @@ export class CatalogBuilder { httpAuth, } = this.env; - const disableRelationsCompatibility = config.getOptionalBoolean( - 'catalog.disableRelationsCompatibility', + const enableRelationsCompatibility = Boolean( + config.getOptionalBoolean('catalog.enableRelationsCompatibility'), ); const policy = this.buildEntityPolicy(); @@ -503,7 +503,7 @@ export class CatalogBuilder { database: dbClient, logger, stitcher, - disableRelationsCompatibility, + enableRelationsCompatibility, }); let permissionsService: PermissionsService; @@ -619,12 +619,10 @@ export class CatalogBuilder { httpAuth, permissionsService, auditor, - disableRelationsCompatibility, + enableRelationsCompatibility, }); - if ( - config.getOptionalString('catalog.orphanProviderStrategy') === 'delete' - ) { + if (config.getOptionalString('catalog.orphanProviderStrategy') !== 'keep') { await evictEntitiesFromOrphanedProviders({ db: providerDatabase, providers: entityProviders, diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.test.ts b/plugins/catalog-backend/src/service/CatalogPlugin.test.ts index 9cec22322e..657cca16f3 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.test.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.test.ts @@ -67,5 +67,9 @@ describe('catalogPlugin', () => { paramsSchema: expect.any(Object), description: 'Test permission rule', }); + + // For some reason the test running breaks if this test exits too soon. + // This is an ugly workaround for now since it is blocking the 1.41 release. + await new Promise(resolve => setTimeout(resolve, 1000)); }); }); diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index 9925f11c7b..a9bb2c9285 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -22,6 +22,7 @@ import { ForwardedError } from '@backstage/errors'; import { CatalogProcessor, CatalogProcessorParser, + catalogServiceRef, EntityProvider, LocationAnalyzer, PlaceholderResolver, @@ -43,6 +44,8 @@ import { eventsServiceRef } from '@backstage/plugin-events-node'; import { Permission } from '@backstage/plugin-permission-common'; import { merge } from 'lodash'; import { CatalogBuilder } from './CatalogBuilder'; +import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha'; +import { createGetCatalogEntityAction } from '../actions/createGetCatalogEntityAction'; class CatalogLocationsExtensionPointImpl implements CatalogLocationsExtensionPoint @@ -237,6 +240,8 @@ export const catalogPlugin = createBackendPlugin({ httpAuth: coreServices.httpAuth, auditor: coreServices.auditor, events: eventsServiceRef, + catalog: catalogServiceRef, + actionsRegistry: actionsRegistryServiceRef, }, async init({ logger, @@ -250,6 +255,8 @@ export const catalogPlugin = createBackendPlugin({ scheduler, auth, httpAuth, + catalog, + actionsRegistry, auditor, events, }) { @@ -313,6 +320,11 @@ export const catalogPlugin = createBackendPlugin({ } httpRouter.use(router); + + createGetCatalogEntityAction({ + catalog, + actionsRegistry, + }); }, }); }, diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts index aad09d7de1..0b8dec71bb 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts @@ -525,7 +525,7 @@ describe('DefaultEntitiesCatalog', () => { ); it.each(databases.eachSupportedId())( - 'should return both target and targetRef for entities', + 'should return both target and targetRef for entities in compat mode', async databaseId => { await createDatabase(databaseId); await addEntity( @@ -557,6 +557,7 @@ describe('DefaultEntitiesCatalog', () => { database: knex, logger: mockServices.logger.mock(), stitcher, + enableRelationsCompatibility: true, }); const res = await catalog.entities(); diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts index a7b8fdc6c9..33847ea87b 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -105,19 +105,19 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { private readonly database: Knex; private readonly logger: LoggerService; private readonly stitcher: Stitcher; - private readonly disableRelationsCompatibility: boolean; + private readonly enableRelationsCompatibility: boolean; constructor(options: { database: Knex; logger: LoggerService; stitcher: Stitcher; - disableRelationsCompatibility?: boolean; + enableRelationsCompatibility?: boolean; }) { this.database = options.database; this.logger = options.logger; this.stitcher = options.stitcher; - this.disableRelationsCompatibility = Boolean( - options.disableRelationsCompatibility, + this.enableRelationsCompatibility = Boolean( + options.enableRelationsCompatibility, ); } @@ -199,15 +199,15 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { return { entities: processRawEntitiesResult( rows.map(r => r.final_entity!), - this.disableRelationsCompatibility - ? request?.fields - : e => { + this.enableRelationsCompatibility + ? e => { expandLegacyCompoundRelationsInEntity(e); if (request?.fields) { return request.fields(e); } return e; - }, + } + : request?.fields, ), pageInfo, }; diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index b0dddbdef0..0246a3d56b 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -56,7 +56,7 @@ describe('createRouter readonly disabled', () => { let app: express.Express | Server; let refreshService: RefreshService; let locationAnalyzer: jest.Mocked; - const permissionsService = mockServices.permissions.mock(); + const permissionsService = mockServices.permissions(); beforeEach(async () => { entitiesCatalog = { @@ -136,38 +136,6 @@ describe('createRouter readonly disabled', () => { { apiVersion: 'a', kind: 'b', metadata: { name: 'n' } }, ]; - entitiesCatalog.entities.mockResolvedValueOnce({ - entities: { type: 'object', entities: [entities[0]] }, - pageInfo: { hasNextPage: false }, - }); - - const response = await request(app).get('/entities'); - - expect(response.status).toEqual(200); - expect(response.body).toEqual(entities); - }); - - it('happy path: lists entities when by-entities emulation is enabled', async () => { - const router = await createRouter({ - entitiesCatalog, - locationService, - orchestrator, - logger: mockServices.logger.mock(), - refreshService, - config: new ConfigReader(undefined), - permissionIntegrationRouter: express.Router(), - auth: mockServices.auth(), - httpAuth: mockServices.httpAuth(), - locationAnalyzer, - permissionsService, - disableRelationsCompatibility: true, // added - }); - app = await wrapServer(express().use(router)); - - const entities: Entity[] = [ - { apiVersion: 'a', kind: 'b', metadata: { name: 'n' } }, - ]; - entitiesCatalog.queryEntities.mockResolvedValueOnce({ items: { type: 'object', entities: [entities[0]] }, pageInfo: {}, @@ -180,34 +148,7 @@ describe('createRouter readonly disabled', () => { expect(response.body).toEqual(entities); }); - it('parses single and multiple request parameters and passes them down', async () => { - entitiesCatalog.entities.mockResolvedValueOnce({ - entities: { type: 'object', entities: [] }, - pageInfo: { hasNextPage: false }, - }); - const response = await request(app).get( - '/entities?filter=a=1,a=2,b=3&filter=c=4', - ); - - expect(response.status).toEqual(200); - expect(entitiesCatalog.entities).toHaveBeenCalledTimes(1); - expect(entitiesCatalog.entities).toHaveBeenCalledWith({ - filter: { - anyOf: [ - { - allOf: [ - { key: 'a', values: ['1', '2'] }, - { key: 'b', values: ['3'] }, - ], - }, - { key: 'c', values: ['4'] }, - ], - }, - credentials: mockCredentials.user(), - }); - }); - - it('parses single and multiple request parameters and passes them down when by-entities emulation is enabled', async () => { + it('happy path: lists entities with relations compat', async () => { const router = await createRouter({ entitiesCatalog, locationService, @@ -220,10 +161,26 @@ describe('createRouter readonly disabled', () => { httpAuth: mockServices.httpAuth(), locationAnalyzer, permissionsService, - disableRelationsCompatibility: true, // added + enableRelationsCompatibility: true, // added }); - app = await wrapServer(express().use(router)); + app = await wrapServer(express().use(router)); + const entities: Entity[] = [ + { apiVersion: 'a', kind: 'b', metadata: { name: 'n' } }, + ]; + + entitiesCatalog.entities.mockResolvedValueOnce({ + entities: { type: 'object', entities: [entities[0]] }, + pageInfo: { hasNextPage: false }, + }); + + const response = await request(app).get('/entities'); + + expect(response.status).toEqual(200); + expect(response.body).toEqual(entities); + }); + + it('parses single and multiple request parameters and passes them down', async () => { entitiesCatalog.queryEntities.mockResolvedValueOnce({ items: { type: 'object', entities: [] }, pageInfo: {}, @@ -252,6 +209,48 @@ describe('createRouter readonly disabled', () => { skipTotalItems: true, }); }); + + it('parses single and multiple request parameters and passes them down with relations compat', async () => { + const router = await createRouter({ + entitiesCatalog, + locationService, + orchestrator, + logger: mockServices.logger.mock(), + refreshService, + config: new ConfigReader(undefined), + permissionIntegrationRouter: express.Router(), + auth: mockServices.auth(), + httpAuth: mockServices.httpAuth(), + locationAnalyzer, + permissionsService, + enableRelationsCompatibility: true, + }); + app = await wrapServer(express().use(router)); + entitiesCatalog.entities.mockResolvedValueOnce({ + entities: { type: 'object', entities: [] }, + pageInfo: { hasNextPage: false }, + }); + const response = await request(app).get( + '/entities?filter=a=1,a=2,b=3&filter=c=4', + ); + + expect(response.status).toEqual(200); + expect(entitiesCatalog.entities).toHaveBeenCalledTimes(1); + expect(entitiesCatalog.entities).toHaveBeenCalledWith({ + filter: { + anyOf: [ + { + allOf: [ + { key: 'a', values: ['1', '2'] }, + { key: 'b', values: ['3'] }, + ], + }, + { key: 'c', values: ['4'] }, + ], + }, + credentials: mockCredentials.user(), + }); + }); }); describe('GET /entities/by-query', () => { @@ -799,12 +798,6 @@ describe('createRouter readonly disabled', () => { metadata: { name: 'n' }, }; - permissionsService.authorize.mockResolvedValueOnce([ - { - result: AuthorizeResult.ALLOW, - }, - ]); - orchestrator.process.mockResolvedValueOnce({ ok: true, state: {}, @@ -845,12 +838,6 @@ describe('createRouter readonly disabled', () => { metadata: { name: 'invalid*name' }, }; - permissionsService.authorize.mockResolvedValueOnce([ - { - result: AuthorizeResult.ALLOW, - }, - ]); - orchestrator.process.mockResolvedValueOnce({ ok: false, errors: [new Error('Invalid entity name')], @@ -888,12 +875,6 @@ describe('createRouter readonly disabled', () => { metadata: { name: 'n' }, }; - permissionsService.authorize.mockResolvedValueOnce([ - { - result: AuthorizeResult.ALLOW, - }, - ]); - const response = await request(app) .post('/validate-entity') .send({ entity, location: null }); @@ -940,7 +921,7 @@ describe('createRouter readonly and raw json enabled', () => { let entitiesCatalog: jest.Mocked; let app: express.Express; let locationService: jest.Mocked; - const permissionsService = mockServices.permissions.mock(); + const permissionsService = mockServices.permissions(); beforeAll(async () => { entitiesCatalog = { @@ -959,7 +940,6 @@ describe('createRouter readonly and raw json enabled', () => { getLocationByEntity: jest.fn(), }; const router = await createRouter({ - disableRelationsCompatibility: true, entitiesCatalog, locationService, logger: mockServices.logger.mock(), @@ -1144,7 +1124,7 @@ describe('NextRouter permissioning', () => { let locationService: jest.Mocked; let app: express.Express; let refreshService: RefreshService; - const permissionsService = mockServices.permissions.mock(); + const permissionsService = mockServices.permissions(); const fakeRule = createPermissionRule({ name: 'FAKE_RULE', diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index f4ce26b66b..9c9b97eb4c 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -81,7 +81,7 @@ export interface RouterOptions { permissionsService: PermissionsService; // TODO: Require AuditorService once `backend-legacy` is removed auditor?: AuditorService; - disableRelationsCompatibility?: boolean; + enableRelationsCompatibility?: boolean; } /** @@ -110,7 +110,7 @@ export async function createRouter( auth, httpAuth, auditor, - disableRelationsCompatibility = false, + enableRelationsCompatibility = false, } = options; const readonlyEnabled = @@ -179,7 +179,7 @@ export async function createRouter( // When pagination parameters are passed in, use the legacy slow path // that loads all entities into memory - if (pagination || disableRelationsCompatibility !== true) { + if (pagination || enableRelationsCompatibility === true) { const { entities, pageInfo } = await entitiesCatalog.entities({ filter, fields, @@ -204,7 +204,7 @@ export async function createRouter( await writeEntitiesResponse({ res, items: entities, - alwaysUseObjectMode: !disableRelationsCompatibility, + alwaysUseObjectMode: enableRelationsCompatibility, }); return; } @@ -214,7 +214,7 @@ export async function createRouter( let cursor: Cursor | undefined; try { - let currentWrite: Promise | undefined = undefined; + let currentWrite: Promise<'ok' | 'closed'> | undefined = undefined; do { const result = await entitiesCatalog.queryEntities( !cursor @@ -230,7 +230,7 @@ export async function createRouter( ); // Wait for previous write to complete - if (await currentWrite) { + if ((await currentWrite) === 'closed') { return; // Client closed connection } @@ -295,7 +295,7 @@ export async function createRouter( await writeEntitiesResponse({ res, items, - alwaysUseObjectMode: !disableRelationsCompatibility, + alwaysUseObjectMode: enableRelationsCompatibility, responseWrapper: entities => ({ items: entities, ...meta, @@ -482,7 +482,7 @@ export async function createRouter( await writeEntitiesResponse({ res, items, - alwaysUseObjectMode: !disableRelationsCompatibility, + alwaysUseObjectMode: enableRelationsCompatibility, responseWrapper: entities => ({ items: entities, }), diff --git a/plugins/catalog-backend/src/service/response/createEntityArrayJsonStream.ts b/plugins/catalog-backend/src/service/response/createEntityArrayJsonStream.ts index 5bdc9d8994..ac557f5a2f 100644 --- a/plugins/catalog-backend/src/service/response/createEntityArrayJsonStream.ts +++ b/plugins/catalog-backend/src/service/response/createEntityArrayJsonStream.ts @@ -16,10 +16,10 @@ import { EntitiesResponseItems } from '../../catalog/types'; import { Response } from 'express'; -import { writeResponseData } from './write'; +import { createResponseDataWriter } from './write'; export interface EntityArrayJsonStream { - send(entities: EntitiesResponseItems): Promise; + send(entities: EntitiesResponseItems): Promise<'ok' | 'closed'>; complete(): void; close(): void; } @@ -28,6 +28,8 @@ export interface EntityArrayJsonStream { export function createEntityArrayJsonStream( res: Response, ): EntityArrayJsonStream { + const writer = createResponseDataWriter(res); + // Imitate the httpRouter behavior of pretty-printing in development const prettyPrint = process.env.NODE_ENV === 'development'; let firstSend = true; @@ -46,11 +48,11 @@ export function createEntityArrayJsonStream( const prefix = firstSend ? '[' : ','; firstSend = false; - if (await writeResponseData(res, prefix + item)) { - return true; + if ((await writer(prefix + item)) === 'closed') { + return 'closed'; } } - return false; + return 'ok'; } let data: string; @@ -63,7 +65,7 @@ export function createEntityArrayJsonStream( } firstSend = false; - return writeResponseData(res, data); + return writer(data); }, complete() { if (firstSend) { diff --git a/plugins/catalog-backend/src/service/response/write.ts b/plugins/catalog-backend/src/service/response/write.ts index b43064a1e4..d6fa0a7dc9 100644 --- a/plugins/catalog-backend/src/service/response/write.ts +++ b/plugins/catalog-backend/src/service/response/write.ts @@ -16,7 +16,7 @@ import { Response } from 'express'; import { EntitiesResponseItems } from '../../catalog/types'; -import { JsonValue } from '@backstage/types'; +import { createDeferred, DeferredPromise, JsonValue } from '@backstage/types'; import { NotFoundError } from '@backstage/errors'; import { processEntitiesResponseItems } from './process'; @@ -50,6 +50,8 @@ export async function writeEntitiesResponse(options: { alwaysUseObjectMode?: boolean; }) { const { res, responseWrapper, alwaysUseObjectMode } = options; + const writer = createResponseDataWriter(res); + const items = alwaysUseObjectMode ? processEntitiesResponseItems(options.items, e => e) : options.items; @@ -83,7 +85,7 @@ export async function writeEntitiesResponse(options: { const prefix = first ? '[' : ','; first = false; - if (await writeResponseData(res, prefix + entity)) { + if ((await writer(prefix + entity)) === 'closed') { return; } } @@ -91,32 +93,55 @@ export async function writeEntitiesResponse(options: { } /** - * Writes a data to the response and waits if the response buffer needs draining. + * Creates a data writer that writes to the response and waits if the response + * buffer needs draining. * * @internal - * @returns true if the response was closed while waiting for the buffer to drain + * @returns A writer function. If a write attempt returns 'closed', the + * connection has become closed prematurely and the caller should stop trying to + * write. */ -export async function writeResponseData(res: Response, data: string | Buffer) { - const ok = res.write(data, 'utf8'); - if (!ok) { - if (res.closed) { - return true; +export function createResponseDataWriter( + res: Response, +): (data: string | Buffer) => Promise<'ok' | 'closed'> { + // See https://github.com/backstage/backstage/issues/30659 + // + // This code goes to some lengths to just add listeners once at the top, + // instead of on every need to drain. Hence it is more complex that seems to + // be necessary, just to avoid listener leaks. + + let drainPromise: DeferredPromise<'ok'> | undefined; + + const closePromise = new Promise<'closed'>(resolve => { + function onClose() { + res.off('drain', onDrain); + res.off('close', onClose); + resolve('closed'); } - const closed = await new Promise(resolve => { - function onContinue() { - res.off('drain', onContinue); - res.off('close', onClose); - resolve(false); - } - function onClose() { - res.off('drain', onContinue); - res.off('close', onClose); - resolve(true); - } - res.on('drain', onContinue); - res.on('close', onClose); - }); - return closed; - } - return false; + function onDrain() { + drainPromise?.resolve('ok'); + drainPromise = undefined; + } + res.on('drain', onDrain); + res.on('close', onClose); + }); + + return async data => { + if (drainPromise) { + throw new Error( + 'Attempted overlapping write while waiting for previous write to drain', + ); + } + + if (res.write(data, 'utf8')) { + return 'ok'; + } + + if (res.closed) { + return 'closed'; + } + + drainPromise = createDeferred(); + return Promise.race([drainPromise, closePromise]); + }; } diff --git a/plugins/catalog-backend/src/stitching/types.ts b/plugins/catalog-backend/src/stitching/types.ts index bbb6223968..7c8a5c7d65 100644 --- a/plugins/catalog-backend/src/stitching/types.ts +++ b/plugins/catalog-backend/src/stitching/types.ts @@ -61,11 +61,11 @@ export function stitchingStrategyFromConfig(config: Config): StitchingStrategy { 'catalog.stitchingStrategy.mode', ); - if (strategyMode === undefined || strategyMode === 'immediate') { + if (strategyMode === 'immediate') { return { mode: 'immediate', }; - } else if (strategyMode === 'deferred') { + } else if (strategyMode === undefined || strategyMode === 'deferred') { const pollingIntervalKey = 'catalog.stitchingStrategy.pollingInterval'; const stitchTimeoutKey = 'catalog.stitchingStrategy.stitchTimeout'; diff --git a/plugins/catalog-backend/src/tests/integration.test.ts b/plugins/catalog-backend/src/tests/integration.test.ts index 400bbc7538..719c5e6ccf 100644 --- a/plugins/catalog-backend/src/tests/integration.test.ts +++ b/plugins/catalog-backend/src/tests/integration.test.ts @@ -205,7 +205,7 @@ class TestHarness { readonly #db: Knex; static async create(options: { - disableRelationsCompatibility?: boolean; + enableRelationsCompatibility?: boolean; logger?: LoggerService; db: Knex; permissions?: PermissionEvaluator; @@ -283,7 +283,7 @@ class TestHarness { database: options.db, logger, stitcher, - disableRelationsCompatibility: options?.disableRelationsCompatibility, + enableRelationsCompatibility: options?.enableRelationsCompatibility, }); const proxyProgressTracker = new ProxyProgressTracker( new NoopProgressTracker(), @@ -863,7 +863,6 @@ describe('Catalog Backend Integration', () => { it('should return valid responses in raw JSON mode', async () => { const harness = await TestHarness.create({ db: await databases.init('SQLITE_3'), - disableRelationsCompatibility: true, }); const entityA = { 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-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index f859e1d0f9..a0cdac70e8 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-catalog-common +## 1.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-search-common@1.2.19 + +## 1.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## 1.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-search-common@1.2.18 + ## 1.1.4-next.0 ### Patch Changes diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index 3cf4c885a9..7ce4e23d55 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-common", - "version": "1.1.4-next.0", + "version": "1.1.5", "description": "Common functionalities for the catalog plugin", "backstage": { "role": "common-library", diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 7f669774f8..a818bf1b33 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,179 @@ # @backstage/plugin-catalog-graph +## 0.4.22-next.3 + +### Patch Changes + +- 8b1bf6e: Updated README instructions for the new frontend system +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.2 + - @backstage/core-compat-api@0.5.0-next.3 + - @backstage/core-components@0.17.5-next.2 + +## 0.4.22-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/types@1.2.1 + +## 0.4.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/types@1.2.1 + +## 0.4.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.4.21 + +### Patch Changes + +- fe1a2f4: Catalog graph plugin support i18n +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.4.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 0.4.21-next.0 + +### Patch Changes + +- fe1a2f4: Catalog graph plugin support i18n +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + +## 0.4.20 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3 + - @backstage/types@1.2.1 + +## 0.4.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/types@1.2.1 + +## 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 + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 72d019d: Removed various typos +- 1307f00: Fix rendering of missing relations +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/types@1.2.1 + +## 0.4.19-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/types@1.2.1 + ## 0.4.19-next.2 ### Patch Changes diff --git a/plugins/catalog-graph/README-alpha.md b/plugins/catalog-graph/README-alpha.md index bab9a26fd1..bc5cea3fd1 100644 --- a/plugins/catalog-graph/README-alpha.md +++ b/plugins/catalog-graph/README-alpha.md @@ -36,18 +36,18 @@ This plugin installation requires the following steps: 1. Add the `@backstage/catalog-graph` dependency to your app `package.json` file and install it; 2. In your application's configuration file, enable the catalog entity relations graphic card extension so that the card begins to be presented on the catalog entity page: -```yaml -# app-config.yaml -app: - experimental: - # Auto discovering all plugins extensions - packages: all - extensions: - # This is required because the card is not enable by default once you install the plugin - - entity-card:catalog-graph/relations -``` + ```yaml + # app-config.yaml + app: + # Auto discovering all plugins extensions + packages: all + extensions: + # This is required because the card is not enable by default once you install the plugin + - entity-card:catalog-graph/relations + ``` 3. Then start the app, navigate to an entity's page and see the Relations graph there; + 4. By clicking on the "View Graph" card action, you will be redirected to the catalog entity relations page. ## Customization @@ -64,11 +64,10 @@ _Enabling auto discovering the plugin extensions in production_ # app-config.production.yaml # Overriding configurations for the local production environment app: - experimental: - packages: - # Only the following packages will be included - include: - - '@backstage/plugin-catalog-graph' + packages: + # Only the following packages will be included + include: + - '@backstage/plugin-catalog-graph' ``` _Disabling auto discovering the plugin extensions in development_ @@ -77,11 +76,10 @@ _Disabling auto discovering the plugin extensions in development_ # app-config.local.yaml # Overriding configurations for the local development environment app: - experimental: - packages: - # All but the following package will be included - exclude: - - '@backstage/plugin-catalog-graph' + packages: + # All but the following package will be included + exclude: + - '@backstage/plugin-catalog-graph' ``` For more options of package configurations, see [this](https://backstage.io/docs/frontend-system/architecture/app/#feature-discovery) documentation. @@ -306,7 +304,7 @@ export default createFrontendModule({ extensions: [ createPageExtension({ // Omitting name since it is an index page - defaultPath: '/catalog-graph', + path: '/catalog-graph', routeRef: convertLegacyRouteRef(catalogGraphRouteRef), createSchemaFromZod(z => z.object({ path: z.string().default('/catalog-graph') diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index d674ca6f95..729b377d3e 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-next.2", + "version": "0.4.22-next.3", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-graph", diff --git a/plugins/catalog-graph/report-alpha.api.md b/plugins/catalog-graph/report-alpha.api.md index cf5835e35c..729be9006b 100644 --- a/plugins/catalog-graph/report-alpha.api.md +++ b/plugins/catalog-graph/report-alpha.api.md @@ -3,19 +3,47 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Direction } from '@backstage/plugin-catalog-graph'; import { Entity } from '@backstage/catalog-model'; import { EntityCardType } from '@backstage/plugin-catalog-react/alpha'; import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; 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 { RouteRef } from '@backstage/frontend-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; + +// @alpha (undocumented) +export const catalogGraphTranslationRef: TranslationRef< + 'catalog-graph', + { + readonly 'catalogGraphCard.title': 'Relations'; + readonly 'catalogGraphCard.deepLinkTitle': 'View graph'; + readonly 'catalogGraphPage.title': 'Catalog Graph'; + readonly 'catalogGraphPage.filterToggleButtonTitle': 'Filters'; + readonly 'catalogGraphPage.supportButtonDescription': 'Start tracking your component in by adding it to the software catalog.'; + readonly 'catalogGraphPage.simplifiedSwitchLabel': 'Simplified'; + readonly 'catalogGraphPage.mergeRelationsSwitchLabel': 'Merge relations'; + readonly 'catalogGraphPage.zoomOutDescription': 'Use pinch & zoom to move around the diagram. Click to change active node, shift click to navigate to entity.'; + readonly 'catalogGraphPage.curveFilter.title': 'Curve'; + readonly 'catalogGraphPage.curveFilter.curveStepBefore': 'Step Before'; + readonly 'catalogGraphPage.curveFilter.curveMonotoneX': 'Monotone X'; + readonly 'catalogGraphPage.directionFilter.title': 'Direction'; + readonly 'catalogGraphPage.directionFilter.leftToRight': 'Left to right'; + readonly 'catalogGraphPage.directionFilter.rightToLeft': 'Right to left'; + readonly 'catalogGraphPage.directionFilter.topToBottom': 'Top to bottom'; + readonly 'catalogGraphPage.directionFilter.bottomToTop': 'Bottom to top'; + readonly 'catalogGraphPage.maxDepthFilter.title': 'Max depth'; + readonly 'catalogGraphPage.maxDepthFilter.inputPlaceholder': '∞ Infinite'; + readonly 'catalogGraphPage.maxDepthFilter.clearButtonAriaLabel': 'clear max depth'; + readonly 'catalogGraphPage.selectedKindsFilter.title': 'Kinds'; + readonly 'catalogGraphPage.selectedRelationsFilter.title': 'Relations'; + } +>; // @public (undocumented) const _default: FrontendPlugin< @@ -64,22 +92,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -88,7 +116,7 @@ const _default: FrontendPlugin< >; inputs: { [x: string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; @@ -139,9 +167,9 @@ const _default: FrontendPlugin< path?: string | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { @@ -150,7 +178,7 @@ const _default: FrontendPlugin< >; inputs: { [x: string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; @@ -160,7 +188,8 @@ const _default: FrontendPlugin< kind: 'page'; name: undefined; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise; routeRef?: RouteRef; }; diff --git a/plugins/catalog-graph/src/alpha.tsx b/plugins/catalog-graph/src/alpha.tsx index d70232e92a..5fc43f1b4a 100644 --- a/plugins/catalog-graph/src/alpha.tsx +++ b/plugins/catalog-graph/src/alpha.tsx @@ -75,7 +75,7 @@ const CatalogGraphPage = PageBlueprint.makeWithOverrides({ }, factory(originalFactory, { config }) { return originalFactory({ - defaultPath: '/catalog-graph', + path: '/catalog-graph', routeRef: convertLegacyRouteRef(catalogGraphRouteRef), loader: () => import('./components/CatalogGraphPage').then(m => @@ -87,6 +87,7 @@ const CatalogGraphPage = PageBlueprint.makeWithOverrides({ export default createFrontendPlugin({ pluginId: 'catalog-graph', + info: { packageJson: () => import('../package.json') }, routes: { catalogGraph: convertLegacyRouteRef(catalogGraphRouteRef), }, @@ -95,3 +96,5 @@ export default createFrontendPlugin({ }, extensions: [CatalogGraphPage, CatalogGraphEntityCard], }); + +export { catalogGraphTranslationRef } from './translation'; diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx index e09d57bd31..ed8069a8f4 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx @@ -34,6 +34,7 @@ import userEvent from '@testing-library/user-event'; import { catalogGraphRouteRef } from '../../routes'; import { CatalogGraphCard } from './CatalogGraphCard'; import Button from '@material-ui/core/Button'; +import { translationApiRef } from '@backstage/core-plugin-api/alpha'; describe('', () => { let entity: Entity; @@ -52,7 +53,10 @@ describe('', () => { namespace: 'd', }, }; - apis = TestApiRegistry.from([catalogApiRef, catalog]); + apis = TestApiRegistry.from( + [catalogApiRef, catalog], + [translationApiRef, mockApis.translation()], + ); wrapper = ( @@ -213,7 +217,12 @@ describe('', () => { const analyticsApi = mockApis.analytics(); await renderInTestApp( - + {wrapper} , { diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx index a75993a1bc..733f209990 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx @@ -38,6 +38,8 @@ import { EntityRelationsGraph, EntityRelationsGraphProps, } from '../EntityRelationsGraph'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { catalogGraphTranslationRef } from '../../translation'; /** @public */ export type CatalogGraphCardClassKey = 'card' | 'graph'; @@ -66,6 +68,7 @@ export const CatalogGraphCard = ( action?: ReactNode; }, ) => { + const { t } = useTranslationRef(catalogGraphTranslationRef); const { variant = 'gridItem', relationPairs = ALL_RELATION_PAIRS, @@ -81,7 +84,7 @@ export const CatalogGraphCard = ( action, rootEntityNames, onNodeClick, - title = 'Relations', + title = t('catalogGraphCard.title'), zoom = 'enable-on-click', } = props; @@ -133,7 +136,7 @@ export const CatalogGraphCard = ( variant={variant} noPadding deepLink={{ - title: 'View graph', + title: t('catalogGraphCard.deepLinkTitle'), link: catalogGraphUrl, }} > diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx index cf3e628a98..69cb04bf3a 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx @@ -50,6 +50,8 @@ import { SelectedKindsFilter } from './SelectedKindsFilter'; import { SelectedRelationsFilter } from './SelectedRelationsFilter'; import { SwitchFilter } from './SwitchFilter'; import { useCatalogGraphPage } from './useCatalogGraphPage'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { catalogGraphTranslationRef } from '../../translation'; /** @public */ export type CatalogGraphPageClassKey = @@ -136,7 +138,7 @@ export const CatalogGraphPage = ( initialState, entityFilter, } = props; - + const { t } = useTranslationRef(catalogGraphTranslationRef); const navigate = useNavigate(); const classes = useStyles(); const catalogEntityRoute = useRouteRef(entityRouteRef); @@ -192,7 +194,7 @@ export const CatalogGraphPage = ( return (
    humanizeEntityRef(e)).join(', ')} /> @@ -203,13 +205,12 @@ export const CatalogGraphPage = ( selected={showFilters} onChange={() => toggleShowFilters()} > - Filters + {t('catalogGraphPage.filterToggleButtonTitle')} } > - Start tracking your component in by adding it to the software - catalog. + {t('catalogGraphPage.supportButtonDescription')} @@ -230,12 +231,12 @@ export const CatalogGraphPage = ( )} @@ -247,9 +248,8 @@ export const CatalogGraphPage = ( display="block" className={classes.legend} > - Use pinch & zoom to move - around the diagram. Click to change active node, shift click to - navigate to entity. + {' '} + {t('catalogGraphPage.zoomOutDescription')} ', () => { - test('should display current curve label', () => { + test('should display current curve label', async () => { const onChange = jest.fn(); - render(); + await renderInTestApp( + , + ); expect(screen.getByText('Monotone X')).toBeInTheDocument(); }); test('should select an alternative curve factory', async () => { const onChange = jest.fn(); - render(); + await renderInTestApp( + , + ); expect(screen.getByText('Step Before')).toBeInTheDocument(); diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx index 411c144b05..0e6a941b1a 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx @@ -16,12 +16,10 @@ import { Select, SelectedItems } from '@backstage/core-components'; import Box from '@material-ui/core/Box'; import { useCallback } from 'react'; +import { catalogGraphTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; type Curve = 'curveStepBefore' | 'curveMonotoneX'; -const CURVE_DISPLAY_NAMES: Record = { - curveMonotoneX: 'Monotone X', - curveStepBefore: 'Step Before', -}; export type Props = { value: Curve; @@ -31,6 +29,12 @@ export type Props = { const curves: Array = ['curveMonotoneX', 'curveStepBefore']; export const CurveFilter = ({ value, onChange }: Props) => { + const { t } = useTranslationRef(catalogGraphTranslationRef); + const CURVE_DISPLAY_NAMES: Record = { + curveMonotoneX: t('catalogGraphPage.curveFilter.curveMonotoneX'), + curveStepBefore: t('catalogGraphPage.curveFilter.curveStepBefore'), + }; + const handleChange = useCallback( (v: SelectedItems) => onChange(v as Curve), [onChange], @@ -39,7 +43,7 @@ export const CurveFilter = ({ value, onChange }: Props) => { return ( ({ label: DIRECTION_DISPLAY_NAMES[v], diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/MaxDepthFilter.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/MaxDepthFilter.test.tsx index ef3df7eb53..2e4d5d44a3 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/MaxDepthFilter.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/MaxDepthFilter.test.tsx @@ -14,20 +14,21 @@ * limitations under the License. */ -import { render, screen } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import user from '@testing-library/user-event'; import { MaxDepthFilter } from './MaxDepthFilter'; +import { renderInTestApp } from '@backstage/test-utils'; describe('', () => { - test('should display current value', () => { - render( {}} />); + test('should display current value', async () => { + await renderInTestApp( {}} />); expect(screen.getByLabelText('maxp')).toBeInTheDocument(); expect(screen.getByLabelText('maxp')).toHaveValue(5); }); - test('should display infinite if non finite', () => { - render( + test('should display infinite if non finite', async () => { + await renderInTestApp( {}} />, ); @@ -37,7 +38,7 @@ describe('', () => { test('should clear max depth', async () => { const onChange = jest.fn(); - render(); + await renderInTestApp(); expect(onChange).not.toHaveBeenCalled(); await user.click(screen.getByLabelText('clear max depth')); @@ -46,7 +47,7 @@ describe('', () => { test('should set max depth to undefined if below one', async () => { const onChange = jest.fn(); - render(); + await renderInTestApp(); await user.clear(screen.getByLabelText('maxp')); await user.type(screen.getByLabelText('maxp'), '0'); @@ -56,7 +57,7 @@ describe('', () => { test('should select direction', async () => { let value = 5; - render( + await renderInTestApp( { diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/MaxDepthFilter.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/MaxDepthFilter.tsx index 33742ad72e..cdd27b9adb 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/MaxDepthFilter.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/MaxDepthFilter.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Box from '@material-ui/core/Box'; import FormControl from '@material-ui/core/FormControl'; import IconButton from '@material-ui/core/IconButton'; @@ -22,6 +23,7 @@ import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import ClearIcon from '@material-ui/icons/Clear'; import { ChangeEvent, useCallback, useEffect, useRef, useState } from 'react'; +import { catalogGraphTranslationRef } from '../../translation'; export type Props = { value: number; @@ -45,6 +47,7 @@ export const MaxDepthFilter = ({ value, onChange }: Props) => { const classes = useStyles(); const onChangeRef = useRef(onChange); const [currentValue, setCurrentValue] = useState(value); + const { t } = useTranslationRef(catalogGraphTranslationRef); // Keep a fresh reference to the latest callback useEffect(() => { @@ -75,16 +78,20 @@ export const MaxDepthFilter = ({ value, onChange }: Props) => { return ( - Max Depth + + {t('catalogGraphPage.maxDepthFilter.title')} + diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.test.tsx index 929b22755d..4349d7005a 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.test.tsx @@ -15,13 +15,18 @@ */ import { ApiProvider } from '@backstage/core-app-api'; -import { AlertApi, alertApiRef } from '@backstage/core-plugin-api'; +import { AlertApi, alertApiRef, errorApiRef } from '@backstage/core-plugin-api'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils'; +import { + mockApis, + renderWithEffects, + TestApiRegistry, +} from '@backstage/test-utils'; import { waitFor, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { SelectedKindsFilter } from './SelectedKindsFilter'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; +import { translationApiRef } from '@backstage/core-plugin-api/alpha'; const catalogApi = catalogApiMock.mock({ getEntityFacets: jest.fn().mockResolvedValue({ @@ -38,6 +43,8 @@ const catalogApi = catalogApiMock.mock({ const apis = TestApiRegistry.from( [catalogApiRef, catalogApi], [alertApiRef, {} as AlertApi], + [translationApiRef, mockApis.translation()], + [errorApiRef, { post: jest.fn() }], ); describe('', () => { diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.tsx index 4970c4527a..d9a5ef9833 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.tsx @@ -27,6 +27,8 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import Autocomplete from '@material-ui/lab/Autocomplete'; import { useCallback, useEffect, useMemo } from 'react'; import useAsync from 'react-use/esm/useAsync'; +import { catalogGraphTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; /** @public */ export type SelectedKindsFilterClassKey = 'formControl'; @@ -49,6 +51,7 @@ export const SelectedKindsFilter = ({ value, onChange }: Props) => { const classes = useStyles(); const alertApi = useApi(alertApiRef); const catalogApi = useApi(catalogApiRef); + const { t } = useTranslationRef(catalogGraphTranslationRef); const { error, value: kinds } = useAsync(async () => { return await catalogApi @@ -91,13 +94,15 @@ export const SelectedKindsFilter = ({ value, onChange }: Props) => { return ( - Kinds + + {t('catalogGraphPage.selectedKindsFilter.title')} + kinds[normalizedKinds.indexOf(k)] ?? k} diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedRelationsFilter.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedRelationsFilter.test.tsx index 41df29d072..057842bcdc 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedRelationsFilter.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedRelationsFilter.test.tsx @@ -19,14 +19,15 @@ import { RELATION_HAS_MEMBER, RELATION_OWNED_BY, } from '@backstage/catalog-model'; -import { render, waitFor, screen } from '@testing-library/react'; +import { waitFor, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ALL_RELATION_PAIRS } from '../EntityRelationsGraph'; import { SelectedRelationsFilter } from './SelectedRelationsFilter'; +import { renderInTestApp } from '@backstage/test-utils'; describe('', () => { - test('should render current value', () => { - render( + test('should render current value', async () => { + await renderInTestApp( ', () => { test('should select value', async () => { const onChange = jest.fn(); - render( + await renderInTestApp( ', () => { test('should return undefined if all values are selected', async () => { const onChange = jest.fn(); - render( + await renderInTestApp( p).filter( @@ -92,7 +93,7 @@ describe('', () => { test('should return all values when cleared', async () => { const onChange = jest.fn(); - render( + await renderInTestApp( { const classes = useStyles(); const relations = useMemo(() => relationPairs.flat(), [relationPairs]); + const { t } = useTranslationRef(catalogGraphTranslationRef); const handleChange = useCallback( (_: unknown, v: string[]) => { @@ -65,13 +68,15 @@ export const SelectedRelationsFilter = ({ return ( - Relations + + {t('catalogGraphPage.selectedRelationsFilter.title')} + >; +/* + This is the full test graph: + - b:d/c -> k:d/a1 (ownerOf) + - b:d/c -> b:d/c1 (hasPart) + - k:d/a1 -> b:d/c (ownedBy) + - k:d/a1 -> b:d/c1 (ownedBy) + - b:d/c1 -> b:d/c (partOf) + - b:d/c1 -> k:d/a1 (ownerOf) + - b:d/c1 -> b:d/c2 (hasPart) + - b:d/c2 -> b:d/c1 (partOf) +*/ const entities: { [ref: string]: Entity } = { 'b:d/c': { apiVersion: 'a', @@ -233,6 +244,12 @@ describe('useEntityRelationNodesAndEdges', () => { relations: [RELATION_HAS_PART, RELATION_PART_OF], to: 'b:d/c1', }, + { + from: 'b:d/c1', + label: 'visible', + relations: [RELATION_OWNER_OF, RELATION_OWNED_BY], + to: 'k:d/a1', + }, { from: 'b:d/c1', label: 'visible', @@ -302,12 +319,42 @@ describe('useEntityRelationNodesAndEdges', () => { relations: [RELATION_HAS_PART], to: 'b:d/c1', }, + { + from: 'b:d/c1', + label: 'visible', + relations: [RELATION_PART_OF], + to: 'b:d/c', + }, + { + from: 'b:d/c1', + label: 'visible', + relations: [RELATION_OWNER_OF], + to: 'k:d/a1', + }, { from: 'b:d/c1', label: 'visible', relations: [RELATION_HAS_PART], to: 'b:d/c2', }, + { + from: 'b:d/c2', + label: 'visible', + relations: [RELATION_PART_OF], + to: 'b:d/c1', + }, + { + from: 'k:d/a1', + label: 'visible', + relations: [RELATION_OWNED_BY], + to: 'b:d/c', + }, + { + from: 'k:d/a1', + label: 'visible', + relations: [RELATION_OWNED_BY], + to: 'b:d/c1', + }, ]); }); @@ -549,6 +596,12 @@ describe('useEntityRelationNodesAndEdges', () => { relations: ['ownerOf', 'ownedBy'], to: 'k:d/a1', }, + { + from: 'b:d/c', + label: 'visible', + relations: ['ownerOf', 'ownedBy'], + to: 'k:d/a1', + }, ]); }); diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts index 31398a137e..19a6dbe566 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts @@ -96,6 +96,21 @@ export function useEntityRelationNodesAndEdges({ const visitedNodes = new Set(); const nodeQueue = [...rootEntityRefs]; + const hasEdge = ( + fromRef: string, + toRef: string, + relation: [string, string?], + ): boolean => { + return edges.some( + edge => + fromRef === edge.from && + toRef === edge.to && + edge.relations.some( + rel => rel[0] === relation[0] && rel[1] === relation[1], + ), + ); + }; + while (nodeQueue.length > 0) { const entityRef = nodeQueue.pop()!; const entity = entities[entityRef]; @@ -122,20 +137,26 @@ export function useEntityRelationNodesAndEdges({ return; } - if (!unidirectional || !visitedNodes.has(rel.targetRef)) { - if (mergeRelations) { - const pair = relationPairs.find( - ([l, r]) => l === rel.type || r === rel.type, - ) ?? [rel.type]; - const [left] = pair; - + if (mergeRelations) { + const pair = relationPairs.find( + ([l, r]) => l === rel.type || r === rel.type, + ) ?? [rel.type]; + const [left] = pair; + const from = left === rel.type ? entityRef : rel.targetRef; + const to = left === rel.type ? rel.targetRef : entityRef; + if (!unidirectional || !hasEdge(from, to, pair)) { edges.push({ - from: left === rel.type ? entityRef : rel.targetRef, - to: left === rel.type ? rel.targetRef : entityRef, + from, + to, relations: pair, label: 'visible', }); - } else { + } + } else { + if ( + !unidirectional || + !hasEdge(entityRef, rel.targetRef, [rel.type]) + ) { edges.push({ from: entityRef, to: rel.targetRef, diff --git a/plugins/catalog-graph/src/translation.ts b/plugins/catalog-graph/src/translation.ts new file mode 100644 index 0000000000..33c4ac2d3a --- /dev/null +++ b/plugins/catalog-graph/src/translation.ts @@ -0,0 +1,60 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createTranslationRef } from '@backstage/frontend-plugin-api'; + +/** @alpha */ +export const catalogGraphTranslationRef = createTranslationRef({ + id: 'catalog-graph', + messages: { + catalogGraphCard: { + title: 'Relations', + deepLinkTitle: 'View graph', + }, + catalogGraphPage: { + title: 'Catalog Graph', + filterToggleButtonTitle: 'Filters', + supportButtonDescription: + 'Start tracking your component in by adding it to the software catalog.', + simplifiedSwitchLabel: 'Simplified', + mergeRelationsSwitchLabel: 'Merge relations', + zoomOutDescription: + 'Use pinch & zoom to move around the diagram. Click to change active node, shift click to navigate to entity.', + curveFilter: { + title: 'Curve', + curveMonotoneX: 'Monotone X', + curveStepBefore: 'Step Before', + }, + directionFilter: { + title: 'Direction', + leftToRight: 'Left to right', + rightToLeft: 'Right to left', + topToBottom: 'Top to bottom', + bottomToTop: 'Bottom to top', + }, + maxDepthFilter: { + title: 'Max depth', + inputPlaceholder: '∞ Infinite', + clearButtonAriaLabel: 'clear max depth', + }, + selectedKindsFilter: { + title: 'Kinds', + }, + selectedRelationsFilter: { + title: 'Relations', + }, + }, + }, +}); diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 3722f55f86..87b81b3706 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,217 @@ # @backstage/plugin-catalog-import +## 0.13.4-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.13.4-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.13.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/integration-react@1.2.9 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.13.3 + +### Patch Changes + +- 406b8b8: Fixed bug with error message since ResponseError is now thrown from CatalogClient +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.13.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 0.13.2-next.0 + +### Patch Changes + +- 406b8b8: Fixed bug with error message since ResponseError is now thrown from CatalogClient +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.13.1 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- 5d7c539: Catalog import plugin full support i18n +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/integration-react@1.2.8 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.13.1-next.2 + +### Patch Changes + +- 5d7c539: Catalog import plugin full support i18n +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + +## 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 + +- e2fd549: **BREAKING**: `generateStepper` and `defaultGenerateStepper` now require a translation argument to be passed through for supporting translations. + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 66a1140: Add i18n support for `catalog-import` plugin. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/integration@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.13.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + ## 0.13.0-next.2 ### Minor Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index cb046ecfb4..07f47ac856 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-next.2", + "version": "0.13.4-next.2", "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..43e62aecac 100644 --- a/plugins/catalog-import/report-alpha.api.md +++ b/plugins/catalog-import/report-alpha.api.md @@ -5,7 +5,9 @@ ```ts import { AnyApiFactory } from '@backstage/core-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/core-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; @@ -26,6 +28,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 +69,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'; @@ -94,15 +99,15 @@ const _default: FrontendPlugin< name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'page:catalog-import': ExtensionDefinition<{ kind: 'page'; @@ -114,9 +119,9 @@ const _default: FrontendPlugin< path?: string | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { @@ -125,7 +130,8 @@ const _default: FrontendPlugin< >; inputs: {}; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise; routeRef?: RouteRef; }; diff --git a/plugins/catalog-import/src/alpha.tsx b/plugins/catalog-import/src/alpha.tsx index 3fade2d92b..ab9d06ef19 100644 --- a/plugins/catalog-import/src/alpha.tsx +++ b/plugins/catalog-import/src/alpha.tsx @@ -16,7 +16,6 @@ import { configApiRef, - createApiFactory, discoveryApiRef, fetchApiRef, } from '@backstage/core-plugin-api'; @@ -43,7 +42,7 @@ export * from './translation'; // whether this type of override is typically done with an input or by overriding the entire extension. const catalogImportPage = PageBlueprint.make({ params: { - defaultPath: '/catalog-import', + path: '/catalog-import', routeRef: convertLegacyRouteRef(rootRouteRef), loader: () => import('./components/ImportPage').then(m => @@ -53,8 +52,8 @@ const catalogImportPage = PageBlueprint.make({ }); const catalogImportApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: catalogImportApiRef, deps: { discoveryApi: discoveryApiRef, @@ -81,12 +80,12 @@ const catalogImportApi = ApiBlueprint.make({ configApi, }), }), - }, }); /** @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/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx index ed6fb5683f..e351acb28a 100644 --- a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx +++ b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx @@ -19,8 +19,8 @@ import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { act, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ReactNode } from 'react'; -import { AnalyzeResult, catalogImportApiRef } from '../../api/'; import { StepInitAnalyzeUrl } from './StepInitAnalyzeUrl'; +import { AnalyzeResult, catalogImportApiRef } from '../../api/CatalogImportApi'; describe('', () => { const catalogImportApi: jest.Mocked = { diff --git a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx index 5a6cad055c..2100d3da81 100644 --- a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx +++ b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx @@ -130,7 +130,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => { } } } catch (e: any) { - setError(e?.data?.error?.message ?? e.message); + setError(e?.body?.error?.message ?? e.message); setSubmitted(false); } }, 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 ebffdcd296..85cec59713 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,117 @@ # @backstage/plugin-catalog-node +## 1.18.0-next.0 + +### Minor Changes + +- 3f4da39: Added the analyze-location endpoint to the CatalogService + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-common@0.9.1 + +## 1.17.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + +## 1.17.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + +## 1.17.1 + +### Patch Changes + +- 0169b23: Internal tweak to avoid circular dependencies +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +## 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 + +- a459f17: Added `parseEntityYaml` from `@backstage/plugin-catalog-backend`, to make it more easily usable by custom plugins and modules +- 1a003ff: Add `getLocations` method to `CatalogApi` and `CatalogClient`. This method calls the [`GET /locations`](https://backstage.io/docs/features/software-catalog/software-catalog-api/#get-locations) endpoint from the catalog backend. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/catalog-client@1.10.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + +## 1.17.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + ## 1.17.0-next.1 ### Minor Changes diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index a222e0093d..d83c44d1c9 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-next.1", + "version": "1.18.0-next.0", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", "backstage": { "role": "node-library", diff --git a/plugins/catalog-node/report-testUtils.api.md b/plugins/catalog-node/report-testUtils.api.md index 3098888fcb..3fa7057a99 100644 --- a/plugins/catalog-node/report-testUtils.api.md +++ b/plugins/catalog-node/report-testUtils.api.md @@ -5,6 +5,8 @@ ```ts import { AddLocationRequest } from '@backstage/catalog-client'; import { AddLocationResponse } from '@backstage/catalog-client'; +import { AnalyzeLocationRequest } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-common'; import { CatalogApi } from '@backstage/catalog-client'; import { CatalogRequestOptions } from '@backstage/catalog-client'; import { CatalogService } from '@backstage/plugin-catalog-node'; @@ -35,6 +37,11 @@ export interface CatalogServiceMock extends CatalogService, CatalogApi { options?: CatalogServiceRequestOptions | CatalogRequestOptions, ): Promise; // (undocumented) + analyzeLocation( + location: AnalyzeLocationRequest, + options?: CatalogServiceRequestOptions | CatalogRequestOptions, + ): Promise; + // (undocumented) getEntities( request?: GetEntitiesRequest, options?: CatalogServiceRequestOptions | CatalogRequestOptions, diff --git a/plugins/catalog-node/report.api.md b/plugins/catalog-node/report.api.md index 807bcaa8d5..debafad5c4 100644 --- a/plugins/catalog-node/report.api.md +++ b/plugins/catalog-node/report.api.md @@ -125,6 +125,11 @@ export interface CatalogService { options: CatalogServiceRequestOptions, ): Promise; // (undocumented) + analyzeLocation( + location: AnalyzeLocationRequest, + options: CatalogServiceRequestOptions, + ): Promise; + // (undocumented) getEntities( request: GetEntitiesRequest | undefined, options: CatalogServiceRequestOptions, diff --git a/plugins/catalog-node/src/api/provider.ts b/plugins/catalog-node/src/api/provider.ts index a576f32022..63f3f0bd09 100644 --- a/plugins/catalog-node/src/api/provider.ts +++ b/plugins/catalog-node/src/api/provider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { DeferredEntity } from '../processing'; +import { DeferredEntity } from '../processing/types'; /** * A 'full' mutation replaces all existing entities created by this entity provider with new ones. diff --git a/plugins/catalog-node/src/catalogService.ts b/plugins/catalog-node/src/catalogService.ts index 51bb1d92e5..9478c6b48a 100644 --- a/plugins/catalog-node/src/catalogService.ts +++ b/plugins/catalog-node/src/catalogService.ts @@ -42,6 +42,10 @@ import { ValidateEntityResponse, } from '@backstage/catalog-client'; import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; +import { + AnalyzeLocationRequest, + AnalyzeLocationResponse, +} from '@backstage/plugin-catalog-common'; /** * @public @@ -132,6 +136,11 @@ export interface CatalogService { locationRef: string, options: CatalogServiceRequestOptions, ): Promise; + + analyzeLocation( + location: AnalyzeLocationRequest, + options: CatalogServiceRequestOptions, + ): Promise; } class DefaultCatalogService implements CatalogService { @@ -301,6 +310,16 @@ class DefaultCatalogService implements CatalogService { ); } + async analyzeLocation( + location: AnalyzeLocationRequest, + options: CatalogServiceRequestOptions, + ): Promise { + return this.#catalogApi.analyzeLocation( + location, + await this.#getOptions(options), + ); + } + async #getOptions( options: CatalogServiceRequestOptions, ): Promise { diff --git a/plugins/catalog-node/src/processing/types.ts b/plugins/catalog-node/src/processing/types.ts index 625effc4df..d4255c1c51 100644 --- a/plugins/catalog-node/src/processing/types.ts +++ b/plugins/catalog-node/src/processing/types.ts @@ -21,7 +21,7 @@ import { AnalyzeLocationResponse, } from '@backstage/plugin-catalog-common'; import { JsonValue } from '@backstage/types'; -import { CatalogProcessorEmit } from '../api'; +import { CatalogProcessorEmit } from '../api/processor'; import { BackstageCredentials } from '@backstage/backend-plugin-api'; /** diff --git a/plugins/catalog-node/src/testUtils/catalogServiceMock.ts b/plugins/catalog-node/src/testUtils/catalogServiceMock.ts index ec469fc927..20e04e12b4 100644 --- a/plugins/catalog-node/src/testUtils/catalogServiceMock.ts +++ b/plugins/catalog-node/src/testUtils/catalogServiceMock.ts @@ -102,5 +102,6 @@ export namespace catalogServiceMock { removeLocationById: jest.fn(), getLocationByEntity: jest.fn(), validateEntity: jest.fn(), + analyzeLocation: jest.fn(), })); } diff --git a/plugins/catalog-node/src/testUtils/types.ts b/plugins/catalog-node/src/testUtils/types.ts index 6e6331a630..96380e9434 100644 --- a/plugins/catalog-node/src/testUtils/types.ts +++ b/plugins/catalog-node/src/testUtils/types.ts @@ -34,6 +34,10 @@ import { ValidateEntityResponse, } from '@backstage/catalog-client'; import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; +import { + AnalyzeLocationRequest, + AnalyzeLocationResponse, +} from '@backstage/plugin-catalog-common'; import { CatalogService, CatalogServiceRequestOptions, @@ -126,4 +130,9 @@ export interface CatalogServiceMock extends CatalogService, CatalogApi { locationRef: string, options?: CatalogServiceRequestOptions | CatalogRequestOptions, ): Promise; + + analyzeLocation( + location: AnalyzeLocationRequest, + options?: CatalogServiceRequestOptions | CatalogRequestOptions, + ): Promise; } diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 7ca221a40d..6ec1ef17df 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,358 @@ # @backstage/plugin-catalog-react +## 1.20.0-next.2 + +### Minor Changes + +- e4ddf22: **BREAKING ALPHA**: The `defaultPath`, `defaultTitle`, and `defaultGroup` params of `PageBlueprint` has been renamed to `path`, `title`, and `group`. The `convertLegacyEntityContentExtension` utility has also received the same change. This change does not affect the compatibility of extensions created with older versions of this blueprint. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/frontend-test-utils@0.3.5-next.2 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-react@0.4.36 + +## 1.20.0-next.1 + +### Minor Changes + +- 3f4da39: Added the `analyzeLocation` method to `catalogApiMock` + +### Patch Changes + +- defc243: hide pagination `queryparams` if pagination mode is set to none +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/frontend-test-utils@0.3.5-next.1 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-react@0.4.36 + +## 1.19.2-next.0 + +### Patch Changes + +- 77eebdc: Support `filter` parameter on the `EntityHeaderBlueprint` +- a3a878d: Adding `type` as an override to the `convertLegacyEntityCardExtension` +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/integration-react@1.2.9 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/frontend-test-utils@0.3.5-next.0 + +## 1.19.1 + +### Patch Changes + +- a07feb7: Fixed a but in the new alpha entity predicates where the `$in` operator mistakenly case sensitive. +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/frontend-test-utils@0.3.4 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + +## 1.19.1-next.1 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-test-utils@0.3.4-next.1 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 1.19.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.9-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/frontend-test-utils@0.3.4-next.0 + - @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.35 + +## 1.19.0 + +### Minor Changes + +- 406acb6: 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. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/integration-react@1.2.8 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/frontend-test-utils@0.3.3 + - @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.35 + +## 1.19.0-next.2 + +### Minor Changes + +- 406acb6: 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. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-test-utils@0.3.3-next.1 + - @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.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 + +- d47aaa3: Added EntityOrderFilter to sort entities by different fields/columns. This new filter allows users to specify the order in which entities are displayed in the catalog. + + Example usage: + + ```ts + import { + EntityOrderFilter, + useEntityList, + } from '@backstage/plugin-catalog-react'; + // ... + const { updateFilters } = useEntityList(); + + // ... + updateFilters({ + order: new EntityOrderFilter([ + { + field: 'metadata.name', + order: 'desc', + }, + ]), + }); + ``` + +- 1a003ff: Add `getLocations` method to `CatalogApi` and `CatalogClient`. This method calls the [`GET /locations`](https://backstage.io/docs/features/software-catalog/software-catalog-api/#get-locations) endpoint from the catalog backend. + +### Patch Changes + +- 2ddbc50: A new `filter` parameter has been added to `EntityContextMenuItemBlueprint` to make it easier to configure which entities a menu item should appear for. The `filter` parameter is a function which accepts an entity and returns a boolean. +- 6d7f0d5: Fixed an issue causing entities of kind user and group to be empty when an owner was selected +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/frontend-test-utils@0.3.2 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4 + +## 1.18.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/frontend-test-utils@0.3.2-next.3 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + ## 1.18.0-next.2 ### Patch Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index bb9d1bde29..96b1396a08 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-next.2", + "version": "1.20.0-next.2", "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 d5f85b7836..4e55240955 100644 --- a/plugins/catalog-react/report-alpha.api.md +++ b/plugins/catalog-react/report-alpha.api.md @@ -8,7 +8,9 @@ import { ComponentType } from 'react'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } 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'; @@ -19,11 +21,10 @@ import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha export const CatalogFilterBlueprint: ExtensionBlueprint<{ kind: 'catalog-filter'; - name: undefined; params: { loader: () => Promise; }; - output: ConfigurableExtensionDataRef; + output: ExtensionDataRef; inputs: {}; config: {}; configInput: {}; @@ -94,6 +95,7 @@ export function convertLegacyEntityCardExtension( overrides?: { name?: string; filter?: string | EntityPredicate | ((entity: Entity) => boolean); + type?: EntityCardType; }, ): ExtensionDefinition; @@ -103,8 +105,10 @@ export function convertLegacyEntityContentExtension( overrides?: { name?: string; filter?: string | EntityPredicate | ((entity: Entity) => boolean); - defaultPath?: string; - defaultTitle?: string; + path?: string; + title?: string; + defaultPath?: [Error: `Use the 'path' override instead`]; + defaultTitle?: [Error: `Use the 'title' override instead`]; }, ): ExtensionDefinition; @@ -121,29 +125,28 @@ export const defaultEntityContentGroups: { // @alpha export const EntityCardBlueprint: ExtensionBlueprint<{ kind: 'entity-card'; - name: undefined; params: { loader: () => Promise; filter?: string | EntityPredicate | ((entity: Entity) => boolean); type?: EntityCardType; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -184,41 +187,43 @@ export type EntityCardType = 'summary' | 'info' | 'content'; // @alpha export const EntityContentBlueprint: ExtensionBlueprint<{ kind: 'entity-content'; - name: undefined; params: { + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; + defaultTitle?: [Error: `Use the 'title' param instead`]; + title: string; + defaultGroup?: [Error: `Use the 'group' param instead`]; + group?: keyof typeof defaultEntityContentGroups | (string & {}); loader: () => Promise; - defaultPath: string; - defaultTitle: string; - defaultGroup?: keyof typeof defaultEntityContentGroups | (string & {}); routeRef?: RouteRef; filter?: string | EntityPredicate | ((entity: Entity) => boolean); }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { optional: true; } > - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< string, 'catalog.entity-content-group', { @@ -265,27 +270,26 @@ export const EntityContentBlueprint: ExtensionBlueprint<{ // @alpha (undocumented) export const EntityContentLayoutBlueprint: ExtensionBlueprint<{ kind: 'entity-content-layout'; - name: undefined; params: { filter?: string | EntityPredicate | ((entity: Entity) => boolean); loader: () => Promise<(props: EntityContentLayoutProps) => JSX_2.Element>; }; output: - | ConfigurableExtensionDataRef< + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< (props: EntityContentLayoutProps) => React.JSX.Element, 'catalog.entity-content-layout.component', {} @@ -330,39 +334,81 @@ export interface EntityContentLayoutProps { // @alpha (undocumented) export const EntityContextMenuItemBlueprint: ExtensionBlueprint<{ kind: 'entity-context-menu-item'; - name: undefined; params: EntityContextMenuItemParams; - output: ConfigurableExtensionDataRef; + output: + | ExtensionDataRef + | ExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + >; inputs: {}; - config: {}; - configInput: {}; - dataRefs: never; + config: { + filter: EntityPredicate | undefined; + }; + configInput: { + filter?: EntityPredicate | undefined; + }; + dataRefs: { + filterFunction: ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + {} + >; + }; }>; // @alpha (undocumented) export type EntityContextMenuItemParams = { useProps: UseProps; icon: JSX_2.Element; + filter?: EntityPredicate | ((entity: Entity) => boolean); }; // @alpha (undocumented) export const EntityHeaderBlueprint: ExtensionBlueprint<{ kind: 'entity-header'; - name: undefined; params: { loader: () => Promise; + filter?: EntityPredicate | ((entity: Entity) => boolean); }; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - { - optional: true; - } - >; + output: + | ExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > + | ExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + { + optional: true; + } + >; inputs: {}; - config: {}; - configInput: {}; + config: { + filter: EntityPredicate | undefined; + }; + configInput: { + filter?: EntityPredicate | undefined; + }; dataRefs: { + filterFunction: ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + {} + >; element: ConfigurableExtensionDataRef< JSX_2.Element, 'core.reactElement', @@ -371,6 +417,63 @@ export const EntityHeaderBlueprint: ExtensionBlueprint<{ }; }>; +// @alpha (undocumented) +export const EntityIconLinkBlueprint: ExtensionBlueprint<{ + kind: 'entity-icon-link'; + params: { + useProps: () => Omit; + filter?: EntityPredicate | ((entity: Entity) => boolean); + }; + output: + | ExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > + | ExtensionDataRef< + () => 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/EntityContentBlueprint.test.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx index 4fb21f1744..729f15079f 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx @@ -32,8 +32,8 @@ describe('EntityContentBlueprint', () => { const extension = EntityContentBlueprint.make({ name: 'test', params: { - defaultPath: '/test', - defaultTitle: 'Test', + path: '/test', + title: 'Test', loader: async () =>
    Test!
    , }, }); @@ -253,8 +253,8 @@ describe('EntityContentBlueprint', () => { const extension = EntityContentBlueprint.make({ name: 'test', params: { - defaultPath: '/test', - defaultTitle: 'Test', + path: '/test', + title: 'Test', routeRef: mockRouteRef, loader: async () =>
    Test!
    , }, @@ -277,8 +277,8 @@ describe('EntityContentBlueprint', () => { EntityContentBlueprint.make({ name: 'test', params: { - defaultPath: '/test', - defaultTitle: 'Test', + path: '/test', + title: 'Test', loader: async () =>
    Test!
    , filter: 'test', }, @@ -291,8 +291,8 @@ describe('EntityContentBlueprint', () => { EntityContentBlueprint.make({ name: 'test', params: { - defaultPath: '/test', - defaultTitle: 'Test', + path: '/test', + title: 'Test', loader: async () =>
    Test!
    , }, }), @@ -305,8 +305,8 @@ describe('EntityContentBlueprint', () => { EntityContentBlueprint.make({ name: 'test', params: { - defaultPath: '/test', - defaultTitle: 'Test', + path: '/test', + title: 'Test', filter: mockFilter, loader: async () =>
    Test!
    , }, @@ -328,8 +328,8 @@ describe('EntityContentBlueprint', () => { }, factory(originalFactory, { inputs, config }) { return originalFactory({ - defaultPath: '/test', - defaultTitle: 'Test', + path: '/test', + title: 'Test', loader: async () => (
    config: {config.mock} diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts index dbfb11092a..dadf059922 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts @@ -27,7 +27,7 @@ import { entityContentGroupDataRef, defaultEntityContentGroups, } from './extensionData'; -import { EntityPredicate } from '../predicates'; +import { EntityPredicate } from '../predicates/types'; import { resolveEntityFilterData } from './resolveEntityFilterData'; import { createEntityPredicateSchema } from '../predicates/createEntityPredicateSchema'; import { Entity } from '@backstage/catalog-model'; @@ -64,38 +64,45 @@ export const EntityContentBlueprint = createExtensionBlueprint({ }, }, *factory( - { - loader, - defaultPath, - defaultTitle, - defaultGroup, - filter, - routeRef, - }: { + params: { + /** + * @deprecated Use the `path` param instead. + */ + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; + /** + * @deprecated Use the `path` param instead. + */ + defaultTitle?: [Error: `Use the 'title' param instead`]; + title: string; + /** + * @deprecated Use the `path` param instead. + */ + defaultGroup?: [Error: `Use the 'group' param instead`]; + group?: keyof typeof defaultEntityContentGroups | (string & {}); loader: () => Promise; - defaultPath: string; - defaultTitle: string; - defaultGroup?: keyof typeof defaultEntityContentGroups | (string & {}); routeRef?: RouteRef; filter?: string | EntityPredicate | ((entity: Entity) => boolean); }, { node, config }, ) { - const path = config.path ?? defaultPath; - const title = config.title ?? defaultTitle; - const group = config.group ?? defaultGroup; + const path = config.path ?? params.path; + const title = config.title ?? params.title; + const group = config.group ?? params.group; - yield coreExtensionData.reactElement(ExtensionBoundary.lazy(node, loader)); + yield coreExtensionData.reactElement( + ExtensionBoundary.lazy(node, params.loader), + ); yield coreExtensionData.routePath(path); yield entityContentTitleDataRef(title); - if (routeRef) { - yield coreExtensionData.routeRef(routeRef); + if (params.routeRef) { + yield coreExtensionData.routeRef(params.routeRef); } - yield* resolveEntityFilterData(filter, config, node); + yield* resolveEntityFilterData(params.filter, config, node); if (group) { yield entityContentGroupDataRef(group); diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContentLayoutBlueprint.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityContentLayoutBlueprint.tsx index e529d60685..c13ea8ad56 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityContentLayoutBlueprint.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContentLayoutBlueprint.tsx @@ -25,7 +25,7 @@ import { EntityCardType, } from './extensionData'; import { JSX } from 'react'; -import { EntityPredicate } from '../predicates'; +import { EntityPredicate } from '../predicates/types'; import { resolveEntityFilterData } from './resolveEntityFilterData'; import { createEntityPredicateSchema } from '../predicates/createEntityPredicateSchema'; import { Entity } from '@backstage/catalog-model'; diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.test.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.test.tsx index 3bdfc00db7..55598f5de1 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.test.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.test.tsx @@ -13,7 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; import { EntityContextMenuItemBlueprint } from './EntityContextMenuItemBlueprint'; +import { screen, waitFor } from '@testing-library/react'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; +import { Entity } from '@backstage/catalog-model'; + +jest.mock('../../hooks/useEntityContextMenu', () => ({ + useEntityContextMenu: () => ({ + onMenuClose: jest.fn(), + }), +})); describe('EntityContextMenuItemBlueprint', () => { const data = [ @@ -49,7 +62,131 @@ describe('EntityContextMenuItemBlueprint', () => { "id": "page:catalog/entity", "input": "contextMenuItems", }, - "configSchema": undefined, + "configSchema": { + "parse": [Function], + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "filter": { + "anyOf": [ + { + "anyOf": [ + { + "type": [ + "string", + "number", + "boolean", + ], + }, + { + "items": { + "$ref": "#/properties/filter/anyOf/0/anyOf/0", + }, + "type": "array", + }, + ], + }, + { + "additionalProperties": false, + "properties": { + "$all": { + "items": { + "$ref": "#/properties/filter", + }, + "type": "array", + }, + }, + "required": [ + "$all", + ], + "type": "object", + }, + { + "additionalProperties": false, + "properties": { + "$any": { + "items": { + "$ref": "#/properties/filter", + }, + "type": "array", + }, + }, + "required": [ + "$any", + ], + "type": "object", + }, + { + "additionalProperties": false, + "properties": { + "$not": { + "$ref": "#/properties/filter", + }, + }, + "required": [ + "$not", + ], + "type": "object", + }, + { + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/properties/filter/anyOf/0", + }, + { + "additionalProperties": false, + "properties": { + "$exists": { + "type": "boolean", + }, + }, + "required": [ + "$exists", + ], + "type": "object", + }, + { + "additionalProperties": false, + "properties": { + "$in": { + "items": { + "$ref": "#/properties/filter/anyOf/0/anyOf/0", + }, + "type": "array", + }, + }, + "required": [ + "$in", + ], + "type": "object", + }, + { + "additionalProperties": false, + "properties": { + "$contains": { + "$ref": "#/properties/filter", + }, + }, + "required": [ + "$contains", + ], + "type": "object", + }, + ], + }, + "propertyNames": { + "pattern": "^(?!\\$).*$", + }, + "type": "object", + }, + ], + }, + }, + "type": "object", + }, + }, "disabled": false, "factory": [Function], "inputs": {}, @@ -57,6 +194,15 @@ describe('EntityContextMenuItemBlueprint', () => { "name": "test", "output": [ [Function], + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "catalog.entity-filter-function", + "optional": [Function], + "toString": [Function], + }, ], "override": [Function], "toString": [Function], @@ -64,4 +210,57 @@ describe('EntityContextMenuItemBlueprint', () => { } `); }); + + it('should render a menu item', async () => { + const extension = EntityContextMenuItemBlueprint.make({ + name: 'test', + params: { + icon: Icon, + useProps: () => ({ + title: 'Test', + onClick: () => {}, + }), + }, + }); + + renderInTestApp( + +
      {createExtensionTester(extension).reactElement()}
    +
    , + ); + + await waitFor(() => { + expect(screen.getByText('Test')).toBeInTheDocument(); + }); + }); + + it.each([ + { filter: { kind: 'Api' } }, + { filter: (e: Entity) => e.kind.toLowerCase() === 'api' }, + ])('should return a filter function', async ({ filter }) => { + const extension = EntityContextMenuItemBlueprint.make({ + name: 'test', + params: { + icon: Icon, + useProps: () => ({ title: 'Test', onClick: () => {} }), + filter, + }, + }); + + const tester = createExtensionTester(extension); + + const filterFn = tester.get( + EntityContextMenuItemBlueprint.dataRefs.filterFunction, + ); + + expect(filterFn).toBeDefined(); + expect(filterFn?.({ kind: 'Api' } as Entity)).toBe(true); + expect(filterFn?.({ kind: 'Component' } as Entity)).toBe(false); + }); }); diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.tsx index f43dd6a6d9..4b9b166196 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.tsx @@ -24,7 +24,11 @@ import MenuItem from '@material-ui/core/MenuItem'; import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemText from '@material-ui/core/ListItemText'; import { useEntityContextMenu } from '../../hooks/useEntityContextMenu'; - +import { EntityPredicate } from '../predicates/types'; +import { entityPredicateToFilterFunction } from '../predicates/entityPredicateToFilterFunction'; +import type { Entity } from '@backstage/catalog-model'; +import { entityFilterFunctionDataRef } from './extensionData'; +import { createEntityPredicateSchema } from '../predicates/createEntityPredicateSchema'; /** @alpha */ export type UseProps = () => | { @@ -42,14 +46,26 @@ export type UseProps = () => export type EntityContextMenuItemParams = { useProps: UseProps; icon: JSX.Element; + filter?: EntityPredicate | ((entity: Entity) => boolean); }; /** @alpha */ export const EntityContextMenuItemBlueprint = createExtensionBlueprint({ kind: 'entity-context-menu-item', attachTo: { id: 'page:catalog/entity', input: 'contextMenuItems' }, - output: [coreExtensionData.reactElement], - *factory(params: EntityContextMenuItemParams, { node }) { + output: [ + coreExtensionData.reactElement, + entityFilterFunctionDataRef.optional(), + ], + dataRefs: { + filterFunction: entityFilterFunctionDataRef, + }, + config: { + schema: { + filter: z => createEntityPredicateSchema(z).optional(), + }, + }, + *factory(params: EntityContextMenuItemParams, { node, config }) { const loader = async () => { const Component = () => { const { onMenuClose } = useEntityContextMenu(); @@ -79,5 +95,17 @@ export const EntityContextMenuItemBlueprint = createExtensionBlueprint({ }; yield coreExtensionData.reactElement(ExtensionBoundary.lazy(node, loader)); + + if (config.filter) { + yield entityFilterFunctionDataRef( + entityPredicateToFilterFunction(config.filter), + ); + } else if (typeof params.filter === 'function') { + yield entityFilterFunctionDataRef(params.filter); + } else if (params.filter) { + yield entityFilterFunctionDataRef( + entityPredicateToFilterFunction(params.filter), + ); + } }, }); diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityHeaderBlueprint.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityHeaderBlueprint.tsx index 431197e753..b83136dcd9 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityHeaderBlueprint.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityHeaderBlueprint.tsx @@ -19,22 +19,44 @@ import { coreExtensionData, ExtensionBoundary, } from '@backstage/frontend-plugin-api'; +import { EntityPredicate } from '../predicates/types'; +import { Entity } from '@backstage/catalog-model'; +import { resolveEntityFilterData } from './resolveEntityFilterData'; +import { createEntityPredicateSchema } from '../predicates/createEntityPredicateSchema'; +import { + entityFilterExpressionDataRef, + entityFilterFunctionDataRef, +} from './extensionData'; /** @alpha */ export const EntityHeaderBlueprint = createExtensionBlueprint({ kind: 'entity-header', - attachTo: { id: 'page:catalog/entity', input: 'header' }, + attachTo: { id: 'page:catalog/entity', input: 'headers' }, dataRefs: { + filterFunction: entityFilterFunctionDataRef, element: coreExtensionData.reactElement, }, - output: [coreExtensionData.reactElement.optional()], + config: { + schema: { + filter: z => createEntityPredicateSchema(z).optional(), + }, + }, + output: [ + entityFilterFunctionDataRef.optional(), + entityFilterExpressionDataRef.optional(), + coreExtensionData.reactElement.optional(), + ], *factory( params: { loader: () => Promise; + filter?: EntityPredicate | ((entity: Entity) => boolean); }, - { node }, + { node, config }, ) { - const { loader } = params; + const { loader, filter } = params; + + yield* resolveEntityFilterData(filter, config, node); + if (loader) { yield coreExtensionData.reactElement( ExtensionBoundary.lazy(node, loader), 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..2368945c65 --- /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/types'; +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-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx index 9e97412f92..fc7bed6281 100644 --- a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx @@ -26,7 +26,7 @@ import { import { screen } from '@testing-library/react'; import { convertLegacyEntityCardExtension } from './convertLegacyEntityCardExtension'; import { convertLegacyRouteRef } from '@backstage/core-compat-api'; -import { EntityContentBlueprint } from '../blueprints'; +import { EntityCardBlueprint } from '../blueprints'; const routeRef = createLegacyRouteRef({ id: 'test' }); const legacyPlugin = createLegacyPlugin({ @@ -60,12 +60,14 @@ describe('convertLegacyEntityCardExtension', () => { await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); - expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe( + expect(tester.get(EntityCardBlueprint.dataRefs.filterExpression)).toBe( undefined, ); - expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe( + expect(tester.get(EntityCardBlueprint.dataRefs.filterFunction)).toBe( undefined, ); + + expect(tester.get(EntityCardBlueprint.dataRefs.type)).toBe(undefined); }); it('should convert an entity card extension with overrides', async () => { @@ -94,10 +96,10 @@ describe('convertLegacyEntityCardExtension', () => { await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); - expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe( + expect(tester.get(EntityCardBlueprint.dataRefs.filterExpression)).toBe( 'my-filter', ); - expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe( + expect(tester.get(EntityCardBlueprint.dataRefs.filterFunction)).toBe( undefined, ); }); @@ -123,4 +125,32 @@ describe('convertLegacyEntityCardExtension', () => { expect(getDiscoveredId('EntityExAmpleCard')).toBe('entity-card:ex-ample'); expect(getDiscoveredId('ExampleCard')).toBe('entity-card:example-card'); }); + + it('should support the type override', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'EntityExampleCard', + mountPoint: routeRef, + component: async () => () =>
    Hello
    , + }), + ); + + const converted = convertLegacyEntityCardExtension(LegacyExtension, { + type: 'info', + }); + + const tester = createExtensionTester(converted); + + expect(tester.query(converted).node.spec.id).toBe('entity-card:example'); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(EntityCardBlueprint.dataRefs.type)).toBe('info'); + }); }); diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx index 2a5dc115d4..9b343db469 100644 --- a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx @@ -18,10 +18,11 @@ import { compatWrapper } from '@backstage/core-compat-api'; import { BackstagePlugin, getComponentData } from '@backstage/core-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; -import { EntityCardBlueprint } from '../blueprints'; +import { EntityCardBlueprint } from '../blueprints/EntityCardBlueprint'; import kebabCase from 'lodash/kebabCase'; -import { EntityPredicate } from '../predicates'; +import { EntityPredicate } from '../predicates/types'; import { Entity } from '@backstage/catalog-model'; +import { EntityCardType } from '../blueprints/extensionData'; /** @alpha */ export function convertLegacyEntityCardExtension( @@ -29,6 +30,7 @@ export function convertLegacyEntityCardExtension( overrides?: { name?: string; filter?: string | EntityPredicate | ((entity: Entity) => boolean); + type?: EntityCardType; }, ): ExtensionDefinition { const element = ; @@ -63,6 +65,7 @@ export function convertLegacyEntityCardExtension( params: { filter: overrides?.filter, loader: async () => compatWrapper(element), + type: overrides?.type, }, }); } diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx index 971e8a5eca..81da89d052 100644 --- a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx @@ -82,8 +82,8 @@ describe('convertLegacyEntityContentExtension', () => { const converted = convertLegacyEntityContentExtension(LegacyExtension, { name: 'other', - defaultPath: '/other', - defaultTitle: 'Other', + path: '/other', + title: 'Other', filter: 'my-filter', }); diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx index 740427e9b5..d9638286cb 100644 --- a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx @@ -27,8 +27,8 @@ import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import kebabCase from 'lodash/kebabCase'; import startCase from 'lodash/startCase'; import { ComponentType } from 'react'; -import { EntityContentBlueprint } from '../blueprints'; -import { EntityPredicate } from '../predicates'; +import { EntityContentBlueprint } from '../blueprints/EntityContentBlueprint'; +import { EntityPredicate } from '../predicates/types'; import { Entity } from '@backstage/catalog-model'; /** @alpha */ @@ -37,8 +37,18 @@ export function convertLegacyEntityContentExtension( overrides?: { name?: string; filter?: string | EntityPredicate | ((entity: Entity) => boolean); - defaultPath?: string; - defaultTitle?: string; + path?: string; + title?: string; + + /** + * @deprecated Use the `path` param instead. + */ + defaultPath?: [Error: `Use the 'path' override instead`]; + + /** + * @deprecated Use the `path` param instead. + */ + defaultTitle?: [Error: `Use the 'title' override instead`]; }, ): ExtensionDefinition { const element = ; @@ -77,8 +87,8 @@ export function convertLegacyEntityContentExtension( name: overrides?.name ?? name, params: { filter: overrides?.filter, - defaultPath: overrides?.defaultPath ?? `/${kebabCase(infix)}`, - defaultTitle: overrides?.defaultTitle ?? startCase(infix), + path: overrides?.path ?? `/${kebabCase(infix)}`, + title: overrides?.title ?? startCase(infix), routeRef: mountPoint && convertLegacyRouteRef(mountPoint), loader: async () => compatWrapper(element), }, diff --git a/plugins/catalog-react/src/alpha/predicates/createEntityPredicateSchema.ts b/plugins/catalog-react/src/alpha/predicates/createEntityPredicateSchema.ts index 9e5f44b327..cfd2e451df 100644 --- a/plugins/catalog-react/src/alpha/predicates/createEntityPredicateSchema.ts +++ b/plugins/catalog-react/src/alpha/predicates/createEntityPredicateSchema.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { EntityPredicate, EntityPredicateValue } from '.'; +import { EntityPredicate, EntityPredicateValue } from './types'; import type { z as zImpl, ZodType } from 'zod'; /** @internal */ diff --git a/plugins/catalog-react/src/alpha/predicates/entityPredicateToFilterFunction.test.ts b/plugins/catalog-react/src/alpha/predicates/entityPredicateToFilterFunction.test.ts index c83b213970..152230de7c 100644 --- a/plugins/catalog-react/src/alpha/predicates/entityPredicateToFilterFunction.test.ts +++ b/plugins/catalog-react/src/alpha/predicates/entityPredicateToFilterFunction.test.ts @@ -149,6 +149,8 @@ describe('entityPredicateToFilterFunction', () => { ['s,w,g,a', Object.create({ kind: 'component' })], ['', { 'metadata.tags': { $contains: 'go' } }], ['', { 'metadata.tags.0': 'java' }], + ['s,w,g', { kind: { $in: ['component', 'group'] } }], + ['a', { $not: { kind: { $in: ['component', 'group'] } } }], ['w,g,a', { $not: { 'metadata.tags': { $contains: 'java' } } }], [ 's,g', diff --git a/plugins/catalog-react/src/alpha/predicates/entityPredicateToFilterFunction.ts b/plugins/catalog-react/src/alpha/predicates/entityPredicateToFilterFunction.ts index 2fa220e9e2..f223911992 100644 --- a/plugins/catalog-react/src/alpha/predicates/entityPredicateToFilterFunction.ts +++ b/plugins/catalog-react/src/alpha/predicates/entityPredicateToFilterFunction.ts @@ -15,11 +15,7 @@ */ import { JsonValue } from '@backstage/types'; -import { - EntityPredicate, - EntityPredicatePrimitive, - EntityPredicateValue, -} from './types'; +import { EntityPredicate, EntityPredicateValue } from './types'; import { valueAtPath } from './valueAtPath'; /** @@ -92,7 +88,7 @@ function evaluatePredicateValue( return value.some(v => evaluateEntityPredicate(filter.$contains, v)); } if ('$in' in filter) { - return filter.$in.includes(value as EntityPredicatePrimitive); + return filter.$in.some(search => valuesAreEqual(value, search)); } if ('$exists' in filter) { if (filter.$exists === true) { diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index 560e2ba987..56af7e9a73 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -346,7 +346,11 @@ export const EntityListProvider = ( ignoreQueryPrefix: true, }); const newParams = qs.stringify( - { ...oldParams, filters: queryParams, cursor, offset, limit }, + { + ...oldParams, + filters: queryParams, + ...(paginationMode === 'none' ? {} : { cursor, limit, offset }), + }, { addQueryPrefix: true, arrayFormat: 'repeat' }, ); const newUrl = `${window.location.pathname}${newParams}`; diff --git a/plugins/catalog-react/src/testUtils/catalogApiMock.ts b/plugins/catalog-react/src/testUtils/catalogApiMock.ts index 08520e2a9b..42c6b1004f 100644 --- a/plugins/catalog-react/src/testUtils/catalogApiMock.ts +++ b/plugins/catalog-react/src/testUtils/catalogApiMock.ts @@ -101,5 +101,6 @@ export namespace catalogApiMock { removeLocationById: jest.fn(), getLocationByEntity: jest.fn(), validateEntity: jest.fn(), + analyzeLocation: jest.fn(), })); } diff --git a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md index 63bf77a4af..d72fea4dd6 100644 --- a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-catalog-unprocessed-entities-common +## 0.0.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + +## 0.0.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + +## 0.0.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.0 + ## 0.0.8-next.0 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities-common/package.json b/plugins/catalog-unprocessed-entities-common/package.json index d96e349347..6663c2535d 100644 --- a/plugins/catalog-unprocessed-entities-common/package.json +++ b/plugins/catalog-unprocessed-entities-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-unprocessed-entities-common", - "version": "0.0.8-next.0", + "version": "0.0.9", "description": "Common functionalities for the catalog-unprocessed-entities plugin", "backstage": { "role": "common-library", diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md index e7b747928f..ea7bb3f4db 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,144 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.20-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + +## 0.2.20-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + +## 0.2.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + +## 0.2.18 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + +## 0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + +## 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 + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + +## 0.2.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + ## 0.2.17-next.2 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index c6663150c0..dc4c4f0f94 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-next.2", + "version": "0.2.20-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-unprocessed-entities", diff --git a/plugins/catalog-unprocessed-entities/report-alpha.api.md b/plugins/catalog-unprocessed-entities/report-alpha.api.md index 8b53bcb8ae..8b29ef3354 100644 --- a/plugins/catalog-unprocessed-entities/report-alpha.api.md +++ b/plugins/catalog-unprocessed-entities/report-alpha.api.md @@ -5,7 +5,9 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; @@ -24,22 +26,22 @@ const _default: FrontendPlugin< name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'nav-item:catalog-unprocessed-entities': ExtensionDefinition<{ kind: 'nav-item'; name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { title: string; icon: IconComponent; @@ -65,9 +67,9 @@ const _default: FrontendPlugin< path?: string | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { @@ -76,7 +78,8 @@ const _default: FrontendPlugin< >; inputs: {}; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise; routeRef?: RouteRef; }; diff --git a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx index 8ff83605c7..39a51184d3 100644 --- a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx +++ b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx @@ -15,7 +15,6 @@ */ import { - createApiFactory, createFrontendPlugin, discoveryApiRef, fetchApiRef, @@ -37,8 +36,8 @@ import { rootRouteRef } from '../routes'; /** @alpha */ export const catalogUnprocessedEntitiesApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: catalogUnprocessedEntitiesApiRef, deps: { discoveryApi: discoveryApiRef, @@ -47,13 +46,12 @@ export const catalogUnprocessedEntitiesApi = ApiBlueprint.make({ factory: ({ discoveryApi, fetchApi }) => new CatalogUnprocessedEntitiesClient(discoveryApi, fetchApi), }), - }, }); /** @alpha */ export const catalogUnprocessedEntitiesPage = PageBlueprint.make({ params: { - defaultPath: '/catalog-unprocessed-entities', + path: '/catalog-unprocessed-entities', routeRef: convertLegacyRouteRef(rootRouteRef), loader: () => import('../components/UnprocessedEntities').then(m => @@ -74,6 +72,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 b410114940..9c11548559 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,311 @@ # @backstage/plugin-catalog +## 1.31.2-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-search-react@1.9.3-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-scaffolder-common@1.7.0-next.0 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + +## 1.31.2-next.1 + +### Patch Changes + +- c0ea01b: Fix card scrolling behaviour +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.7.0-next.0 + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-search-react@1.9.3-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.31.2-next.0 + +### Patch Changes + +- f4622e8: Adding a more sensible default order to the default filters +- 77eebdc: Support multiple headers in new frontend system, and don't render a header until the entity has finished loading +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/integration-react@1.2.9 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-search-react@1.9.2 + +## 1.31.1 + +### Patch Changes + +- 6991dab: Turn on `pagination` by default in new frontend system, and also make configurable +- 3ab9b96: Updated card extensions for the new frontend system to use the new entity predicates, and to not show the about card on User and Group pages. +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-scaffolder-common@1.6.0 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-search-react@1.9.2 + - @backstage/plugin-techdocs-react@1.3.1 + +## 1.31.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.2 + - @backstage/plugin-scaffolder-common@1.6.0-next.1 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + +## 1.31.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-scaffolder-common@1.5.12-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 1.31.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-react@1.2.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/plugin-search-react@1.9.2-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-scaffolder-common@1.5.11 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.31.0 + +### Minor Changes + +- 406acb6: 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`). + +- 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/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/plugin-techdocs-react@1.3.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-search-react@1.9.1 + - @backstage/integration-react@1.2.8 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@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.35 + - @backstage/plugin-scaffolder-common@1.5.11 + - @backstage/plugin-search-common@1.2.18 + +## 1.31.0-next.2 + +### Minor Changes + +- 406acb6: 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`). + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@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-techdocs-common@0.1.1-next.0 + +## 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 + +- 970cb48: Show the pagination text for the offset-paginated catalog table, and remove the pagination bar from the top of the `CatalogTable` when pagination is enabled. + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 2ddbc50: A new `filter` parameter has been added to `EntityContextMenuItemBlueprint` to make it easier to configure which entities a menu item should appear for. The `filter` parameter is a function which accepts an entity and returns a boolean. +- bf85d37: Fix for missing `routeRef` when using `core-plugin-api` in a dialog context +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-search-react@1.9.0 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-scaffolder-common@1.5.11 + - @backstage/plugin-search-common@1.2.18 + +## 1.30.0-next.3 + +### Minor Changes + +- 970cb48: Show the pagination text for the offset-paginated catalog table, and remove the pagination bar from the top of the `CatalogTable` when pagination is enabled. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-react@1.9.0-next.2 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 1.29.1-next.2 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 8eda14596b..c9eb54c153 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.29.1-next.2", + "version": "1.31.2-next.2", "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 54d4187536..b308eb2699 100644 --- a/plugins/catalog/report-alpha.api.md +++ b/plugins/catalog/report-alpha.api.md @@ -4,8 +4,8 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; -import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { defaultEntityContentGroups } from '@backstage/plugin-catalog-react/alpha'; import { Entity } from '@backstage/catalog-model'; @@ -13,11 +13,14 @@ import { EntityCardType } from '@backstage/plugin-catalog-react/alpha'; import { EntityContentLayoutProps } from '@backstage/plugin-catalog-react/alpha'; import { EntityContextMenuItemParams } from '@backstage/plugin-catalog-react/alpha'; import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; 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'; @@ -139,45 +142,45 @@ const _default: FrontendPlugin< name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:catalog/entity-presentation': ExtensionDefinition<{ kind: 'api'; name: 'entity-presentation'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:catalog/starred-entities': ExtensionDefinition<{ kind: 'api'; name: 'starred-entities'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'catalog-filter:catalog/kind': ExtensionDefinition<{ config: { @@ -186,14 +189,10 @@ const _default: FrontendPlugin< configInput: { initialFilter?: string | undefined; }; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + output: ExtensionDataRef; inputs: { [x: string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; @@ -211,11 +210,7 @@ const _default: FrontendPlugin< name: 'lifecycle'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + output: ExtensionDataRef; inputs: {}; params: { loader: () => Promise; @@ -228,14 +223,10 @@ const _default: FrontendPlugin< configInput: { initialFilter?: 'all' | 'owned' | 'starred' | undefined; }; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + output: ExtensionDataRef; inputs: { [x: string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; @@ -255,14 +246,10 @@ const _default: FrontendPlugin< configInput: { mode?: 'all' | 'owners-only' | undefined; }; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + output: ExtensionDataRef; inputs: { [x: string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; @@ -280,11 +267,7 @@ const _default: FrontendPlugin< name: 'namespace'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + output: ExtensionDataRef; inputs: {}; params: { loader: () => Promise; @@ -295,11 +278,7 @@ const _default: FrontendPlugin< name: 'processing-status'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + output: ExtensionDataRef; inputs: {}; params: { loader: () => Promise; @@ -310,11 +289,7 @@ const _default: FrontendPlugin< name: 'tag'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + output: ExtensionDataRef; inputs: {}; params: { loader: () => Promise; @@ -325,19 +300,13 @@ const _default: FrontendPlugin< name: 'type'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + output: ExtensionDataRef; inputs: {}; params: { loader: () => Promise; }; }>; 'entity-card:catalog/about': ExtensionDefinition<{ - kind: 'entity-card'; - name: 'about'; config: { filter: EntityPredicate | undefined; type: 'content' | 'summary' | 'info' | undefined; @@ -347,29 +316,57 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { 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); @@ -388,22 +385,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -429,22 +426,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -470,22 +467,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -511,22 +508,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -552,22 +549,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -593,22 +590,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -634,22 +631,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -675,22 +672,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -716,22 +713,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -759,35 +756,31 @@ const _default: FrontendPlugin< group?: string | false | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { optional: true; } > - | ConfigurableExtensionDataRef< - string, - 'catalog.entity-content-title', - {} - > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< string, 'catalog.entity-content-group', { @@ -852,10 +845,13 @@ const _default: FrontendPlugin< kind: 'entity-content'; name: 'overview'; params: { + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; + defaultTitle?: [Error: `Use the 'title' param instead`]; + title: string; + defaultGroup?: [Error: `Use the 'group' param instead`]; + group?: keyof defaultEntityContentGroups | (string & {}); loader: () => Promise; - defaultPath: string; - defaultTitle: string; - defaultGroup?: keyof defaultEntityContentGroups | (string & {}); routeRef?: RouteRef; filter?: string | EntityPredicate | ((entity: Entity) => boolean); }; @@ -863,48 +859,111 @@ const _default: FrontendPlugin< 'entity-context-menu-item:catalog/copy-entity-url': ExtensionDefinition<{ kind: 'entity-context-menu-item'; name: 'copy-entity-url'; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + config: { + filter: EntityPredicate | undefined; + }; + configInput: { + filter?: EntityPredicate | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + >; inputs: {}; params: EntityContextMenuItemParams; }>; 'entity-context-menu-item:catalog/inspect-entity': ExtensionDefinition<{ kind: 'entity-context-menu-item'; name: 'inspect-entity'; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + config: { + filter: EntityPredicate | undefined; + }; + configInput: { + filter?: EntityPredicate | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + >; inputs: {}; params: EntityContextMenuItemParams; }>; 'entity-context-menu-item:catalog/unregister-entity': ExtensionDefinition<{ kind: 'entity-context-menu-item'; name: 'unregister-entity'; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - {} - >; + config: { + filter: EntityPredicate | undefined; + }; + configInput: { + filter?: EntityPredicate | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + >; 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: + | ExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > + | ExtensionDataRef< + () => IconLinkVerticalProps, + 'entity-icon-link-props', + {} + >; + inputs: {}; + params: { + useProps: () => Omit; + filter?: EntityPredicate | ((entity: Entity) => boolean); + }; + }>; 'nav-item:catalog': ExtensionDefinition<{ kind: 'nav-item'; name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { title: string; icon: IconComponent; @@ -922,15 +981,32 @@ const _default: FrontendPlugin< }>; 'page:catalog': ExtensionDefinition<{ config: { + pagination: + | boolean + | { + mode: 'offset' | 'cursor'; + offset?: number | undefined; + limit?: number | undefined; + }; + } & { path: string | undefined; }; configInput: { + pagination?: + | boolean + | { + mode: 'offset' | 'cursor'; + offset?: number | undefined; + limit?: number | undefined; + } + | undefined; + } & { path?: string | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { @@ -949,7 +1025,8 @@ const _default: FrontendPlugin< kind: 'page'; name: undefined; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise; routeRef?: RouteRef; }; @@ -980,9 +1057,9 @@ const _default: FrontendPlugin< path?: string | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { @@ -990,17 +1067,24 @@ const _default: FrontendPlugin< } >; inputs: { - header: ExtensionInput< - ConfigurableExtensionDataRef< - JSX_2.Element, - 'core.reactElement', - { - optional: true; - } - >, + headers: ExtensionInput< + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + JSX_2.Element, + 'core.reactElement', + { + optional: true; + } + >, { - singleton: true; - optional: true; + singleton: false; + optional: false; } >; contents: ExtensionInput< @@ -1045,7 +1129,14 @@ const _default: FrontendPlugin< } >; contextMenuItems: ExtensionInput< - ConfigurableExtensionDataRef, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + >, { singleton: false; optional: false; @@ -1055,7 +1146,8 @@ const _default: FrontendPlugin< kind: 'page'; name: 'entity'; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise; routeRef?: RouteRef; }; @@ -1069,7 +1161,7 @@ const _default: FrontendPlugin< configInput: { noTrack?: boolean | undefined; }; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { predicate?: SearchResultItemExtensionPredicate; component: SearchResultItemExtensionComponent; 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/DefaultEntityContentLayout.tsx b/plugins/catalog/src/alpha/DefaultEntityContentLayout.tsx index 21276f424a..3188b98437 100644 --- a/plugins/catalog/src/alpha/DefaultEntityContentLayout.tsx +++ b/plugins/catalog/src/alpha/DefaultEntityContentLayout.tsx @@ -41,7 +41,7 @@ const useStyles = makeStyles< flexFlow: 'column nowrap', gap: theme.spacing(3), }, - contentArea: { + mainContent: { display: 'flex', flexFlow: 'column', gap: theme.spacing(3), @@ -51,9 +51,13 @@ const useStyles = makeStyles< infoArea: { display: 'flex', flexFlow: 'column nowrap', - alignItems: 'stretch', + alignItems: 'flex-start', gap: theme.spacing(3), minWidth: 0, + '& > *': { + flexShrink: 0, + flexGrow: 0, + }, }, summaryArea: { minWidth: 0, @@ -65,10 +69,17 @@ const useStyles = makeStyles< marginLeft: theme.spacing(3), }, }, + contentArea: { + display: 'flex', + flexFlow: 'column', + gap: theme.spacing(3), + alignItems: 'stretch', + minWidth: 0, + }, [theme.breakpoints.up('md')]: { root: { display: 'grid', - gap: 0, + gap: theme.spacing(3), gridTemplateAreas: ({ summaryCards }) => ` "${summaryCards ? 'summary' : 'content'} info" "content info" @@ -76,11 +87,8 @@ const useStyles = makeStyles< gridTemplateColumns: ({ infoCards }) => (infoCards ? '2fr 1fr' : '1fr'), alignItems: 'start', }, - infoArea: { - gridArea: 'info', - position: 'sticky', - top: theme.spacing(3), - marginLeft: theme.spacing(3), + mainContent: { + display: 'contents', }, contentArea: { gridArea: 'content', @@ -89,6 +97,25 @@ const useStyles = makeStyles< gridArea: 'summary', marginBottom: theme.spacing(3), }, + infoArea: { + gridArea: 'info', + position: 'sticky', + top: theme.spacing(3), + // this is a little unfortunate, but it's required to make the info cards scrollable + // in a fixed container of the full height when it's stuck. + // 100% doesn't work as that's the height of the entire layout, which is what powers the card scrolling. + maxHeight: '100vh', + overflowY: 'auto', + alignSelf: 'start', + alignItems: 'stretch', + // Hide the scrollbar for the inner info cards + // kind of an accessibility nightmare, but we see. + scrollbarWidth: 'none', + msOverflowStyle: 'none', + '&::-webkit-scrollbar': { + display: 'none', + }, + }, }, })); @@ -144,20 +171,22 @@ export function DefaultEntityContentLayout(props: EntityContentLayoutProps) { {infoCards.map(card => card.element)}
    ) : null} - {summaryCards.length > 0 ? ( -
    - - {summaryCards.map(card => ( -
    {card.element}
    - ))} -
    -
    - ) : null} - {contentCards.length > 0 ? ( -
    - {contentCards.map(card => card.element)} -
    - ) : null} +
    + {summaryCards.length > 0 ? ( +
    + + {summaryCards.map(card => ( +
    {card.element}
    + ))} +
    +
    + ) : null} + {contentCards.length > 0 ? ( +
    + {contentCards.map(card => card.element)} +
    + ) : null} +
    ); diff --git a/plugins/catalog/src/alpha/apis.tsx b/plugins/catalog/src/alpha/apis.tsx index d0f80c3aec..bf5a502726 100644 --- a/plugins/catalog/src/alpha/apis.tsx +++ b/plugins/catalog/src/alpha/apis.tsx @@ -15,7 +15,6 @@ */ import { - createApiFactory, discoveryApiRef, fetchApiRef, storageApiRef, @@ -33,8 +32,8 @@ import { } from '../apis'; export const catalogApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: catalogApiRef, deps: { discoveryApi: discoveryApiRef, @@ -43,31 +42,28 @@ export const catalogApi = ApiBlueprint.make({ factory: ({ discoveryApi, fetchApi }) => new CatalogClient({ discoveryApi, fetchApi }), }), - }, }); export const catalogStarredEntitiesApi = ApiBlueprint.make({ name: 'starred-entities', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: starredEntitiesApiRef, deps: { storageApi: storageApiRef }, factory: ({ storageApi }) => new DefaultStarredEntitiesApi({ storageApi }), }), - }, }); export const entityPresentationApi = ApiBlueprint.make({ name: 'entity-presentation', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: entityPresentationApiRef, deps: { catalogApiImp: catalogApiRef }, factory: ({ catalogApiImp }) => DefaultEntityPresentationApi.create({ catalogApi: catalogApiImp }), }), - }, }); export default [catalogApi, catalogStarredEntitiesApi, entityPresentationApi]; diff --git a/plugins/catalog/src/alpha/components/EntityLayout/EntityLayout.tsx b/plugins/catalog/src/alpha/components/EntityLayout/EntityLayout.tsx index 750c8567ee..8f5838d545 100644 --- a/plugins/catalog/src/alpha/components/EntityLayout/EntityLayout.tsx +++ b/plugins/catalog/src/alpha/components/EntityLayout/EntityLayout.tsx @@ -102,13 +102,21 @@ export const EntityLayout = (props: EntityLayoutProps) => { UNSTABLE_contextMenuOptions, contextMenuItems, children, - header, NotFoundComponent, parentEntityRelations, } = props; const { kind } = useRouteRefParams(entityRouteRef); const { entity, loading, error } = useAsyncEntity(); + const header = props.header ?? ( + + ); + const routes = useElementFilter( children, elements => @@ -142,14 +150,7 @@ export const EntityLayout = (props: EntityLayoutProps) => { return ( - {header ?? ( - - )} + {!loading && header} {loading && } diff --git a/plugins/catalog/src/alpha/entityCards.tsx b/plugins/catalog/src/alpha/entityCards.tsx index 8662518d30..f99c5961a1 100644 --- a/plugins/catalog/src/alpha/entityCards.tsx +++ b/plugins/catalog/src/alpha/entityCards.tsx @@ -14,17 +14,62 @@ * 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', + filter: { + $not: { + kind: { $in: ['user', 'group'] }, + }, + }, + async loader() { + const { InternalAboutCard } = await import( + '../components/AboutCard/AboutCard' + ); + return compatWrapper( + } />, + ); + }, + }); }, }); @@ -32,7 +77,7 @@ export const catalogLinksEntityCard = EntityCardBlueprint.make({ name: 'links', params: { type: 'info', - filter: 'has:links', + filter: { 'metadata.links': { $exists: true } }, loader: async () => import('../components/EntityLinksCard').then(m => compatWrapper(), @@ -44,7 +89,7 @@ export const catalogLabelsEntityCard = EntityCardBlueprint.make({ name: 'labels', params: { type: 'info', - filter: 'has:labels', + filter: { 'metadata.labels': { $exists: true } }, loader: async () => import('../components/EntityLabelsCard').then(m => compatWrapper(), @@ -55,7 +100,7 @@ export const catalogLabelsEntityCard = EntityCardBlueprint.make({ export const catalogDependsOnComponentsEntityCard = EntityCardBlueprint.make({ name: 'depends-on-components', params: { - filter: 'kind:component', + filter: { kind: 'component' }, loader: async () => import('../components/DependsOnComponentsCard').then(m => compatWrapper(), @@ -66,7 +111,7 @@ export const catalogDependsOnComponentsEntityCard = EntityCardBlueprint.make({ export const catalogDependsOnResourcesEntityCard = EntityCardBlueprint.make({ name: 'depends-on-resources', params: { - filter: 'kind:component', + filter: { kind: 'component' }, loader: async () => import('../components/DependsOnResourcesCard').then(m => compatWrapper(), @@ -77,7 +122,7 @@ export const catalogDependsOnResourcesEntityCard = EntityCardBlueprint.make({ export const catalogHasComponentsEntityCard = EntityCardBlueprint.make({ name: 'has-components', params: { - filter: 'kind:system', + filter: { kind: 'system' }, loader: async () => import('../components/HasComponentsCard').then(m => compatWrapper(), @@ -88,7 +133,7 @@ export const catalogHasComponentsEntityCard = EntityCardBlueprint.make({ export const catalogHasResourcesEntityCard = EntityCardBlueprint.make({ name: 'has-resources', params: { - filter: 'kind:system', + filter: { kind: 'system' }, loader: async () => import('../components/HasResourcesCard').then(m => compatWrapper(), @@ -99,7 +144,7 @@ export const catalogHasResourcesEntityCard = EntityCardBlueprint.make({ export const catalogHasSubcomponentsEntityCard = EntityCardBlueprint.make({ name: 'has-subcomponents', params: { - filter: 'kind:component', + filter: { kind: 'component' }, loader: async () => import('../components/HasSubcomponentsCard').then(m => compatWrapper(), @@ -110,7 +155,7 @@ export const catalogHasSubcomponentsEntityCard = EntityCardBlueprint.make({ export const catalogHasSubdomainsEntityCard = EntityCardBlueprint.make({ name: 'has-subdomains', params: { - filter: 'kind:domain', + filter: { kind: 'domain' }, loader: async () => import('../components/HasSubdomainsCard').then(m => compatWrapper(), @@ -121,7 +166,7 @@ export const catalogHasSubdomainsEntityCard = EntityCardBlueprint.make({ export const catalogHasSystemsEntityCard = EntityCardBlueprint.make({ name: 'has-systems', params: { - filter: 'kind:domain', + filter: { kind: 'domain' }, loader: async () => import('../components/HasSystemsCard').then(m => compatWrapper(), diff --git a/plugins/catalog/src/alpha/entityContents.tsx b/plugins/catalog/src/alpha/entityContents.tsx index 165ed33e04..bc4556fdda 100644 --- a/plugins/catalog/src/alpha/entityContents.tsx +++ b/plugins/catalog/src/alpha/entityContents.tsx @@ -47,9 +47,9 @@ export const catalogOverviewEntityContent = }, factory: (originalFactory, { node, inputs }) => { return originalFactory({ - defaultPath: '/', - defaultTitle: 'Overview', - defaultGroup: 'overview', + path: '/', + title: 'Overview', + group: 'overview', loader: async () => { const LazyDefaultLayoutComponent = reactLazy(() => import('./DefaultEntityContentLayout').then(m => ({ 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/filters.tsx b/plugins/catalog/src/alpha/filters.tsx index fcfbc394f2..d72e299fbb 100644 --- a/plugins/catalog/src/alpha/filters.tsx +++ b/plugins/catalog/src/alpha/filters.tsx @@ -133,13 +133,14 @@ const catalogListCatalogFilter = CatalogFilterBlueprint.makeWithOverrides({ }, }); +// this is the default order that the filters will be applied in export default [ - catalogTagCatalogFilter, catalogKindCatalogFilter, catalogTypeCatalogFilter, - catalogModeCatalogFilter, - catalogNamespaceCatalogFilter, - catalogLifecycleCatalogFilter, - catalogProcessingStatusCatalogFilter, catalogListCatalogFilter, + catalogModeCatalogFilter, + catalogLifecycleCatalogFilter, + catalogTagCatalogFilter, + catalogProcessingStatusCatalogFilter, + catalogNamespaceCatalogFilter, ]; diff --git a/plugins/catalog/src/alpha/pages.test.tsx b/plugins/catalog/src/alpha/pages.test.tsx index f5aeb7d196..cc83b9168b 100644 --- a/plugins/catalog/src/alpha/pages.test.tsx +++ b/plugins/catalog/src/alpha/pages.test.tsx @@ -36,6 +36,7 @@ import { } from '@backstage/plugin-catalog-react'; import { convertLegacyRouteRef } from '@backstage/core-compat-api'; import { rootRouteRef } from '../routes'; +import { Entity } from '@backstage/catalog-model'; describe('Entity page', () => { const entityMock = { @@ -115,8 +116,8 @@ describe('Entity page', () => { const overviewEntityContent = EntityContentBlueprint.make({ name: 'overview', params: { - defaultPath: '/overview', - defaultTitle: 'Overview', + path: '/overview', + title: 'Overview', loader: async () =>
    Mock Overview content
    , }, }); @@ -124,9 +125,9 @@ describe('Entity page', () => { const techdocsEntityContent = EntityContentBlueprint.make({ name: 'techdocs', params: { - defaultPath: '/techdocs', - defaultTitle: 'TechDocs', - defaultGroup: 'documentation', + path: '/techdocs', + title: 'TechDocs', + group: 'documentation', loader: async () =>
    Mock TechDocs content
    , }, }); @@ -134,9 +135,9 @@ describe('Entity page', () => { const apidocsEntityContent = EntityContentBlueprint.make({ name: 'apidocs', params: { - defaultPath: '/apidocs', - defaultTitle: 'ApiDocs', - defaultGroup: 'documentation', + path: '/apidocs', + title: 'ApiDocs', + group: 'documentation', loader: async () =>
    Mock ApiDocs content
    , }, }); @@ -733,5 +734,91 @@ describe('Entity page', () => { expect(onClickMock).toHaveBeenCalledTimes(disabled ? 0 : 1); }); }); + + it.each([ + { + positive: { params: {} }, + negative: { params: { filter: { kind: 'api' } } }, + }, + { + positive: { params: { filter: { kind: 'component' } } }, + negative: { params: { filter: { kind: 'api' } } }, + }, + { + positive: { + params: { + filter: (e: Entity) => e.kind.toLowerCase() === 'component', + }, + }, + negative: { + params: { filter: (e: Entity) => e.kind.toLowerCase() === 'api' }, + }, + }, + ])( + 'should render menu items according to filters', + async ({ positive, negative }) => { + const menuItem = EntityContextMenuItemBlueprint.make({ + name: 'should-render-menu-item', + params: { + icon: Test Icon, + useProps: () => ({ + onClick: onClickMock, + title: 'Should Render', + }), + ...positive.params, + }, + }); + + const filteredMenuItem = EntityContextMenuItemBlueprint.make({ + name: 'should-not-render-menu-item', + params: { + icon: Test Icon, + useProps: () => ({ + onClick: onClickMock, + title: 'Should Not Render', + }), + ...negative.params, + }, + }); + + const tester = createExtensionTester( + Object.assign({ namespace: 'catalog' }, catalogEntityPage), + ) + .add(menuItem) + .add(filteredMenuItem); + + renderInTestApp( + + {tester.reactElement()} + , + { + config: { + app: { + title: 'Custom app', + }, + backend: { baseUrl: 'http://localhost:7000' }, + }, + mountedRoutes: { + '/catalog': convertLegacyRouteRef(rootRouteRef), + '/catalog/:namespace/:kind/:name': + convertLegacyRouteRef(entityRouteRef), + }, + }, + ); + + await waitFor(async () => { + await userEvent.click(screen.getByTestId('menu-button')); + expect(screen.getByText('Should Render')).toBeInTheDocument(); + expect( + screen.queryByText('Should Not Render'), + ).not.toBeInTheDocument(); + }); + }, + ); }); }); diff --git a/plugins/catalog/src/alpha/pages.tsx b/plugins/catalog/src/alpha/pages.tsx index b39fe510e6..e4e1a78151 100644 --- a/plugins/catalog/src/alpha/pages.tsx +++ b/plugins/catalog/src/alpha/pages.tsx @@ -31,26 +31,46 @@ import { EntityHeaderBlueprint, EntityContentBlueprint, defaultEntityContentGroups, + EntityContextMenuItemBlueprint, } from '@backstage/plugin-catalog-react/alpha'; import { rootRouteRef } from '../routes'; import { useEntityFromUrl } from '../components/CatalogEntityPage/useEntityFromUrl'; import { buildFilterFn } from './filter/FilterWrapper'; -import { EntityHeader } from './components/EntityHeader'; export const catalogPage = PageBlueprint.makeWithOverrides({ inputs: { filters: createExtensionInput([coreExtensionData.reactElement]), }, - factory(originalFactory, { inputs }) { + config: { + schema: { + pagination: z => + z + .union([ + z.boolean(), + z.object({ + mode: z.enum(['cursor', 'offset']), + limit: z.number().optional(), + offset: z.number().optional(), + }), + ]) + .default(true), + }, + }, + factory(originalFactory, { inputs, config }) { return originalFactory({ - defaultPath: '/catalog', + path: '/catalog', routeRef: convertLegacyRouteRef(rootRouteRef), loader: async () => { const { BaseCatalogPage } = await import('../components/CatalogPage'); const filters = inputs.filters.map(filter => filter.get(coreExtensionData.reactElement), ); - return compatWrapper({filters}} />); + return compatWrapper( + {filters}} + pagination={config.pagination} + />, + ); }, }); }, @@ -59,10 +79,10 @@ export const catalogPage = PageBlueprint.makeWithOverrides({ export const catalogEntityPage = PageBlueprint.makeWithOverrides({ name: 'entity', inputs: { - header: createExtensionInput( - [EntityHeaderBlueprint.dataRefs.element.optional()], - { singleton: true, optional: true }, - ), + headers: createExtensionInput([ + EntityHeaderBlueprint.dataRefs.element.optional(), + EntityHeaderBlueprint.dataRefs.filterFunction.optional(), + ]), contents: createExtensionInput([ coreExtensionData.reactElement, coreExtensionData.routePath, @@ -72,7 +92,10 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({ EntityContentBlueprint.dataRefs.filterExpression.optional(), EntityContentBlueprint.dataRefs.group.optional(), ]), - contextMenuItems: createExtensionInput([coreExtensionData.reactElement]), + contextMenuItems: createExtensionInput([ + coreExtensionData.reactElement, + EntityContextMenuItemBlueprint.dataRefs.filterFunction.optional(), + ]), }, config: { schema: { @@ -84,23 +107,36 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({ }, factory(originalFactory, { config, inputs }) { return originalFactory({ - defaultPath: '/catalog/:namespace/:kind/:name', + path: '/catalog/:namespace/:kind/:name', routeRef: convertLegacyRouteRef(entityRouteRef), loader: async () => { const { EntityLayout } = await import('./components/EntityLayout'); - const menuItems = inputs.contextMenuItems.map(item => - item.get(coreExtensionData.reactElement), - ); + const menuItems = inputs.contextMenuItems.map(item => ({ + element: item.get(coreExtensionData.reactElement), + filter: + item.get(EntityContextMenuItemBlueprint.dataRefs.filterFunction) ?? + (() => true), + })); type Groups = Record< string, { title: string; items: Array<(typeof inputs.contents)[0]> } >; - const header = inputs.header?.get( - EntityHeaderBlueprint.dataRefs.element, - ) ?? ; + // Get available headers, sorted by if they have a filter function or not. + // TODO(blam): we should really have priority or some specificity here which can be used to sort the headers. + // That can be done with embedding the priority in the dataRef alongside the filter function. + const headers = inputs.headers + .map(header => ({ + element: header.get(EntityHeaderBlueprint.dataRefs.element), + filter: header.get(EntityHeaderBlueprint.dataRefs.filterFunction), + })) + .sort((a, b) => { + if (a.filter && !b.filter) return -1; + if (!a.filter && b.filter) return 1; + return 0; + }); let groups = Object.entries(defaultEntityContentGroups).reduce( (rest, group) => { @@ -137,9 +173,22 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({ } const Component = () => { + const entityFromUrl = useEntityFromUrl(); + const { entity } = entityFromUrl; + const filteredMenuItems = entity + ? menuItems.filter(i => i.filter(entity)).map(i => i.element) + : []; + + const header = headers.find( + h => !h.filter || h.filter(entity!), + )?.element; + return ( - - + + {Object.values(groups).flatMap(({ title, items }) => items.map(output => ( 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/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 9b3a950ffd..464688c590 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -47,7 +47,7 @@ import { OffsetPaginatedCatalogTable } from './OffsetPaginatedCatalogTable'; import { CursorPaginatedCatalogTable } from './CursorPaginatedCatalogTable'; import { defaultCatalogTableColumnsFunc } from './defaultCatalogTableColumnsFunc'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -import { catalogTranslationRef } from '../../alpha/translation'; +import { catalogTranslationRef } from '../../alpha'; import { FavoriteToggleIcon } from '@backstage/core-components'; /** @@ -194,7 +194,7 @@ export const CatalogTable = (props: CatalogTableProps) => { .join(' '); const actions = props.actions || defaultActions; - const options = { + const options: TableProps['options'] = { actionsColumnIndex: -1, loadingType: 'linear' as const, showEmptyDataSourceMessage: !loading, diff --git a/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.tsx index e49ecf3cd9..1fcc2683be 100644 --- a/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.tsx @@ -35,7 +35,6 @@ export function CursorPaginatedCatalogTable(props: PaginatedCatalogTableProps) { columns={columns} data={data} options={{ - paginationPosition: 'both', ...options, // These settings are configured to force server side pagination pageSizeOptions: [], diff --git a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx index 96c81088ca..896cf0f8b6 100644 --- a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx @@ -47,7 +47,6 @@ export function OffsetPaginatedCatalogTable( columns={columns} data={data} options={{ - paginationPosition: 'both', pageSizeOptions: [5, 10, 20, 50, 100], pageSize: limit, emptyRowsWhenPaging: false, @@ -57,14 +56,9 @@ export function OffsetPaginatedCatalogTable( Toolbar: CatalogTableToolbar, }} page={page} - onPageChange={newPage => { - setPage(newPage); - }} - onRowsPerPageChange={pageSize => { - setLimit(pageSize); - }} + onPageChange={setPage} + onRowsPerPageChange={setLimit} totalCount={totalItems} - localization={{ pagination: { labelDisplayedRows: '' } }} {...restProps} /> ); diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index ff5cd9d89f..46ab9c4f14 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/plugin-config-schema +## 0.1.71-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.1.70 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + +## 0.1.70-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## 0.1.70-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.1.69 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.1.69-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.1.68 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.1.68-next.1 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index f21d03be67..11c45820c5 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-config-schema", - "version": "0.1.68-next.1", + "version": "0.1.71-next.0", "description": "A Backstage plugin that lets you browse the configuration schema of your app", "backstage": { "role": "frontend-plugin", diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index b51d75a457..55c786c797 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,154 @@ # @backstage/plugin-devtools-backend +## 0.5.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config-loader@1.10.2 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-devtools-common@0.1.17 + - @backstage/plugin-permission-common@0.9.1 + +## 0.5.7 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2 + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/backend-defaults@0.11.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-devtools-common@0.1.17 + +## 0.5.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.2-next.0 + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-devtools-common@0.1.17-next.0 + +## 0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config-loader@1.10.1 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-devtools-common@0.1.16 + - @backstage/plugin-permission-common@0.9.0 + +## 0.5.6 + +### Patch Changes + +- 9a99735: Removed the usage of `permissionIntegrationRouter` in favor of using the new `coreServices.permissionsRegistry` +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +## 0.5.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @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.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 + +- Updated dependencies + - @backstage/backend-defaults@0.10.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/config-loader@1.10.1 + - @backstage/config@1.3.2 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-devtools-common@0.1.16 + +## 0.5.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-devtools-common@0.1.16-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + ## 0.5.5-next.2 ### Patch Changes diff --git a/plugins/devtools-backend/knip-report.md b/plugins/devtools-backend/knip-report.md index 0923e3006f..dc1b572006 100644 --- a/plugins/devtools-backend/knip-report.md +++ b/plugins/devtools-backend/knip-report.md @@ -1,9 +1,10 @@ # Knip report -## Unused dependencies (2) +## Unused dependencies (3) -| Name | Location | Severity | -| :----- | :----------- | :------- | -| semver | package.json | error | -| yn | package.json | error | +| Name | Location | Severity | +| :-------------------------------- | :----------- | :------- | +| @backstage/plugin-permission-node | package.json | error | +| semver | package.json | error | +| yn | package.json | error | diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index f3831118b7..01c542364f 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-next.2", + "version": "0.5.8-next.0", "backstage": { "role": "backend-plugin", "pluginId": "devtools", diff --git a/plugins/devtools-backend/src/plugin.ts b/plugins/devtools-backend/src/plugin.ts index ee7bf369f5..d3fefda343 100644 --- a/plugins/devtools-backend/src/plugin.ts +++ b/plugins/devtools-backend/src/plugin.ts @@ -19,6 +19,7 @@ import { createBackendPlugin, } from '@backstage/backend-plugin-api'; import { createRouter } from './service/router'; +import { devToolsPermissions } from '@backstage/plugin-devtools-common'; /** * DevTools backend plugin @@ -36,6 +37,7 @@ export const devtoolsPlugin = createBackendPlugin({ httpRouter: coreServices.httpRouter, discovery: coreServices.discovery, httpAuth: coreServices.httpAuth, + permissionsRegistry: coreServices.permissionsRegistry, }, async init({ config, @@ -44,6 +46,7 @@ export const devtoolsPlugin = createBackendPlugin({ httpRouter, discovery, httpAuth, + permissionsRegistry, }) { httpRouter.use( await createRouter({ @@ -58,6 +61,7 @@ export const devtoolsPlugin = createBackendPlugin({ path: '/health', allow: 'unauthenticated', }); + permissionsRegistry.addPermissions(devToolsPermissions); }, }); }, diff --git a/plugins/devtools-backend/src/service/router.ts b/plugins/devtools-backend/src/service/router.ts index ad77495b36..caba2185fc 100644 --- a/plugins/devtools-backend/src/service/router.ts +++ b/plugins/devtools-backend/src/service/router.ts @@ -19,13 +19,11 @@ import { devToolsConfigReadPermission, devToolsExternalDependenciesReadPermission, devToolsInfoReadPermission, - devToolsPermissions, } from '@backstage/plugin-devtools-common'; import { DevToolsBackendApi } from '../api'; import { NotAllowedError } from '@backstage/errors'; import Router from 'express-promise-router'; import express from 'express'; -import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; import { DiscoveryService, HttpAuthService, @@ -59,11 +57,6 @@ export async function createRouter( const router = Router(); router.use(express.json()); - router.use( - createPermissionIntegrationRouter({ - permissions: devToolsPermissions, - }), - ); router.get('/health', (_req, res) => { res.status(200).json({ status: 'ok' }); diff --git a/plugins/devtools-common/CHANGELOG.md b/plugins/devtools-common/CHANGELOG.md index ef20660a07..55ed84de0f 100644 --- a/plugins/devtools-common/CHANGELOG.md +++ b/plugins/devtools-common/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-devtools-common +## 0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + +## 0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.0 + - @backstage/types@1.2.1 + ## 0.1.16-next.0 ### Patch Changes diff --git a/plugins/devtools-common/package.json b/plugins/devtools-common/package.json index 7f5ec4e499..a5056edf41 100644 --- a/plugins/devtools-common/package.json +++ b/plugins/devtools-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-common", - "version": "0.1.16-next.0", + "version": "0.1.17", "description": "Common functionalities for the devtools plugin", "backstage": { "role": "common-library", diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index 267df2e952..e98835e625 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,154 @@ # @backstage/plugin-devtools +## 0.1.30-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.17 + - @backstage/plugin-permission-react@0.4.36 + +## 0.1.30-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.17 + - @backstage/plugin-permission-react@0.4.36 + +## 0.1.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.1.29 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-devtools-common@0.1.17 + - @backstage/plugin-permission-react@0.4.36 + +## 0.1.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-devtools-common@0.1.17-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-devtools-common@0.1.16 + - @backstage/plugin-permission-react@0.4.35 + +## 0.1.28 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.16 + - @backstage/plugin-permission-react@0.4.35 + +## 0.1.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.16 + - @backstage/plugin-permission-react@0.4.34 + +## 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 + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/core-compat-api@0.4.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.16 + +## 0.1.27-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-devtools-common@0.1.16-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + ## 0.1.27-next.2 ### Patch Changes diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 2a1c4890a1..44f4d7e105 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.27-next.2", + "version": "0.1.30-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "devtools", diff --git a/plugins/devtools/report-alpha.api.md b/plugins/devtools/report-alpha.api.md index 663c4be34d..764fe4079a 100644 --- a/plugins/devtools/report-alpha.api.md +++ b/plugins/devtools/report-alpha.api.md @@ -5,7 +5,9 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; @@ -24,22 +26,22 @@ const _default: FrontendPlugin< name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'nav-item:devtools': ExtensionDefinition<{ kind: 'nav-item'; name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { title: string; icon: IconComponent; @@ -65,9 +67,9 @@ const _default: FrontendPlugin< path?: string | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { @@ -76,7 +78,8 @@ const _default: FrontendPlugin< >; inputs: {}; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise; routeRef?: RouteRef; }; diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx index 959670f3a5..0f6d9ec4a1 100644 --- a/plugins/devtools/src/alpha/plugin.tsx +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -15,7 +15,6 @@ */ import { - createApiFactory, createFrontendPlugin, discoveryApiRef, fetchApiRef, @@ -34,8 +33,8 @@ import { rootRouteRef } from '../routes'; /** @alpha */ export const devToolsApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: devToolsApiRef, deps: { discoveryApi: discoveryApiRef, @@ -44,13 +43,12 @@ export const devToolsApi = ApiBlueprint.make({ factory: ({ discoveryApi, fetchApi }) => new DevToolsClient({ discoveryApi, fetchApi }), }), - }, }); /** @alpha */ export const devToolsPage = PageBlueprint.make({ params: { - defaultPath: '/devtools', + path: '/devtools', routeRef: convertLegacyRouteRef(rootRouteRef), loader: () => import('../components/DevToolsPage').then(m => @@ -71,6 +69,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 7af253f205..05cc56b910 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,81 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.4.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/types@1.2.1 + +## 0.4.13 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## 0.4.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.4.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + +## 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## 0.4.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.4.11-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index c006f80ba5..4c3131b4d3 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-next.1", + "version": "0.4.14-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md index 0beac8787c..38de0391fd 100644 --- a/plugins/events-backend-module-azure/CHANGELOG.md +++ b/plugins/events-backend-module-azure/CHANGELOG.md @@ -1,5 +1,69 @@ # @backstage/plugin-events-backend-module-azure +## 0.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + +## 0.2.22 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## 0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.2.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + +## 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-events-node@0.4.11 + +## 0.2.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.2.20-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index 79af151214..5fa78cee94 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-next.1", + "version": "0.2.23-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md index 8681c0540c..776e9b3158 100644 --- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,69 @@ # @backstage/plugin-events-backend-module-bitbucket-cloud +## 0.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + +## 0.2.22 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## 0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.2.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + +## 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-events-node@0.4.11 + +## 0.2.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.2.20-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index 62d39f6f0e..76bc9bc7af 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-next.1", + "version": "0.2.23-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-bitbucket-server/CHANGELOG.md b/plugins/events-backend-module-bitbucket-server/CHANGELOG.md index dea752d4d7..6f74c057cd 100644 --- a/plugins/events-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,69 @@ # @backstage/plugin-events-backend-module-bitbucket-server +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + +## 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-events-node@0.4.11 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.1.1-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-server/knip-report.md b/plugins/events-backend-module-bitbucket-server/knip-report.md new file mode 100644 index 0000000000..2661c35327 --- /dev/null +++ b/plugins/events-backend-module-bitbucket-server/knip-report.md @@ -0,0 +1,2 @@ +# Knip report + diff --git a/plugins/events-backend-module-bitbucket-server/package.json b/plugins/events-backend-module-bitbucket-server/package.json index bda209ef5a..c0c1fd613a 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-next.1", + "version": "0.1.4-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md index d19438a8c5..57a90d352b 100644 --- a/plugins/events-backend-module-gerrit/CHANGELOG.md +++ b/plugins/events-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,69 @@ # @backstage/plugin-events-backend-module-gerrit +## 0.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + +## 0.2.22 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## 0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.2.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + +## 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-events-node@0.4.11 + +## 0.2.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.2.20-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index 102e118552..d9e61ad1f7 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-next.1", + "version": "0.2.23-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md index db61a7eba0..f6ae7e3fce 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,106 @@ # @backstage/plugin-events-backend-module-github +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + +## 0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-events-node@0.4.12 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## 0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + +## 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 + +- ae249fc: **BREAKING**: Removed the `createGithubSignatureValidator` export. + + Added support webhook validation based on `integrations.github.[].apps.[].webhookSecret`. + +### Patch Changes + +- c7ef81c: Correct README installation instructions. +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## 0.4.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.4.0-next.2 ### Patch Changes diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index 8b38788c91..4a522338f3 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-next.2", + "version": "0.4.3-next.0", "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 2f509717be..d0fb34ddab 100644 --- a/plugins/events-backend-module-gitlab/CHANGELOG.md +++ b/plugins/events-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,77 @@ # @backstage/plugin-events-backend-module-gitlab +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + +## 0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/plugin-events-node@0.4.12 + +## 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 + +- a820df1: Adds support for `object_kind` field with priority over `event_name` on Gitlab webhook event types +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-events-node@0.4.11 + +## 0.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.3.1-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index 6af20dccdc..d44d7873f9 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-next.1", + "version": "0.3.4-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-google-pubsub/CHANGELOG.md b/plugins/events-backend-module-google-pubsub/CHANGELOG.md index c24077f931..d9321fcf7c 100644 --- a/plugins/events-backend-module-google-pubsub/CHANGELOG.md +++ b/plugins/events-backend-module-google-pubsub/CHANGELOG.md @@ -1,5 +1,91 @@ # @backstage/plugin-events-backend-module-google-pubsub +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.1.1 + +### Patch Changes + +- f983e99: Add an `EventConsumingGooglePubSubPublisher`, for pushing Backstage events to pubsub +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + +## 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 + +- af853ef: Added a module that is able to transfer messages from Google Pub/Sub subscriptions into the Backstage events system. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## 0.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.1.0-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-google-pubsub/README.md b/plugins/events-backend-module-google-pubsub/README.md index 67ddcfacb4..9752fa30c6 100644 --- a/plugins/events-backend-module-google-pubsub/README.md +++ b/plugins/events-backend-module-google-pubsub/README.md @@ -19,11 +19,24 @@ events: # The fully qualified name of the subscription subscriptionName: 'projects/my-google-project/subscriptions/github-enterprise-events' # The event system topic to transfer to. This can also be just a plain string - targetTopic: - # This example picks the topic name from a message attribute + a prefix - fromMessageAttribute: - attributeName: 'x-github-event' - withPrefix: 'github.' + targetTopic: 'github.{{ event.attributes.x-github-event }}' +``` + +The following configuration enables the transfer of events from a Backstage events topic into a Google +Pub/Sub topic. + +```yaml +events: + modules: + googlePubSub: + eventConsumingGooglePubSubPublisher: + subscriptions: + # A unique key for your subscription, to be used in logging and metrics + mySubscription: + # The source topic (or array of topics) + sourceTopic: 'github' + # The fully qualified name of the target topic + targetTopicName: 'projects/my-google-project/topics/github-enterprise-events' ``` ## Installation diff --git a/plugins/events-backend-module-google-pubsub/config.d.ts b/plugins/events-backend-module-google-pubsub/config.d.ts index 3e70489b9d..1097b71e00 100644 --- a/plugins/events-backend-module-google-pubsub/config.d.ts +++ b/plugins/events-backend-module-google-pubsub/config.d.ts @@ -90,6 +90,68 @@ export interface Config { }; }; }; + + /** + * Configuration for `EventConsumingGooglePubSubPublisher`, which + * consumes messages from the Backstage events system and forwards them + * into Google Pub/Sub topics. + */ + eventConsumingGooglePubSubPublisher?: { + subscriptions: { + [name: string]: { + /** + * The name of the events backend topic(s) that messages are + * consumed from. + */ + sourceTopic: string | string[]; + + /** + * The complete name of the Google Pub/Sub subscription to forward + * events to, on the form + * `projects/PROJECT_ID/topics/TOPIC_ID`. + * + * The value can contain placeholders on the form `{{ + * message.attributes.foo }}`, to mirror attribute `foo` as the + * whole or part of the topic name. + * + * @example + * + * This example expects the events topic to contain GitHub + * webhook events where the HTTP headers were mapped into + * event metadata fields. The outcome should be that messages + * end up on event topics such as `github.push`, + * `github.repository` etc which matches the [`@backstage/plugin-events-backend-module-github`](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github) structure. + * + * ```yaml + * targetTopic: 'projects/my-project/topics/github.{{ event.metadata.x-github-event }}' + * ``` + */ + targetTopicName: string; + + /** + * Event metadata fields are by default copied to the Pub/Sub + * message attribute. This setting allows you to override or amend + * those attributes. + * + * @remarks + * + * The values can contain placeholders on the form `{{ event.metadata.foo }}`, + * to mirror metadata field `foo` as the whole or part of a + * message attribute value. + * + * @example + * + * ```yaml + * messageAttributes: + * x-gitHub-event: '{{ event.metadata.event }}' + * ``` + */ + messageAttributes?: { + [key: string]: string; + }; + }; + }; + }; }; }; }; diff --git a/plugins/events-backend-module-google-pubsub/dev/index.ts b/plugins/events-backend-module-google-pubsub/dev/index.ts index 752217e6db..78a7e775cf 100644 --- a/plugins/events-backend-module-google-pubsub/dev/index.ts +++ b/plugins/events-backend-module-google-pubsub/dev/index.ts @@ -28,20 +28,21 @@ backend.add(import('../src')); backend.add( createBackendPlugin({ - pluginId: 'example-event-consumer', + pluginId: 'example-event-drivers', register(reg) { reg.registerInit({ deps: { events: eventsServiceRef, logger: coreServices.logger, + scheduler: coreServices.scheduler, }, - async init({ events, logger }) { - events.subscribe({ - id: 'example-subscription', - topics: ['example-topic'], + async init({ events, logger, scheduler }) { + await events.subscribe({ + id: 'inbox-subscription', + topics: ['inbox-topic'], async onEvent(event) { logger.info( - `Received event: ${JSON.stringify( + `Received inbox event: ${JSON.stringify( { topic: event.topic, payload: event.eventPayload, @@ -53,6 +54,24 @@ backend.add( ); }, }); + + await scheduler.scheduleTask({ + id: 'outbox-task', + scope: 'local', + frequency: { seconds: 5 }, + timeout: { seconds: 5 }, + fn: async () => { + await events.publish({ + topic: 'outbox-topic', + eventPayload: { + message: 'Hello, world!', + }, + metadata: { + source: 'outbox-task', + }, + }); + }, + }); }, }); }, diff --git a/plugins/events-backend-module-google-pubsub/knip-report.md b/plugins/events-backend-module-google-pubsub/knip-report.md new file mode 100644 index 0000000000..2661c35327 --- /dev/null +++ b/plugins/events-backend-module-google-pubsub/knip-report.md @@ -0,0 +1,2 @@ +# Knip report + diff --git a/plugins/events-backend-module-google-pubsub/package.json b/plugins/events-backend-module-google-pubsub/package.json index 6d8418eb59..43ee0df11a 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-next.1", + "version": "0.1.3-next.0", "description": "The google-pubsub backend module for the events plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/events-backend-module-google-pubsub/report.api.md b/plugins/events-backend-module-google-pubsub/report.api.md index bdb057d7fc..b42c1f80ff 100644 --- a/plugins/events-backend-module-google-pubsub/report.api.md +++ b/plugins/events-backend-module-google-pubsub/report.api.md @@ -6,6 +6,6 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; // @public -const eventsModuleGooglePubsubConsumingEventPublisher: BackendFeature; -export default eventsModuleGooglePubsubConsumingEventPublisher; +const _default: BackendFeature; +export default _default; ``` diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.test.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.test.ts new file mode 100644 index 0000000000..e5af68add7 --- /dev/null +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.test.ts @@ -0,0 +1,111 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { mockServices } from '@backstage/backend-test-utils'; +import { EventParams } from '@backstage/plugin-events-node'; +import { PubSub } from '@google-cloud/pubsub'; +import waitFor from 'wait-for-expect'; +import { EventConsumingGooglePubSubPublisher } from './EventConsumingGooglePubSubPublisher'; + +describe('EventConsumingGooglePubSubPublisher', () => { + const logger = mockServices.logger.mock(); + const events = mockServices.events.mock(); + + let onEvent: undefined | ((event: EventParams) => void); + events.subscribe.mockImplementation(async options => { + onEvent = options.onEvent; + }); + + const topic = { + publishMessage: jest.fn(), + }; + const pubSub = { + close: jest.fn(), + topic: jest.fn(() => topic), + }; + const pubSubFactory = jest.fn(() => pubSub as unknown as PubSub); + + beforeEach(() => { + onEvent = undefined; + jest.clearAllMocks(); + }); + + it('should go though the expected registration and flows', async () => { + const publisher = new EventConsumingGooglePubSubPublisher({ + logger, + events, + tasks: [ + { + id: 'my-id', + sourceTopics: ['my-topic'], + targetTopicPattern: 'projects/my-project/topics/my-topic', + mapToTopic: () => ({ project: 'my-project', topic: 'my-topic' }), + mapToAttributes: m => ({ ...m.metadata, more: 'yes' }), + }, + ], + pubSubFactory, + }); + + // Start up + + await publisher.start(); + + expect(events.subscribe).toHaveBeenCalledWith({ + id: 'EventConsumingGooglePubSubPublisher.my-id', + topics: ['my-topic'], + onEvent: expect.any(Function), + }); + + // Publish successfully + + onEvent!({ + topic: 'my-topic', + eventPayload: { foo: 'bar' }, + metadata: { extra: 'data', more: 'yes' }, + }); + + await waitFor(() => { + expect(pubSubFactory).toHaveBeenCalledWith('my-project'); + expect(pubSub.topic).toHaveBeenCalledWith('my-topic'); + expect(topic.publishMessage).toHaveBeenCalledWith({ + json: { foo: 'bar' }, + attributes: { extra: 'data', more: 'yes' }, + }); + }); + + // Simulate a failed publish + + topic.publishMessage.mockRejectedValueOnce(new Error('Failed to publish')); + + await expect( + onEvent!({ + topic: 'my-topic', + eventPayload: { foo: 'bar' }, + metadata: { extra: 'data', more: 'yes' }, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Failed to publish"`); + + expect(logger.error).toHaveBeenCalledWith( + 'Error publishing Google Pub/Sub message', + new Error('Failed to publish'), + ); + + // Shut down + + await publisher.stop(); + expect(pubSub.close).toHaveBeenCalled(); + }); +}); diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.ts new file mode 100644 index 0000000000..4a88bc2e79 --- /dev/null +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.ts @@ -0,0 +1,153 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + LoggerService, + RootConfigService, + RootLifecycleService, +} from '@backstage/backend-plugin-api'; +import { EventsService } from '@backstage/plugin-events-node'; +import { PubSub } from '@google-cloud/pubsub'; +import { Counter, metrics } from '@opentelemetry/api'; +import { readSubscriptionTasksFromConfig } from './config'; +import { SubscriptionTask } from './types'; + +/** + * Reads messages off of the events system and forwards them into Google Pub/Sub + * topics. + */ +export class EventConsumingGooglePubSubPublisher { + readonly #logger: LoggerService; + readonly #events: EventsService; + readonly #tasks: SubscriptionTask[]; + readonly #pubSubFactory: (projectId: string) => PubSub; + readonly #metrics: { messages: Counter }; + #activeClientsByProjectId: Map; + + static create(options: { + config: RootConfigService; + logger: LoggerService; + rootLifecycle: RootLifecycleService; + events: EventsService; + }) { + const publisher = new EventConsumingGooglePubSubPublisher({ + logger: options.logger, + events: options.events, + tasks: readSubscriptionTasksFromConfig(options.config), + pubSubFactory: projectId => new PubSub({ projectId }), + }); + + options.rootLifecycle.addStartupHook(async () => { + await publisher.start(); + }); + + options.rootLifecycle.addBeforeShutdownHook(async () => { + await publisher.stop(); + }); + + return publisher; + } + + constructor(options: { + logger: LoggerService; + events: EventsService; + tasks: SubscriptionTask[]; + pubSubFactory: (projectId: string) => PubSub; + }) { + this.#logger = options.logger; + this.#events = options.events; + this.#tasks = options.tasks; + this.#pubSubFactory = options.pubSubFactory; + + const meter = metrics.getMeter('default'); + this.#metrics = { + messages: meter.createCounter( + 'events.google.pubsub.publisher.messages.total', + { + description: + 'Number of Pub/Sub messages sent by EventConsumingGooglePubSubPublisher', + unit: 'short', + }, + ), + }; + + this.#activeClientsByProjectId = new Map(); + } + + async start() { + for (const task of this.#tasks) { + this.#logger.info( + `Starting publisher: id=${ + task.id + } sourceTopics=${task.sourceTopics.join(',')} targetTopic=${ + task.targetTopicPattern + }`, + ); + + await this.#events.subscribe({ + id: `EventConsumingGooglePubSubPublisher.${task.id}`, + topics: task.sourceTopics, + onEvent: async event => { + let status: 'success' | 'failed' | 'ignored' = 'failed'; + try { + const topic = task.mapToTopic(event); + if (!topic) { + status = 'ignored'; + return; + } + + let pubsub = this.#activeClientsByProjectId.get(topic.project); + if (!pubsub) { + pubsub = this.#pubSubFactory(topic.project); + this.#activeClientsByProjectId.set(topic.project, pubsub); + } + + await pubsub.topic(topic.topic).publishMessage({ + json: event.eventPayload, + attributes: task.mapToAttributes(event), + }); + + status = 'success'; + } catch (error) { + this.#logger.error( + 'Error publishing Google Pub/Sub message', + error, + ); + status = 'failed'; + throw error; + } finally { + this.#metrics.messages.add(1, { + subscription: task.id, + status: status, + }); + } + }, + }); + } + } + + async stop() { + const clients = Array.from(this.#activeClientsByProjectId.values()); + this.#activeClientsByProjectId = new Map(); + + await Promise.allSettled( + clients.map(async client => { + this.#logger.info(`Closing Google Pub/Sub client: ${client.projectId}`); + await client.close(); + }), + ); + } +} diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.test.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.test.ts new file mode 100644 index 0000000000..c3a5c1a5bb --- /dev/null +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.test.ts @@ -0,0 +1,196 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { mockServices } from '@backstage/backend-test-utils'; +import { readSubscriptionTasksFromConfig } from './config'; + +describe('readSubscriptionTasksFromConfig', () => { + it('reads with basic targetTopic', () => { + const data = { + events: { + modules: { + googlePubSub: { + eventConsumingGooglePubSubPublisher: { + subscriptions: { + subKey1: { + sourceTopic: 'my-topic', + targetTopicName: 'projects/pid/topics/tid', + }, + subKey2: { + sourceTopic: ['my-topic-1', 'my-topic-2'], + targetTopicName: 'projects/pid/topics/tid.{{ event.topic }}', + }, + }, + }, + }, + }, + }, + }; + + const result = readSubscriptionTasksFromConfig( + mockServices.rootConfig({ data }), + ); + + expect(result).toEqual([ + { + id: 'subKey1', + sourceTopics: ['my-topic'], + targetTopicPattern: 'projects/pid/topics/tid', + mapToTopic: expect.any(Function), + mapToAttributes: expect.any(Function), + }, + { + id: 'subKey2', + sourceTopics: ['my-topic-1', 'my-topic-2'], + targetTopicPattern: 'projects/pid/topics/tid.{{ event.topic }}', + mapToTopic: expect.any(Function), + mapToAttributes: expect.any(Function), + }, + ]); + + expect( + result[0].mapToTopic({ + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { attr: 'yes' }, + }), + ).toEqual({ project: 'pid', topic: 'tid' }); + expect( + result[0].mapToAttributes({ + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { attr: 'yes' }, + }), + ).toEqual({ attr: 'yes' }); + }); + + it('fills in placeholders', () => { + const data = { + events: { + modules: { + googlePubSub: { + eventConsumingGooglePubSubPublisher: { + subscriptions: { + sub1: { + sourceTopic: 'my-topic', + targetTopicName: 'projects/pid/topics/tid.{{ event.topic }}', + messageAttributes: { + attr1: 'updated.{{ event.metadata.exists }}', + attr2: 'updated.{{ event.metadata.missing }}', + }, + }, + sub2: { + sourceTopic: 'my-topic', + targetTopicName: + 'projects/pid/topics/tid.{{ event.metadata.missing }}', + messageAttributes: { + attr3: 'new', + }, + }, + }, + }, + }, + }, + }, + }; + + const result = readSubscriptionTasksFromConfig( + mockServices.rootConfig({ data }), + ); + + expect(result).toEqual([ + { + id: 'sub1', + sourceTopics: ['my-topic'], + targetTopicPattern: 'projects/pid/topics/tid.{{ event.topic }}', + mapToTopic: expect.any(Function), + mapToAttributes: expect.any(Function), + }, + { + id: 'sub2', + sourceTopics: ['my-topic'], + targetTopicPattern: + 'projects/pid/topics/tid.{{ event.metadata.missing }}', + mapToTopic: expect.any(Function), + mapToAttributes: expect.any(Function), + }, + ]); + + expect( + result[0].mapToTopic({ + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { exists: 'exists', attr1: 'original1', attr2: 'original2' }, + }), + ).toEqual({ project: 'pid', topic: 'tid.a' }); // Message attribute existed, successfully routed + expect( + result[0].mapToAttributes({ + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { exists: 'exists', attr1: 'original1', attr2: 'original2' }, + }), + ).toEqual({ + exists: 'exists', + attr1: 'updated.exists', // message attribute existed, was replaced + attr2: 'original2', // message attribute did not exist, was not replaced + }); + + expect( + result[1].mapToTopic({ + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { exists: 'exists', attr1: 'original1', attr2: 'original2' }, + }), + ).toBeUndefined(); // Message attribute did not exist, could not be routed + expect( + result[1].mapToAttributes({ + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { exists: 'exists', attr1: 'original1', attr2: 'original2' }, + }), + ).toEqual({ + exists: 'exists', + attr1: 'original1', + attr2: 'original2', + attr3: 'new', + }); + }); + + it('rejects malformed subscription name', () => { + const data = { + events: { + modules: { + googlePubSub: { + eventConsumingGooglePubSubPublisher: { + subscriptions: { + subKey: { + sourceTopic: 'sid', + targetTopicName: 'foo', + }, + }, + }, + }, + }, + }, + }; + + expect(() => + readSubscriptionTasksFromConfig(mockServices.rootConfig({ data })), + ).toThrowErrorMatchingInlineSnapshot( + `"Expected Google Pub/Sub 'targetTopicName' to be on the form 'projects/PROJECT_ID/topics/TOPIC_ID' but got 'foo'"`, + ); + }); +}); diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.ts new file mode 100644 index 0000000000..7ccd78cfcb --- /dev/null +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.ts @@ -0,0 +1,146 @@ +/* + * 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 { RootConfigService } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { InputError } from '@backstage/errors'; +import { EventParams } from '@backstage/plugin-events-node'; +import { createPatternResolver } from '../util/createPatternResolver'; +import { SubscriptionTask } from './types'; + +export function readSubscriptionTasksFromConfig( + rootConfig: RootConfigService, +): SubscriptionTask[] { + const subscriptionsConfig = rootConfig.getOptionalConfig( + 'events.modules.googlePubSub.eventConsumingGooglePubSubPublisher.subscriptions', + ); + if (!subscriptionsConfig) { + return []; + } + + return subscriptionsConfig.keys().map(subscriptionId => { + if (!subscriptionId.match(/^[-_\w]+$/)) { + throw new InputError( + `Expected Google Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`, + ); + } + + const config = subscriptionsConfig.getConfig(subscriptionId); + const sourceTopics = readSourceTopics(config); + const mapToTopic = readTopicMapper(config); + const mapToAttributes = readAttributeMapper(config); + + return { + id: subscriptionId, + sourceTopics: sourceTopics, + targetTopicPattern: config.getString('targetTopicName'), + mapToTopic, + mapToAttributes, + }; + }); +} + +function readSourceTopics(config: Config): string[] { + if (Array.isArray(config.getOptional('sourceTopic'))) { + return config.getStringArray('sourceTopic'); + } + return [config.getString('sourceTopic')]; +} + +/** + * Handles the `targetTopicName` configuration field. + */ +function readTopicMapper( + config: Config, +): (event: EventParams) => { project: string; topic: string } | undefined { + const regex = /^projects\/([^/]+)\/topics\/(.+)$/; + + const targetTopicPattern = config.getString('targetTopicName'); + let parts = targetTopicPattern.match(regex); + if (!parts) { + throw new InputError( + `Expected Google Pub/Sub 'targetTopicName' to be on the form 'projects/PROJECT_ID/topics/TOPIC_ID' but got '${targetTopicPattern}'`, + ); + } + + const patternResolver = createPatternResolver(targetTopicPattern); + + return event => { + try { + parts = patternResolver({ event }).match(regex); + if (!parts) { + return undefined; + } + return { + project: parts[1], + topic: parts[2], + }; + } catch { + // could not map to a topic + return undefined; + } + }; +} + +/** + * Handles the `messageAttributes` configuration field. + */ +function readAttributeMapper( + config: Config, +): (event: EventParams) => Record { + const setters = new Array< + (options: { + event: EventParams; + attributes: Record; + }) => void + >(); + + const eventMetadata = config.getOptionalConfig('messageAttributes'); + if (eventMetadata) { + for (const key of eventMetadata?.keys() ?? []) { + const valuePattern = eventMetadata.getString(key); + const patternResolver = createPatternResolver(valuePattern); + setters.push(({ event, attributes }) => { + try { + const value = patternResolver({ event }); + if (value) { + attributes[key] = value; + } + } catch { + // ignore silently, keep original + } + }); + } + } + + return event => { + const result: Record = {}; + for (const [key, value] of Object.entries(event.metadata ?? {})) { + if (value) { + if (typeof value === 'string') { + result[key] = value; + } else if (Array.isArray(value) && value.length > 0) { + // Google Pub/Sub does not support array values + result[key] = value.join(','); + } + } + } + for (const setter of setters) { + setter({ event, attributes: result }); + } + return result; + }; +} diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/index.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/index.ts new file mode 100644 index 0000000000..3e5713a28d --- /dev/null +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { eventsModuleEventConsumingGooglePubSubPublisher } from './module'; diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/module.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/module.ts new file mode 100644 index 0000000000..498996fee0 --- /dev/null +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/module.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 { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { eventsServiceRef } from '@backstage/plugin-events-node'; +import { EventConsumingGooglePubSubPublisher } from './EventConsumingGooglePubSubPublisher'; + +/** + * Reads messages off of the events system and forwards them into Google Pub/Sub + * topics. + * + * @public + */ +export const eventsModuleEventConsumingGooglePubSubPublisher = + createBackendModule({ + pluginId: 'events', + moduleId: 'event-consuming-google-pubsub-publisher', + register(reg) { + reg.registerInit({ + deps: { + config: coreServices.rootConfig, + logger: coreServices.logger, + rootLifecycle: coreServices.rootLifecycle, + events: eventsServiceRef, + }, + async init({ config, logger, rootLifecycle, events }) { + EventConsumingGooglePubSubPublisher.create({ + config, + logger, + rootLifecycle, + events, + }); + }, + }); + }, + }); diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/types.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/types.ts new file mode 100644 index 0000000000..36b3aeaa78 --- /dev/null +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/types.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { EventParams } from '@backstage/plugin-events-node'; + +/** + * A configured subscription task. + */ +export interface SubscriptionTask { + id: string; + sourceTopics: string[]; + targetTopicPattern: string; + mapToTopic: ( + event: EventParams, + ) => { project: string; topic: string } | undefined; + mapToAttributes: (event: EventParams) => Record; +} diff --git a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.test.ts b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.test.ts index bfe3c20773..b4034123f0 100644 --- a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.test.ts +++ b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.test.ts @@ -188,7 +188,7 @@ describe('readSubscriptionTasksFromConfig', () => { expect(() => readSubscriptionTasksFromConfig(mockServices.rootConfig({ data })), ).toThrowErrorMatchingInlineSnapshot( - `"Expected Googoe Pub/Sub 'subscriptionName' to be on the form 'projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID' but got 'sid'"`, + `"Expected Google Pub/Sub 'subscriptionName' to be on the form 'projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID' but got 'sid'"`, ); }); }); diff --git a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.ts b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.ts index fdcab2e2e5..d8dfb2ada2 100644 --- a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.ts +++ b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.ts @@ -34,7 +34,7 @@ export function readSubscriptionTasksFromConfig( return subscriptionsConfig.keys().map(subscriptionId => { if (!subscriptionId.match(/^[-_\w]+$/)) { throw new InputError( - `Expected Googoe Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`, + `Expected Google Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`, ); } @@ -63,7 +63,7 @@ function readSubscriptionName(config: Config): { ); if (!parts) { throw new InputError( - `Expected Googoe Pub/Sub 'subscriptionName' to be on the form 'projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID' but got '${subscriptionName}'`, + `Expected Google Pub/Sub 'subscriptionName' to be on the form 'projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID' but got '${subscriptionName}'`, ); } return { diff --git a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/module.ts b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/module.ts index 14cfdd2bd8..3d5fce14ee 100644 --- a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/module.ts +++ b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/module.ts @@ -24,8 +24,6 @@ import { GooglePubSubConsumingEventPublisher } from './GooglePubSubConsumingEven /** * Reads messages off of Google Pub/Sub subscriptions and forwards them into the * Backstage events system. - * - * @public */ export const eventsModuleGooglePubsubConsumingEventPublisher = createBackendModule({ diff --git a/plugins/events-backend-module-google-pubsub/src/index.ts b/plugins/events-backend-module-google-pubsub/src/index.ts index 2b79fcb9a1..0dcbe3c5d8 100644 --- a/plugins/events-backend-module-google-pubsub/src/index.ts +++ b/plugins/events-backend-module-google-pubsub/src/index.ts @@ -14,10 +14,19 @@ * limitations under the License. */ +import { createBackendFeatureLoader } from '@backstage/backend-plugin-api'; +import { eventsModuleEventConsumingGooglePubSubPublisher } from './EventConsumingGooglePubSubPublisher'; +import { eventsModuleGooglePubsubConsumingEventPublisher } from './GooglePubSubConsumingEventPublisher'; + /** * The google-pubsub backend module for the events plugin. * * @packageDocumentation */ -export { eventsModuleGooglePubsubConsumingEventPublisher as default } from './GooglePubSubConsumingEventPublisher'; +export default createBackendFeatureLoader({ + *loader() { + yield eventsModuleGooglePubsubConsumingEventPublisher; + yield eventsModuleEventConsumingGooglePubSubPublisher; + }, +}); diff --git a/plugins/events-backend-module-kafka/.eslintrc.js b/plugins/events-backend-module-kafka/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/events-backend-module-kafka/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/events-backend-module-kafka/CHANGELOG.md b/plugins/events-backend-module-kafka/CHANGELOG.md new file mode 100644 index 0000000000..910cff6130 --- /dev/null +++ b/plugins/events-backend-module-kafka/CHANGELOG.md @@ -0,0 +1,67 @@ +# @backstage/plugin-events-backend-module-kafka + +## 0.1.2-next.1 + +### Patch Changes + +- 0d38009: Remove luxon dependency and minor internal improvements + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/types@1.2.1 + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.1.0 + +### Minor Changes + +- b034b9d: Adds a new module `kafka` for plugin-events-backend + + The module introduces the `KafkaConsumerClient` which creates a Kafka client used to establish consumer connections. It also provides the `KafkaConsumingEventPublisher`, a consumer that subscribes to configured Kafka topics and publishes received messages to the Event Service. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + +## 0.1.0-next.0 + +### Minor Changes + +- b034b9d: Adds a new module `kafka` for plugin-events-backend + + The module introduces the `KafkaConsumerClient` which creates a Kafka client used to establish consumer connections. It also provides the `KafkaConsumingEventPublisher`, a consumer that subscribes to configured Kafka topics and publishes received messages to the Event Service. + +### 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 diff --git a/plugins/events-backend-module-kafka/README.md b/plugins/events-backend-module-kafka/README.md new file mode 100644 index 0000000000..dbc5bf5607 --- /dev/null +++ b/plugins/events-backend-module-kafka/README.md @@ -0,0 +1,78 @@ +# @backstage/backstage-plugin-events-backend-module-kafka + +Welcome to the `events-backend-module-kafka` backend module! + +This package is a module for the `events-backend` backend plugin and extends the events system with an `KafkaConsumingEventPublisher.` + +This event publisher will allow you to receive events from an Kafka queue and will publish these to the used `EventsService` implementation. + +## Configuration + +To set up Kafka queues, you need to configure the following values: + +```yaml +events: + modules: + kafka: + kafkaConsumingEventPublisher: + clientId: your-client-id # (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster. + brokers: # (Required) List of brokers in the Kafka cluster to connect to. + - broker1 + - broker2 + topics: + - topic: 'backstage.topic' # (Required) Replace with actual topic name as expected by subscribers + kafka: + topics: # (Required) The Kafka topics to subscribe to. + - topic1 + groupId: your-group-id # (Required) The GroupId to be used by the topic consumers. +``` + +For a complete list of all available fields that can be configured, refer to the [config.d.ts file](./config.d.ts). + +### Optional SSL Configuration + +If your Kafka cluster requires SSL, you can configure it as follows: + +```yaml +events: + modules: + kafka: + kafkaConsumingEventPublisher: + ssl: + rejectUnauthorized: true # (Optional) If true, the server certificate is verified against the list of supplied CAs. + ca: [path/to/ca-cert] # (Optional) Array of trusted certificates in PEM format. + key: path/to/client-key # (Optional) Private key in PEM format. + cert: path/to/client-cert # (Optional) Public x509 certificate in PEM format. +``` + +### Optional SASL Authentication Configuration + +If your Kafka cluster requires `SASL` authentication, you can configure it as follows: + +```yaml +events: + modules: + kafka: + kafkaConsumingEventPublisher: + sasl: + mechanism: 'plain' # SASL mechanism ('plain', 'scram-sha-256' or 'scram-sha-512') + username: your-username # SASL username + password: your-password # SASL password +``` + +This section includes optional `SSL` and `SASL` authentication configuration for enhanced security. + +## Installation + +1. Install this module +2. Add your configuration. + +```bash +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/plugin-events-backend-module-kafka +``` + +```typescript +// packages/backend/src/index.ts +backend.add(import('@backstage/plugin-events-backend-module-kafka')); +``` diff --git a/plugins/events-backend-module-kafka/catalog-info.yaml b/plugins/events-backend-module-kafka/catalog-info.yaml new file mode 100644 index 0000000000..72358ec3c3 --- /dev/null +++ b/plugins/events-backend-module-kafka/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-events-backend-module-kafka + title: '@backstage/plugin-events-backend-module-kafka' + description: The kafka backend module for the events plugin. +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/events-backend-module-kafka/config.d.ts b/plugins/events-backend-module-kafka/config.d.ts new file mode 100644 index 0000000000..49c417098c --- /dev/null +++ b/plugins/events-backend-module-kafka/config.d.ts @@ -0,0 +1,205 @@ +/* + * 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 { HumanDuration } from '@backstage/types'; + +export interface Config { + events?: { + modules?: { + /** + * events-backend-module-kafka plugin configuration. + */ + kafka?: { + /** + * Configuration for KafkaConsumingEventPublisher + */ + kafkaConsumingEventPublisher?: { + /** + * (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster. + */ + clientId: string; + /** + * (Required) List of brokers in the Kafka cluster to connect to. + */ + brokers: string[]; + /** + * Optional SSL connection parameters to connect to the cluster. Passed directly to Node tls.connect. + * See https://nodejs.org/dist/latest-v8.x/docs/api/tls.html#tls_tls_createsecurecontext_options + */ + ssl?: + | { + ca?: string[]; + /** @visibility secret */ + key?: string; + cert?: string; + rejectUnauthorized?: boolean; + } + | boolean; + /** + * Optional SASL connection parameters. + */ + sasl?: { + mechanism: 'plain' | 'scram-sha-256' | 'scram-sha-512'; + username: string; + /** @visibility secret */ + password: string; + }; + + /** + * Optional retry connection parameters. + */ + retry: { + /** + * (Optional) Maximum wait time for a retry + * Default: 30000 ms. + */ + maxRetryTime: HumanDuration | string; + + /** + * (Optional) Initial value used to calculate the retry (This is still randomized following the randomization factor) + * Default: 300 ms. + */ + initialRetryTime: HumanDuration | string; + + /** + * (Optional) Randomization factor + * Default: 0.2. + */ + factor: number; + + /** + * (Optional) Exponential factor + * Default: 2. + */ + multiplier: number; + + /** + * (Optional) Max number of retries per call + * Default: 5. + */ + retries: number; + }; + + /** + * (Optional) Timeout for authentication requests. + * Default: 10000 ms. + */ + authenticationTimeout: HumanDuration | string; + + /** + * (Optional) Time to wait for a successful connection. + * Default: 1000 ms. + */ + connectionTimeout: HumanDuration | string; + + /** + * (Optional) Time to wait for a successful request. + * Default: 30000 ms. + */ + requestTimeout: HumanDuration | string; + + /** + * (Optional) The request timeout can be disabled by setting enforceRequestTimeout to false. + * Default: true + */ + enforceRequestTimeout: boolean; + + /** + * Contains a object per topic for which an Kafka queue + * should be used as source of events. + */ + topics: Array<{ + /** + * (Required) The Backstage topic to publish to + */ + topic: string; + /** + * (Required) KafkaConsumer-related configuration. + */ + kafka: { + /** + * (Required) The Kafka topics to subscribe to + */ + topics: string[]; + /** + * (Required) The GroupId to be used by the topic consumers + */ + groupId: string; + + /** + * (Optional) Timeout used to detect failures. + * The consumer sends periodic heartbeats to indicate its liveness to the broker. + * If no heartbeats are received by the broker before the expiration of this session timeout, + * then the broker will remove this consumer from the group and initiate a rebalance + * Default: 30000 ms. + */ + sessionTimeout: HumanDuration | string; + + /** + * (Optional) The maximum time that the coordinator will wait for each member to rejoin when rebalancing the group + * Default: 60000 ms. + */ + rebalanceTimeout: HumanDuration | string; + + /** + * (Optional) The expected time between heartbeats to the consumer coordinator. + * Heartbeats are used to ensure that the consumer's session stays active. + * The value must be set lower than session timeout + * Default: 3000 ms. + */ + heartbeatInterval: HumanDuration | string; + + /** + * (Optional) The period of time after which we force a refresh of metadata + * even if we haven't seen any partition leadership changes to proactively discover any new brokers or partitions + * Default: 300000 ms (5 minutes). + */ + metadataMaxAge: HumanDuration | string; + + /** + * (Optional) The maximum amount of data per-partition the server will return. + * This size must be at least as large as the maximum message size the server allows + * or else it is possible for the producer to send messages larger than the consumer can fetch. + * If that happens, the consumer can get stuck trying to fetch a large message on a certain partition + * Default: 1048576 (1MB) + */ + maxBytesPerPartition: number; + + /** + * (Optional) Minimum amount of data the server should return for a fetch request, otherwise wait up to maxWaitTime for more data to accumulate. + * Default: 1 + */ + minBytes: number; + + /** + * (Optional) Maximum amount of bytes to accumulate in the response. Supported by Kafka >= 0.10.1.0 + * Default: 10485760 (10MB) + */ + maxBytes: number; + + /** + * (Optional) The maximum amount of time the server will block before answering the fetch request + * if there isn’t sufficient data to immediately satisfy the requirement given by minBytes + * Default: 5000 + */ + maxWaitTime: HumanDuration | string; + }; + }>; + }; + }; + }; + }; +} diff --git a/plugins/events-backend-module-kafka/knip-report.md b/plugins/events-backend-module-kafka/knip-report.md new file mode 100644 index 0000000000..2661c35327 --- /dev/null +++ b/plugins/events-backend-module-kafka/knip-report.md @@ -0,0 +1,2 @@ +# Knip report + diff --git a/plugins/events-backend-module-kafka/package.json b/plugins/events-backend-module-kafka/package.json new file mode 100644 index 0000000000..40ab742ebf --- /dev/null +++ b/plugins/events-backend-module-kafka/package.json @@ -0,0 +1,50 @@ +{ + "name": "@backstage/plugin-events-backend-module-kafka", + "version": "0.1.2-next.1", + "description": "The kafka backend module for the events plugin.", + "backstage": { + "role": "backend-plugin-module", + "pluginId": "events", + "pluginPackage": "@backstage/plugin-events-backend" + }, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/events-backend-module-kafka" + }, + "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist", + "config.d.ts" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/config": "workspace:^", + "@backstage/plugin-events-node": "workspace:^", + "@backstage/types": "workspace:^", + "kafkajs": "^2.2.4" + }, + "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "@backstage/plugin-events-backend-test-utils": "workspace:^" + }, + "configSchema": "config.d.ts" +} diff --git a/plugins/events-backend-module-kafka/report.api.md b/plugins/events-backend-module-kafka/report.api.md new file mode 100644 index 0000000000..a6b129b82e --- /dev/null +++ b/plugins/events-backend-module-kafka/report.api.md @@ -0,0 +1,11 @@ +## API Report File for "@backstage/plugin-events-backend-module-kafka" + +> 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'; + +// @public +const eventsModuleKafkaConsumingEventPublisher: BackendFeature; +export default eventsModuleKafkaConsumingEventPublisher; +``` diff --git a/plugins/events-backend-module-kafka/src/index.ts b/plugins/events-backend-module-kafka/src/index.ts new file mode 100644 index 0000000000..3ba6207d7d --- /dev/null +++ b/plugins/events-backend-module-kafka/src/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The module "kafka" for the Backstage backend plugin "events" + * adding an Kafka-based publisher, + * receiving events from an Kafka topic and passing it to the + * internal event broker. + * + * @packageDocumentation + */ + +export { eventsModuleKafkaConsumingEventPublisher as default } from './service/eventsModuleKafkaConsumingEventPublisher'; diff --git a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.test.ts b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.test.ts new file mode 100644 index 0000000000..5824bf4d41 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.test.ts @@ -0,0 +1,132 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { KafkaConsumerClient } from './KafkaConsumerClient'; +import { ConfigReader } from '@backstage/config'; +import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; +import { mockServices } from '@backstage/backend-test-utils'; + +jest.mock('kafkajs'); +jest.mock('./KafkaConsumingEventPublisher'); + +describe('KafkaConsumerClient', () => { + const mockLogger = mockServices.logger.mock(); + const mockEvents = mockServices.events.mock(); + + const mockConfig = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + topics: [ + { + topic: 'fake1', + kafka: { + topics: ['topic-A'], + groupId: 'my-group', + }, + }, + { + topic: 'fake2', + kafka: { + topics: ['topic-B'], + groupId: 'my-group', + }, + }, + ], + }, + }, + }, + }, + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should create an instance from config', () => { + const client = KafkaConsumerClient.fromConfig({ + config: mockConfig, + events: mockEvents, + logger: mockLogger, + }); + + expect(client).toBeInstanceOf(KafkaConsumerClient); + }); + + it('should not create an instance from config', () => { + const client = KafkaConsumerClient.fromConfig({ + config: new ConfigReader({}), + events: mockEvents, + logger: mockLogger, + }); + + expect(client).toBeUndefined(); + }); + + it('should create a consumer for each topic from config', () => { + KafkaConsumerClient.fromConfig({ + config: mockConfig, + events: mockEvents, + logger: mockLogger, + }); + + expect(KafkaConsumingEventPublisher.fromConfig).toHaveBeenCalledTimes(2); + }); + + it('should start all consumers', async () => { + const mockConsumer = { + start: jest.fn().mockResolvedValue(undefined), + }; + (KafkaConsumingEventPublisher.fromConfig as jest.Mock).mockReturnValue( + mockConsumer, + ); + + const client = KafkaConsumerClient.fromConfig({ + config: mockConfig, + events: mockEvents, + logger: mockLogger, + }); + + expect(client).toBeDefined(); + + await client?.start(); + + expect(mockConsumer.start).toHaveBeenCalled(); + }); + + it('should shutdown all consumers', async () => { + const mockConsumer = { + shutdown: jest.fn().mockResolvedValue(undefined), + }; + (KafkaConsumingEventPublisher.fromConfig as jest.Mock).mockReturnValue( + mockConsumer, + ); + + const client = KafkaConsumerClient.fromConfig({ + config: mockConfig, + events: mockEvents, + logger: mockLogger, + }); + + expect(client).toBeDefined(); + + await client?.shutdown(); + + expect(mockConsumer.shutdown).toHaveBeenCalled(); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.ts b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.ts new file mode 100644 index 0000000000..d506e4a6af --- /dev/null +++ b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.ts @@ -0,0 +1,77 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerService } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { EventsService } from '@backstage/plugin-events-node'; +import { Kafka } from 'kafkajs'; +import { KafkaEventSourceConfig, readConfig } from './config'; +import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; +import { loggerServiceAdapter } from './LoggerServiceAdapter'; + +/** + * KafkaConsumerClient + * + * This class creates the Kafka client that will be used to create the KafkaConsumingEventPublisher + */ +export class KafkaConsumerClient { + private readonly kafka: Kafka; + private readonly consumers: KafkaConsumingEventPublisher[]; + + static fromConfig(options: { + config: Config; + events: EventsService; + logger: LoggerService; + }): KafkaConsumerClient | undefined { + const kafkaConfig = readConfig(options.config); + + if (!kafkaConfig) { + options.logger.info( + 'Kafka consumer not configured, skipping initialization', + ); + return undefined; + } + + return new KafkaConsumerClient(options.logger, options.events, kafkaConfig); + } + + private constructor( + logger: LoggerService, + events: EventsService, + config: KafkaEventSourceConfig, + ) { + this.kafka = new Kafka({ + ...config.kafkaConfig, + logCreator: loggerServiceAdapter(logger), + }); + + this.consumers = config.kafkaConsumerConfigs.map(consumerConfig => + KafkaConsumingEventPublisher.fromConfig({ + kafkaClient: this.kafka, + config: consumerConfig, + logger, + events, + }), + ); + } + + async start(): Promise { + this.consumers.map(async consumer => await consumer.start()); + } + + async shutdown(): Promise { + this.consumers.map(async consumer => await consumer.shutdown()); + } +} diff --git a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.test.ts b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.test.ts new file mode 100644 index 0000000000..64c53595ad --- /dev/null +++ b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.test.ts @@ -0,0 +1,92 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; +import { Kafka } from 'kafkajs'; +import { KafkaConsumerConfig } from './config'; +import { mockServices } from '@backstage/backend-test-utils'; + +jest.mock('kafkajs'); + +describe('KafkaConsumingEventPublisher', () => { + const mockLogger = mockServices.logger.mock(); + const mockEvents = mockServices.events.mock(); + + const mockConsumer = { + connect: jest.fn(), + disconnect: jest.fn(), + subscribe: jest.fn(), + run: jest.fn(), + }; + + const mockKafkaClient = { + consumer: jest.fn().mockReturnValue(mockConsumer), + } as unknown as Kafka; + + const kafkaConsumerConfig: KafkaConsumerConfig = { + consumerConfig: { + groupId: 'test-group', + }, + consumerSubscribeTopics: { + topics: ['test-topic'], + }, + backstageTopic: 'backstage-topic', + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should create an instance from config', () => { + const consumer = KafkaConsumingEventPublisher.fromConfig({ + kafkaClient: mockKafkaClient, + config: kafkaConsumerConfig, + events: mockEvents, + logger: mockLogger, + }); + + expect(consumer).toBeInstanceOf(KafkaConsumingEventPublisher); + }); + + it('should start the consumer', async () => { + const consumer = KafkaConsumingEventPublisher.fromConfig({ + kafkaClient: mockKafkaClient, + config: kafkaConsumerConfig, + events: mockEvents, + logger: mockLogger, + }); + + await consumer.start(); + + expect(mockConsumer.connect).toHaveBeenCalled(); + expect(mockConsumer.subscribe).toHaveBeenCalledWith( + kafkaConsumerConfig.consumerSubscribeTopics, + ); + expect(mockConsumer.run).toHaveBeenCalled(); + }); + + it('should shutdown the consumer', async () => { + const consumer = KafkaConsumingEventPublisher.fromConfig({ + kafkaClient: mockKafkaClient, + config: kafkaConsumerConfig, + events: mockEvents, + logger: mockLogger, + }); + + await consumer.shutdown(); + + expect(mockConsumer.disconnect).toHaveBeenCalled(); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.ts b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.ts new file mode 100644 index 0000000000..f730696abe --- /dev/null +++ b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.ts @@ -0,0 +1,106 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerService } from '@backstage/backend-plugin-api'; +import { EventParams, EventsService } from '@backstage/plugin-events-node'; +import { Consumer, ConsumerSubscribeTopics, IHeaders, Kafka } from 'kafkajs'; +import { KafkaConsumerConfig } from './config'; + +type EventMetadata = EventParams['metadata']; + +/** + * This class subscribes to Kafka topics and publishes events received to the registered subscriber. + * The message payload will be used as the event payload and passed to the subscribers. + */ +export class KafkaConsumingEventPublisher { + private readonly kafkaConsumer: Consumer; + private readonly consumerSubscribeTopics: ConsumerSubscribeTopics; + private readonly backstageTopic: string; + private readonly logger: LoggerService; + + static fromConfig(env: { + kafkaClient: Kafka; + config: KafkaConsumerConfig; + events: EventsService; + logger: LoggerService; + }): KafkaConsumingEventPublisher { + return new KafkaConsumingEventPublisher( + env.kafkaClient, + env.logger, + env.events, + env.config, + ); + } + + private constructor( + kafkaClient: Kafka, + logger: LoggerService, + private readonly events: EventsService, + config: KafkaConsumerConfig, + ) { + this.kafkaConsumer = kafkaClient.consumer(config.consumerConfig); + this.consumerSubscribeTopics = config.consumerSubscribeTopics; + this.backstageTopic = config.backstageTopic; + const id = `events.kafka.publisher:${this.backstageTopic}`; + this.logger = logger.child({ + class: KafkaConsumingEventPublisher.prototype.constructor.name, + groupId: config.consumerConfig.groupId, + kafkaTopics: config.consumerSubscribeTopics.topics.toString(), + backstageTopic: config.backstageTopic, + taskId: id, + }); + } + + async start(): Promise { + try { + await this.kafkaConsumer.connect(); + + await this.kafkaConsumer.subscribe(this.consumerSubscribeTopics); + + await this.kafkaConsumer.run({ + eachMessage: async ({ message }) => { + this.events.publish({ + topic: this.backstageTopic, + eventPayload: JSON.parse(message.value?.toString()!), + metadata: this.convertHeadersToMetadata(message.headers), + }); + }, + }); + } catch (error: any) { + this.logger.error('Kafka consumer connection failed ', error); + } + } + + async shutdown(): Promise { + await this.kafkaConsumer.disconnect(); + } + + private convertHeadersToMetadata = ( + headers: IHeaders | undefined, + ): EventParams['metadata'] => { + if (!headers) return undefined; + + const metadata: EventMetadata = {}; + + Object.entries(headers).forEach(([key, value]) => { + // If value is an array use toString() on all values converting any Buffer types to valid strings + if (Array.isArray(value)) metadata[key] = value.map(v => v.toString()); + // Always return the values using toString() to catch all Buffer types that should be converted to strings + else metadata[key] = value?.toString(); + }); + + return metadata; + }; +} diff --git a/plugins/events-backend-module-kafka/src/publisher/LoggerServiceAdapter.ts b/plugins/events-backend-module-kafka/src/publisher/LoggerServiceAdapter.ts new file mode 100644 index 0000000000..5d635fb919 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/publisher/LoggerServiceAdapter.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerService } from '@backstage/backend-plugin-api'; +import { LogEntry, logLevel } from 'kafkajs'; + +export const loggerServiceAdapter = (loggerService: LoggerService) => { + const logMethods: Record void> = + { + [logLevel.ERROR]: loggerService.error, + [logLevel.WARN]: loggerService.warn, + [logLevel.INFO]: loggerService.info, + [logLevel.DEBUG]: loggerService.debug, + [logLevel.NOTHING]: () => {}, + }; + + return (_level: logLevel) => { + return (entry: LogEntry) => { + const { namespace, level, log } = entry; + const { message, ...extra } = log; + + // Use loggerService method that matches the level + logMethods[level].call( + loggerService, + `Kafka ${namespace} ${log.message}`, + { + ...extra, + }, + ); + }; + }; +}; diff --git a/plugins/events-backend-module-kafka/src/publisher/config.test.ts b/plugins/events-backend-module-kafka/src/publisher/config.test.ts new file mode 100644 index 0000000000..fc7e3049b0 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/publisher/config.test.ts @@ -0,0 +1,250 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ConfigReader } from '@backstage/config'; +import { readConfig } from './config'; + +describe('readConfig', () => { + it('not configured', () => { + const publisherConfigs = readConfig(new ConfigReader({})); + + expect(publisherConfigs).toBeUndefined(); + }); + + it('only required fields configured', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + topics: [ + { + topic: 'fake1', + kafka: { + topics: ['topic-A'], + groupId: 'my-group', + }, + }, + { + topic: 'fake2', + kafka: { + topics: ['topic-B'], + groupId: 'my-group', + }, + }, + ], + }, + }, + }, + }, + }); + + const publisherConfigs = readConfig(config); + + expect(publisherConfigs).toBeDefined(); + + expect(publisherConfigs?.kafkaConsumerConfigs.length).toBe(2); + + expect(publisherConfigs?.kafkaConfig.clientId).toEqual('backstage-events'); + expect(publisherConfigs?.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + expect(publisherConfigs?.kafkaConsumerConfigs[0].backstageTopic).toEqual( + 'fake1', + ); + expect( + publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.groupId, + ).toEqual('my-group'); + expect( + publisherConfigs?.kafkaConsumerConfigs[0].consumerSubscribeTopics.topics, + ).toEqual(['topic-A']); + }); + + it('all fields configured', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + ssl: true, + sasl: { + mechanism: 'plain', + username: 'username', + password: 'password', + }, + retry: { + maxRetryTime: { milliseconds: 20000 }, + initialRetryTime: { milliseconds: 200 }, + factor: '0.4', + multiplier: '4', + retries: '10', + }, + authenticationTimeout: { milliseconds: 20000 }, + connectionTimeout: { milliseconds: 1500 }, + requestTimeout: { milliseconds: 20000 }, + enforceRequestTimeout: false, + topics: [ + { + topic: 'fake1', + kafka: { + topics: ['topic-A'], + groupId: 'my-group', + sessionTimeout: { milliseconds: 20000 }, + rebalanceTimeout: { milliseconds: 50000 }, + heartbeatInterval: { milliseconds: 2000 }, + metadataMaxAge: { milliseconds: 400000 }, + maxBytesPerPartition: 50000, + minBytes: 2, + maxBytes: 500000, + maxWaitTime: { milliseconds: 4000 }, + }, + }, + { + topic: 'fake2', + kafka: { + topics: ['topic-B'], + groupId: 'my-group', + }, + }, + ], + }, + }, + }, + }, + }); + + const publisherConfigs = readConfig(config); + + expect(publisherConfigs).toBeDefined(); + + // Client configuration + expect(publisherConfigs?.kafkaConfig.clientId).toEqual('backstage-events'); + expect(publisherConfigs?.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + expect(publisherConfigs?.kafkaConfig.ssl).toBeTruthy(); + expect(publisherConfigs?.kafkaConfig.sasl).toStrictEqual({ + mechanism: 'plain', + username: 'username', + password: 'password', + }); + expect(publisherConfigs?.kafkaConfig.authenticationTimeout).toBe(20000); + expect(publisherConfigs?.kafkaConfig.connectionTimeout).toBe(1500); + expect(publisherConfigs?.kafkaConfig.requestTimeout).toBe(20000); + expect(publisherConfigs?.kafkaConfig.enforceRequestTimeout).toBeFalsy(); + expect(publisherConfigs?.kafkaConfig.retry).toStrictEqual({ + maxRetryTime: 20000, + initialRetryTime: 200, + factor: 0.4, + multiplier: 4, + retries: 10, + }); + + // Consumer configuration + expect(publisherConfigs?.kafkaConsumerConfigs.length).toBe(2); + expect(publisherConfigs?.kafkaConsumerConfigs[0].backstageTopic).toEqual( + 'fake1', + ); + expect( + publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.groupId, + ).toEqual('my-group'); + expect( + publisherConfigs?.kafkaConsumerConfigs[0].consumerSubscribeTopics.topics, + ).toEqual(['topic-A']); + + expect( + publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.sessionTimeout, + ).toBe(20000); + expect( + publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.rebalanceTimeout, + ).toBe(50000); + expect( + publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig + .heartbeatInterval, + ).toBe(2000); + expect( + publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.metadataMaxAge, + ).toBe(400000); + expect( + publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig + .maxBytesPerPartition, + ).toBe(50000); + expect( + publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.minBytes, + ).toBe(2); + expect( + publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.maxBytes, + ).toBe(500000); + expect( + publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.maxWaitTimeInMs, + ).toBe(4000); + }); + + it('should handle HumanDuration and string values for durations and timeouts', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + retry: { + maxRetryTime: { seconds: 1 }, + initialRetryTime: { minutes: 1 }, + factor: 0.4, + multiplier: 4, + retries: 10, + }, + authenticationTimeout: { hours: 1 }, + connectionTimeout: { days: 1 }, + topics: [], + requestTimeout: '1m', + }, + }, + }, + }, + }); + + const publisherConfigs = readConfig(config); + + expect(publisherConfigs).toBeDefined(); + + // Client configuration + expect(publisherConfigs?.kafkaConfig.clientId).toEqual('backstage-events'); + expect(publisherConfigs?.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + expect(publisherConfigs?.kafkaConfig.authenticationTimeout).toBe(3600000); + expect(publisherConfigs?.kafkaConfig.connectionTimeout).toBe(86400000); + expect(publisherConfigs?.kafkaConfig.requestTimeout).toBe(60000); + expect(publisherConfigs?.kafkaConfig.retry).toStrictEqual({ + maxRetryTime: 1000, + initialRetryTime: 60000, + factor: 0.4, + multiplier: 4, + retries: 10, + }); + + // Consumer configuration + expect(publisherConfigs?.kafkaConsumerConfigs.length).toBe(0); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/publisher/config.ts b/plugins/events-backend-module-kafka/src/publisher/config.ts new file mode 100644 index 0000000000..7534ce4d38 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/publisher/config.ts @@ -0,0 +1,153 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Config, readDurationFromConfig } from '@backstage/config'; +import { durationToMilliseconds } from '@backstage/types'; +import { ConsumerConfig, ConsumerSubscribeTopics, KafkaConfig } from 'kafkajs'; + +export interface KafkaConsumerConfig { + backstageTopic: string; + consumerConfig: ConsumerConfig; + consumerSubscribeTopics: ConsumerSubscribeTopics; +} + +export interface KafkaEventSourceConfig { + kafkaConfig: KafkaConfig; + kafkaConsumerConfigs: KafkaConsumerConfig[]; +} + +const CONFIG_PREFIX_PUBLISHER = + 'events.modules.kafka.kafkaConsumingEventPublisher'; + +/** + * Reads an optional HumanDuration from the config and returns the value in milliseconds if the key is defined. + * + * @param config - The configuration object to read from. + * @param key - The key to look up in the configuration. + * @returns The duration in milliseconds, or undefined if the key is not defined. + */ +const readOptionalHumanDurationInMs = ( + config: Config, + key: string, +): number | undefined => { + const humanDuration = config.has(key) + ? readDurationFromConfig(config, { key }) + : undefined; + + if (!humanDuration) return undefined; + + return durationToMilliseconds(humanDuration); +}; + +export const readConfig = ( + config: Config, +): KafkaEventSourceConfig | undefined => { + const kafkaConfig = config.getOptionalConfig(CONFIG_PREFIX_PUBLISHER); + + if (!kafkaConfig) { + return undefined; + } + + const clientId = kafkaConfig.getString('clientId'); + const brokers = kafkaConfig.getStringArray('brokers'); + + const authenticationTimeout = readOptionalHumanDurationInMs( + kafkaConfig, + 'authenticationTimeout', + ); + + const connectionTimeout = readOptionalHumanDurationInMs( + kafkaConfig, + 'connectionTimeout', + ); + const requestTimeout = readOptionalHumanDurationInMs( + kafkaConfig, + 'requestTimeout', + ); + const enforceRequestTimeout = kafkaConfig.getOptionalBoolean( + 'enforceRequestTimeout', + ); + + const ssl = kafkaConfig.getOptional('ssl') as KafkaConfig['ssl']; + const sasl = kafkaConfig.getOptional('sasl') as KafkaConfig['sasl']; + + const retry: KafkaConfig['retry'] = { + maxRetryTime: readOptionalHumanDurationInMs( + kafkaConfig, + 'retry.maxRetryTime', + ), + initialRetryTime: readOptionalHumanDurationInMs( + kafkaConfig, + 'retry.initialRetryTime', + ), + factor: kafkaConfig.getOptionalNumber('retry.factor'), + multiplier: kafkaConfig.getOptionalNumber('retry.multiplier'), + retries: kafkaConfig.getOptionalNumber('retry.retries'), + }; + + const kafkaConsumerConfigs: KafkaConsumerConfig[] = kafkaConfig + .getConfigArray('topics') + .map(topic => { + return { + backstageTopic: topic.getString('topic'), + consumerConfig: { + groupId: topic.getString('kafka.groupId'), + sessionTimeout: readOptionalHumanDurationInMs( + topic, + 'kafka.sessionTimeout', + ), + rebalanceTimeout: readOptionalHumanDurationInMs( + topic, + 'kafka.rebalanceTimeout', + ), + heartbeatInterval: readOptionalHumanDurationInMs( + topic, + 'kafka.heartbeatInterval', + ), + metadataMaxAge: readOptionalHumanDurationInMs( + topic, + 'kafka.metadataMaxAge', + ), + maxBytesPerPartition: topic.getOptionalNumber( + 'kafka.maxBytesPerPartition', + ), + minBytes: topic.getOptionalNumber('kafka.minBytes'), + maxBytes: topic.getOptionalNumber('kafka.maxBytes'), + maxWaitTimeInMs: readOptionalHumanDurationInMs( + topic, + 'kafka.maxWaitTime', + ), + }, + consumerSubscribeTopics: { + topics: topic.getStringArray('kafka.topics'), + }, + }; + }); + + return { + kafkaConfig: { + clientId, + brokers, + ssl, + sasl, + authenticationTimeout, + connectionTimeout, + requestTimeout, + enforceRequestTimeout, + retry, + }, + kafkaConsumerConfigs, + }; +}; diff --git a/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.test.ts b/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.test.ts new file mode 100644 index 0000000000..816793f635 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.test.ts @@ -0,0 +1,85 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createServiceFactory } from '@backstage/backend-plugin-api'; +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { eventsServiceRef } from '@backstage/plugin-events-node'; +import { TestEventsService } from '@backstage/plugin-events-backend-test-utils'; +import { eventsModuleKafkaConsumingEventPublisher } from './eventsModuleKafkaConsumingEventPublisher'; +import { KafkaConsumerClient } from '../publisher/KafkaConsumerClient'; + +jest.mock('../publisher/KafkaConsumerClient'); + +describe('eventsModuleKafkaConsumingEventPublisher', () => { + it('should be correctly wired and set up', async () => { + const events = new TestEventsService(); + const eventsServiceFactory = createServiceFactory({ + service: eventsServiceRef, + deps: {}, + async factory({}) { + return events; + }, + }); + + const mockKafkaConsumerClient = { + start: jest.fn(), + shutdown: jest.fn(), + }; + (KafkaConsumerClient.fromConfig as jest.Mock).mockReturnValue( + mockKafkaConsumerClient, + ); + + await startTestBackend({ + features: [ + eventsServiceFactory, + eventsModuleKafkaConsumingEventPublisher, + mockServices.rootConfig.factory({ + data: { + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + topics: { + fake1: { + kafka: { + topics: ['topic-A'], + groupId: 'my-group', + }, + }, + fake2: { + kafka: { + topics: ['topic-B'], + groupId: 'my-group', + }, + }, + }, + }, + }, + }, + }, + }, + }), + ], + }); + + // Verify that the Kafka consumer client was started + expect(mockKafkaConsumerClient.start).toHaveBeenCalled(); + + // Verify that the shutdown hook was registered + expect(mockKafkaConsumerClient.shutdown).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.ts b/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.ts new file mode 100644 index 0000000000..b524454ca0 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { KafkaConsumerClient } from '../publisher/KafkaConsumerClient'; +import { eventsServiceRef } from '@backstage/plugin-events-node'; + +/** + * Kafka module for the Events plugin. + * + * @public + */ +export const eventsModuleKafkaConsumingEventPublisher = createBackendModule({ + pluginId: 'events', + moduleId: 'kafka-consuming-event-publisher', + register(env) { + env.registerInit({ + deps: { + config: coreServices.rootConfig, + events: eventsServiceRef, + logger: coreServices.logger, + lifecycle: coreServices.lifecycle, + }, + async init({ config, logger, events, lifecycle }) { + const kafka = KafkaConsumerClient.fromConfig({ + config, + events, + logger, + }); + + if (!kafka) { + return; + } + + await kafka.start(); + + lifecycle.addShutdownHook(async () => await kafka.shutdown()); + }, + }); + }, +}); diff --git a/plugins/events-backend-test-utils/CHANGELOG.md b/plugins/events-backend-test-utils/CHANGELOG.md index 2827335a14..7a5f9596d8 100644 --- a/plugins/events-backend-test-utils/CHANGELOG.md +++ b/plugins/events-backend-test-utils/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/plugin-events-backend-test-utils +## 0.1.47-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.14-next.0 + +## 0.1.46 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.13 + +## 0.1.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.1.45 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.12 + +## 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 + +- Updated dependencies + - @backstage/plugin-events-node@0.4.11 + +## 0.1.44-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.1.44-next.1 ### Patch Changes diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json index 8edb3f30f5..3a63eaaff9 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-next.1", + "version": "0.1.47-next.0", "description": "The plugin-events-backend-test-utils for @backstage/plugin-events-node", "backstage": { "role": "node-library", diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md index b41cb3e07f..5ac34b864d 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,95 @@ # @backstage/plugin-events-backend +## 0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-openapi-utils@0.5.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-events-node@0.4.13 + +## 0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/backend-openapi-utils@0.5.5-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.5.3 + +### Patch Changes + +- c83cd8b: Fixed some circular or otherwise unclear imports +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/backend-openapi-utils@0.5.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + +## 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/backend-openapi-utils@0.5.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## 0.5.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.5.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.5.2-next.1 ### Patch Changes diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index f41794fc68..5ab50ffe27 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-next.1", + "version": "0.5.5-next.0", "backstage": { "role": "backend-plugin", "pluginId": "events", diff --git a/plugins/events-backend/src/schema/openapi/generated/router.ts b/plugins/events-backend/src/schema/openapi/generated/router.ts index 3c43880170..d2cedd2166 100644 --- a/plugins/events-backend/src/schema/openapi/generated/router.ts +++ b/plugins/events-backend/src/schema/openapi/generated/router.ts @@ -18,7 +18,7 @@ // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** import { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage/backend-openapi-utils'; -import { EndpointMap } from './'; +import { EndpointMap } from './apis'; export const spec = { openapi: '3.0.3', diff --git a/plugins/events-backend/src/service/EventsPlugin.test.ts b/plugins/events-backend/src/service/EventsPlugin.test.ts index 12a0f63002..2c3fb3b909 100644 --- a/plugins/events-backend/src/service/EventsPlugin.test.ts +++ b/plugins/events-backend/src/service/EventsPlugin.test.ts @@ -34,6 +34,8 @@ import { TestEventsService } from '@backstage/plugin-events-backend-test-utils'; import request from 'supertest'; import { eventsPlugin } from './EventsPlugin'; +jest.setTimeout(60_000); + describe('eventsPlugin', () => { it('should be initialized properly', async () => { const eventsService = new TestEventsService(); diff --git a/plugins/events-backend/src/service/hub/DatabaseEventBusStore.test.ts b/plugins/events-backend/src/service/hub/DatabaseEventBusStore.test.ts index 959341cbc0..5664475851 100644 --- a/plugins/events-backend/src/service/hub/DatabaseEventBusStore.test.ts +++ b/plugins/events-backend/src/service/hub/DatabaseEventBusStore.test.ts @@ -21,6 +21,8 @@ import { } from '@backstage/backend-test-utils'; import { DatabaseEventBusStore } from './DatabaseEventBusStore'; +jest.setTimeout(60_000); + const logger = mockServices.logger.mock(); const databases = TestDatabases.create({ diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md index e7d8de11e2..b35a20ad64 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/plugin-events-node +## 0.4.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + +## 0.4.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + +## 0.4.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.4.11-next.1 ### Patch Changes diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index 4753149051..cb82c8e79e 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-next.1", + "version": "0.4.14-next.0", "description": "The plugin-events-node module for @backstage/plugin-events-backend", "backstage": { "role": "node-library", diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index e55e25e9c0..7dafaa0d1c 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,66 @@ # @internal/plugin-todo-list-backend +## 1.0.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + +## 1.0.41 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + +## 1.0.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + +## 1.0.40 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + +## 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + +## 1.0.39-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 + ## 1.0.39-next.1 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 1317b7dfd2..85aa64ec63 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-next.1", + "version": "1.0.42-next.0", "backstage": { "role": "backend-plugin", "pluginId": "todo-list", diff --git a/plugins/example-todo-list-common/CHANGELOG.md b/plugins/example-todo-list-common/CHANGELOG.md index f5f994be43..72a97d5d14 100644 --- a/plugins/example-todo-list-common/CHANGELOG.md +++ b/plugins/example-todo-list-common/CHANGELOG.md @@ -1,5 +1,26 @@ # @internal/plugin-todo-list-common +## 1.0.26 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + +## 1.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + +## 1.0.25 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.0 + ## 1.0.25-next.0 ### Patch Changes diff --git a/plugins/example-todo-list-common/package.json b/plugins/example-todo-list-common/package.json index 80e647bfe7..3858b463dc 100644 --- a/plugins/example-todo-list-common/package.json +++ b/plugins/example-todo-list-common/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-common", - "version": "1.0.25-next.0", + "version": "1.0.26", "backstage": { "role": "common-library", "pluginId": "todo-list", diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index 9af56aa38d..dd15458823 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,61 @@ # @internal/plugin-todo-list +## 1.0.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + +## 1.0.41 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + +## 1.0.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## 1.0.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + +## 1.0.40 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + +## 1.0.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-plugin-api@1.10.7 + +## 1.0.39 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + ## 1.0.39-next.1 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index 620f8ad05b..b893715607 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list", - "version": "1.0.39-next.1", + "version": "1.0.42-next.0", "backstage": { "role": "frontend-plugin", "pluginId": "todo-list", diff --git a/plugins/gateway-backend/CHANGELOG.md b/plugins/gateway-backend/CHANGELOG.md index 5af5109613..4ac9c38af1 100644 --- a/plugins/gateway-backend/CHANGELOG.md +++ b/plugins/gateway-backend/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/plugin-gateway-backend +## 1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 1.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + +## 1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + +## 1.0.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + +## 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + +## 1.0.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + ## 1.0.1-next.1 ### Patch Changes diff --git a/plugins/gateway-backend/knip-report.md b/plugins/gateway-backend/knip-report.md new file mode 100644 index 0000000000..2661c35327 --- /dev/null +++ b/plugins/gateway-backend/knip-report.md @@ -0,0 +1,2 @@ +# Knip report + diff --git a/plugins/gateway-backend/package.json b/plugins/gateway-backend/package.json index bb898cedd5..01240b51aa 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-next.1", + "version": "1.0.4-next.0", "backstage": { "role": "backend-plugin", "pluginId": "gateway", diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index 06a3cdff85..1cd70dc92b 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,88 @@ # @backstage/plugin-home-react +## 0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + +## 0.1.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + +## 0.1.27 + +### Patch Changes + +- c83cd8b: Fixed some circular or otherwise unclear imports +- cef60db: Home plugin support i18n +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + +## 0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/core-plugin-api@1.10.7 + +## 0.1.26 + +### Patch Changes + +- 16eb4bf: Export ContentModal from `@backstage/plugin-home-react` so people can use this in other scenarios. + Renamed `CatalogReactComponentsNameToClassKey` to `PluginHomeComponentsNameToClassKey` in `overridableComponents.ts` + + Made QuickStartCard `docsLinkTitle` prop more flexible to allow for any React.JSX.Element instead of just a string. + Added QuickStartCard prop `additionalContent` which can eventually replace the prop `video`. + +- Updated dependencies + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + +## 0.1.26-next.2 + +### Patch Changes + +- 16eb4bf: Export ContentModal from `@backstage/plugin-home-react` so people can use this in other scenarios. + Renamed `CatalogReactComponentsNameToClassKey` to `PluginHomeComponentsNameToClassKey` in `overridableComponents.ts` + + Made QuickStartCard `docsLinkTitle` prop more flexible to allow for any React.JSX.Element instead of just a string. + Added QuickStartCard prop `additionalContent` which can eventually replace the prop `video`. + +- Updated dependencies + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + ## 0.1.26-next.1 ### Patch Changes diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 43f4e79aed..9e3b697123 100644 --- a/plugins/home-react/package.json +++ b/plugins/home-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home-react", - "version": "0.1.26-next.1", + "version": "0.1.29-next.0", "description": "A Backstage plugin that contains react components helps you build a home page", "backstage": { "role": "web-library", @@ -11,9 +11,7 @@ ] }, "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" }, "keywords": [ "backstage", @@ -27,8 +25,23 @@ }, "license": "Apache-2.0", "sideEffects": false, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", + "./package.json": "./package.json" + }, "main": "src/index.ts", "types": "src/index.ts", + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.ts" + ], + "package.json": [ + "package.json" + ] + } + }, "files": [ "dist" ], @@ -44,6 +57,7 @@ "dependencies": { "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@rjsf/utils": "5.23.2" diff --git a/plugins/home-react/report-alpha.api.md b/plugins/home-react/report-alpha.api.md new file mode 100644 index 0000000000..fad4c5a2bc --- /dev/null +++ b/plugins/home-react/report-alpha.api.md @@ -0,0 +1,19 @@ +## API Report File for "@backstage/plugin-home-react" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { TranslationRef } from '@backstage/frontend-plugin-api'; + +// @alpha (undocumented) +export const homeReactTranslationRef: TranslationRef< + 'home-react', + { + readonly 'settingsModal.title': 'Settings'; + readonly 'settingsModal.closeButtonTitle': 'Close'; + readonly 'cardExtension.settingsButtonTitle': 'Settings'; + } +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/home-react/report.api.md b/plugins/home-react/report.api.md index c7e847fb36..26ef6267aa 100644 --- a/plugins/home-react/report.api.md +++ b/plugins/home-react/report.api.md @@ -5,9 +5,19 @@ ```ts import { Extension } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react/jsx-runtime'; +import { JSX as JSX_3 } from 'react'; +import { Overrides } from '@material-ui/core/styles/overrides'; import { RJSFSchema } from '@rjsf/utils'; +import { StyleRules } from '@material-ui/core/styles/withStyles'; import { UiSchema } from '@rjsf/utils'; +// @public (undocumented) +export type BackstageOverrides = Overrides & { + [Name in keyof PluginHomeComponentsNameToClassKey]?: Partial< + StyleRules + >; +}; + // @public (undocumented) export type CardConfig = { layout?: CardLayout; @@ -52,6 +62,15 @@ export type ComponentRenderer = { Renderer?: (props: RendererProps) => JSX.Element; }; +// @public +export const ContentModal: (props: ContentModalProps) => JSX_2.Element; + +// @public +export type ContentModalProps = { + modalContent: JSX_3.Element; + linkContent: string | JSX_3.Element; +}; + // @public export function createCardExtension(options: { title?: string; @@ -62,6 +81,14 @@ export function createCardExtension(options: { settings?: CardSettings; }): Extension<(props: CardExtensionProps) => JSX_2.Element>; +// @public (undocumented) +export type PluginHomeComponentsNameToClassKey = { + PluginHomeContentModal: PluginHomeContentModalClassKey; +}; + +// @public (undocumented) +export type PluginHomeContentModalClassKey = 'contentModal' | 'linkText'; + // @public (undocumented) export type RendererProps = { title?: string; diff --git a/canon-docs/src/components/Columns/index.ts b/plugins/home-react/src/alpha.ts similarity index 85% rename from canon-docs/src/components/Columns/index.ts rename to plugins/home-react/src/alpha.ts index 05de0850be..3a08699f66 100644 --- a/canon-docs/src/components/Columns/index.ts +++ b/plugins/home-react/src/alpha.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { Columns } from './Columns'; +export { homeReactTranslationRef } from './translation'; diff --git a/plugins/home/src/homePageComponents/QuickStart/ContentModal.tsx b/plugins/home-react/src/components/ContentModal.tsx similarity index 71% rename from plugins/home/src/homePageComponents/QuickStart/ContentModal.tsx rename to plugins/home-react/src/components/ContentModal.tsx index ff917f367c..cde317e248 100644 --- a/plugins/home/src/homePageComponents/QuickStart/ContentModal.tsx +++ b/plugins/home-react/src/components/ContentModal.tsx @@ -18,14 +18,43 @@ import { JSX, useState } from 'react'; import { Link } from '@backstage/core-components'; import Modal from '@material-ui/core/Modal'; import Box from '@material-ui/core/Box'; +import { makeStyles, Theme } from '@material-ui/core/styles'; -import { useStyles } from './styles'; +/** @public */ +export type PluginHomeContentModalClassKey = 'contentModal' | 'linkText'; +export const useStyles = makeStyles( + (theme: Theme) => ({ + contentModal: { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '80%', + height: 'auto', + }, + linkText: { + marginBottom: theme.spacing(1.5), + }, + }), + { name: 'PluginHomeContentModal' }, +); + +/** + * Props customizing the component. + * + * @public + */ export type ContentModalProps = { modalContent: JSX.Element; linkContent: string | JSX.Element; }; +/** + * A component to expand given content into a full screen modal. + * + * @public + */ export const ContentModal = (props: ContentModalProps) => { const { modalContent, linkContent } = props; const styles = useStyles(); diff --git a/plugins/home-react/src/components/SettingsModal.tsx b/plugins/home-react/src/components/SettingsModal.tsx index 4a6aa46198..37b8d103c2 100644 --- a/plugins/home-react/src/components/SettingsModal.tsx +++ b/plugins/home-react/src/components/SettingsModal.tsx @@ -14,11 +14,13 @@ * limitations under the License. */ +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Button from '@material-ui/core/Button'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import DialogTitle from '@material-ui/core/DialogTitle'; +import { homeReactTranslationRef } from '../translation'; /** @public */ export const SettingsModal = (props: { @@ -28,16 +30,18 @@ export const SettingsModal = (props: { children: JSX.Element; }) => { const { open, close, componentName, children } = props; - + const { t } = useTranslationRef(homeReactTranslationRef); return ( close()}> - {componentName ? `Settings - ${componentName}` : 'Settings'} + {componentName + ? `${t('settingsModal.title')} - ${componentName}` + : t('settingsModal.title')} {children} diff --git a/plugins/home-react/src/components/index.ts b/plugins/home-react/src/components/index.ts index dce6968b36..8a982075eb 100644 --- a/plugins/home-react/src/components/index.ts +++ b/plugins/home-react/src/components/index.ts @@ -15,3 +15,7 @@ */ export { SettingsModal } from './SettingsModal'; +export { ContentModal } from './ContentModal'; + +export type { PluginHomeContentModalClassKey } from './ContentModal'; +export type { ContentModalProps } from './ContentModal'; diff --git a/plugins/home-react/src/extensions.tsx b/plugins/home-react/src/extensions.tsx index 610c76bb0e..7795e20c73 100644 --- a/plugins/home-react/src/extensions.tsx +++ b/plugins/home-react/src/extensions.tsx @@ -21,6 +21,8 @@ import { InfoCard } from '@backstage/core-components'; import { SettingsModal } from './components'; import { createReactExtension, useApp } from '@backstage/core-plugin-api'; import { RJSFSchema, UiSchema } from '@rjsf/utils'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeReactTranslationRef } from './translation'; /** * @public @@ -133,6 +135,7 @@ function CardExtension(props: CardExtensionComponentProps) { const app = useApp(); const { Progress } = app.getComponents(); const [settingsOpen, setSettingsOpen] = useState(false); + const { t } = useTranslationRef(homeReactTranslationRef); if (Renderer) { return ( @@ -158,7 +161,9 @@ function CardExtension(props: CardExtensionComponentProps) { ? { action: ( setSettingsOpen(true)}> - Settings + + {t('cardExtension.settingsButtonTitle')} + ), } diff --git a/plugins/home-react/src/index.ts b/plugins/home-react/src/index.ts index 1f02ded1c6..54602dc814 100644 --- a/plugins/home-react/src/index.ts +++ b/plugins/home-react/src/index.ts @@ -30,3 +30,4 @@ export type { CardSettings, CardConfig, } from './extensions'; +export * from './overridableComponents'; diff --git a/packages/canon/src/components/IconButton/IconButton.props.ts b/plugins/home-react/src/overridableComponents.ts similarity index 50% rename from packages/canon/src/components/IconButton/IconButton.props.ts rename to plugins/home-react/src/overridableComponents.ts index e584997ef5..3653257507 100644 --- a/packages/canon/src/components/IconButton/IconButton.props.ts +++ b/plugins/home-react/src/overridableComponents.ts @@ -14,28 +14,23 @@ * limitations under the License. */ -import type { PropDef, GetPropDefTypes } from '../../props/prop-def'; +import { Overrides } from '@material-ui/core/styles/overrides'; +import { StyleRules } from '@material-ui/core/styles/withStyles'; +import { PluginHomeContentModalClassKey } from './components/ContentModal'; /** @public */ -export const iconButtonPropDefs = { - variant: { - type: 'enum', - values: ['primary', 'secondary'], - className: 'canon-Button--variant', - default: 'primary', - responsive: true, - }, - size: { - type: 'enum', - values: ['small', 'medium'], - className: 'canon-Button--size', - default: 'medium', - responsive: true, - }, -} satisfies { - variant: PropDef<'primary' | 'secondary'>; - size: PropDef<'small' | 'medium'>; +export type PluginHomeComponentsNameToClassKey = { + PluginHomeContentModal: PluginHomeContentModalClassKey; }; /** @public */ -export type IconButtonOwnProps = GetPropDefTypes; +export type BackstageOverrides = Overrides & { + [Name in keyof PluginHomeComponentsNameToClassKey]?: Partial< + StyleRules + >; +}; + +declare module '@backstage/theme' { + interface OverrideComponentNameToClassKeys + extends PluginHomeComponentsNameToClassKey {} +} diff --git a/plugins/home-react/src/translation.ts b/plugins/home-react/src/translation.ts new file mode 100644 index 0000000000..8de5bb1ff1 --- /dev/null +++ b/plugins/home-react/src/translation.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createTranslationRef } from '@backstage/frontend-plugin-api'; + +/** + * @alpha + */ +export const homeReactTranslationRef = createTranslationRef({ + id: 'home-react', + messages: { + settingsModal: { + title: 'Settings', + closeButtonTitle: 'Close', + }, + cardExtension: { + settingsButtonTitle: 'Settings', + }, + }, +}); diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index 73eb07ffd7..58cb407fdb 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,238 @@ # @backstage/plugin-home +## 0.8.11-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- 121899a: **BREAKING ALPHA**: The `app-root-element` extension now only accepts `JSX.Element` in its `element` param, meaning overrides need to be updated. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.8-next.0 + - @backstage/plugin-home-react@0.1.29-next.0 + +## 0.8.11-next.1 + +### Patch Changes + +- f16d380: Add the missing Visits API to the alpha plugin, fixing a crash due to the API not being installed. +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-home-react@0.1.29-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + +## 0.8.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.8.10 + +### Patch Changes + +- d52d67f: Added a New Frontend System App Root Element for the `` component +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/core-app-api@1.18.0 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-home-react@0.1.28 + +## 0.8.10-next.2 + +### Patch Changes + +- d52d67f: Added a New Frontend System App Root Element for the `` component +- Updated dependencies + - @backstage/theme@0.6.7-next.1 + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + +## 0.8.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + - @backstage/plugin-home-react@0.1.28-next.1 + +## 0.8.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-home-react@0.1.28-next.0 + +## 0.8.9 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- 0ebad54: Fixed the `WelcomeTitle` to properly default to the previous value of `inherit` +- cef60db: Home plugin support i18n +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-home-react@0.1.27 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.1 + - @backstage/core-compat-api@0.4.3 + - @backstage/theme@0.6.6 + +## 0.8.9-next.2 + +### Patch Changes + +- 0ebad54: Fixed the `WelcomeTitle` to properly default to the previous value of `inherit` +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/plugin-home-react@0.1.27-next.0 + - @backstage/catalog-client@1.10.1-next.0 + - @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.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/theme@0.6.6 + +## 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 + +- f7ca0fe: Added the Catalog presentation API to the HomePageRecentlyVisited and HomePageTopVisited components +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- eddd96c: Added optional title prop to `customHomePageGrid` +- 16eb4bf: Export ContentModal from `@backstage/plugin-home-react` so people can use this in other scenarios. + Renamed `CatalogReactComponentsNameToClassKey` to `PluginHomeComponentsNameToClassKey` in `overridableComponents.ts` + + Made QuickStartCard `docsLinkTitle` prop more flexible to allow for any React.JSX.Element instead of just a string. + Added QuickStartCard prop `additionalContent` which can eventually replace the prop `video`. + +- 2c1761f: Added a `variant` prop to the `WelcomeTitle` component making it work with the Customizable Home page feature. Adding it like this `` to the list of items under `CustomHomepageGrid` will allow it to render with a size that works well. +- 195323f: Export root page route from the home plugin to enable adding links/nav to it from outside the plugin +- 72d019d: Removed various typos +- d710d74: docs: Update default for `preventCollision` prop +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-home-react@0.1.26 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + +## 0.8.8-next.3 + +### Patch Changes + +- f7ca0fe: Added the Catalog presentation API to the HomePageRecentlyVisited and HomePageTopVisited components +- eddd96c: Added optional title prop to `customHomePageGrid` +- 16eb4bf: Export ContentModal from `@backstage/plugin-home-react` so people can use this in other scenarios. + Renamed `CatalogReactComponentsNameToClassKey` to `PluginHomeComponentsNameToClassKey` in `overridableComponents.ts` + + Made QuickStartCard `docsLinkTitle` prop more flexible to allow for any React.JSX.Element instead of just a string. + Added QuickStartCard prop `additionalContent` which can eventually replace the prop `video`. + +- 195323f: Export root page route from the home plugin to enable adding links/nav to it from outside the plugin +- d710d74: docs: Update default for `preventCollision` prop +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/plugin-home-react@0.1.26-next.2 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/theme@0.6.6-next.0 + ## 0.8.8-next.2 ### Patch Changes diff --git a/plugins/home/README.md b/plugins/home/README.md index 50b422a3dc..3498d6700f 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -107,6 +107,9 @@ export const homePage = ( ); ``` +> [!NOTE] +> You can provide a title to the grid by passing it as a prop: ``. This will be displayed as a header above the grid layout. + ### Creating Customizable Components The custom home page can use the default components created by using the default `createCardExtension` method but if you diff --git a/plugins/home/package.json b/plugins/home/package.json index c832e8f888..262d559bd9 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.8.8-next.2", + "version": "0.8.11-next.2", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin", diff --git a/plugins/home/report-alpha.api.md b/plugins/home/report-alpha.api.md index 6033cb47f8..d2e5c22ef8 100644 --- a/plugins/home/report-alpha.api.md +++ b/plugins/home/report-alpha.api.md @@ -3,19 +3,52 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: FrontendPlugin< - {}, + { + root: RouteRef; + }, {}, { + 'api:home/visits': ExtensionDefinition<{ + kind: 'api'; + name: 'visits'; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; + }>; + 'app-root-element:home/visit-listener': ExtensionDefinition<{ + kind: 'app-root-element'; + name: 'visit-listener'; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: { + element: JSX.Element; + }; + }>; 'page:home': ExtensionDefinition<{ config: { path: string | undefined; @@ -24,9 +57,9 @@ const _default: FrontendPlugin< path?: string | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { @@ -58,7 +91,8 @@ const _default: FrontendPlugin< kind: 'page'; name: undefined; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise; routeRef?: RouteRef; }; @@ -67,6 +101,38 @@ const _default: FrontendPlugin< >; export default _default; +// @alpha (undocumented) +export const homeTranslationRef: TranslationRef< + 'home', + { + readonly 'starredEntities.noStarredEntitiesMessage': 'Click the star beside an entity name to add it to this list!'; + readonly 'addWidgetDialog.title': 'Add new widget to dashboard'; + readonly 'customHomepageButtons.clearAll': 'Clear all'; + readonly 'customHomepageButtons.edit': 'Edit'; + readonly 'customHomepageButtons.restoreDefaults': 'Restore defaults'; + readonly 'customHomepageButtons.addWidget': 'Add widget'; + readonly 'customHomepageButtons.save': 'Save'; + readonly 'customHomepage.noWidgets': "No widgets added. Start by clicking the 'Add widget' button."; + readonly 'widgetSettingsOverlay.cancelButtonTitle': 'Cancel'; + readonly 'widgetSettingsOverlay.editSettingsTooptip': 'Edit settings'; + readonly 'widgetSettingsOverlay.deleteWidgetTooltip': 'Delete widget'; + readonly 'widgetSettingsOverlay.submitButtonTitle': 'Submit'; + readonly 'starredEntityListItem.removeFavoriteEntityTitle': 'Remove entity from favorites'; + readonly 'visitList.empty.title': 'There are no visits to show yet.'; + readonly 'visitList.empty.description': 'Once you start using Backstage, your visits will appear here as a quick link to carry on where you left off.'; + readonly 'visitList.few.title': 'The more pages you visit, the more pages will appear here.'; + readonly 'quickStart.title': 'Onboarding'; + readonly 'quickStart.description': 'Get started with Backstage'; + readonly 'quickStart.learnMoreLinkTitle': 'Learn more'; + readonly 'visitedByType.action.viewMore': 'View more'; + readonly 'visitedByType.action.viewLess': 'View less'; + readonly 'featuredDocsCard.empty.title': 'No documents to show'; + readonly 'featuredDocsCard.empty.description': 'Create your own document. Check out our Getting Started Information'; + readonly 'featuredDocsCard.empty.learnMoreLinkTitle': 'DOCS'; + readonly 'featuredDocsCard.learnMoreTitle': 'LEARN MORE'; + } +>; + // @alpha (undocumented) export const titleExtensionDataRef: ConfigurableExtensionDataRef< string, diff --git a/plugins/home/report.api.md b/plugins/home/report.api.md index 7c7f55fd47..dd33b952ee 100644 --- a/plugins/home/report.api.md +++ b/plugins/home/report.api.md @@ -23,6 +23,7 @@ import { ReactNode } from 'react'; import { RendererProps as RendererProps_2 } from '@backstage/plugin-home-react'; import { RouteRef } from '@backstage/core-plugin-api'; import { StorageApi } from '@backstage/core-plugin-api'; +import { Variant } from '@material-ui/core/styles/createTypography'; // @public export type Breakpoint = 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'; @@ -89,6 +90,7 @@ export const CustomHomepageGrid: ( export type CustomHomepageGridProps = { children?: ReactNode; config?: LayoutConfiguration[]; + title?: string; rowHeight?: number; breakpoints?: Record; cols?: Record; @@ -194,9 +196,10 @@ export const QuickStartCard: ( // @public export type QuickStartCardProps = { modalTitle?: string | JSX_3.Element; - docsLinkTitle?: string; + docsLinkTitle?: string | JSX_3.Element; docsLink?: string; video?: JSX_3.Element; + additionalContent?: JSX_3.Element; image: JSX_3.Element; cardDescription?: string; downloadImage?: JSX_3.Element; @@ -334,10 +337,12 @@ export type VisitsWebStorageApiOptions = { // @public export const WelcomeTitle: ({ language, + variant, }: WelcomeTitleLanguageProps) => JSX_2.Element; // @public (undocumented) export type WelcomeTitleLanguageProps = { language?: string[]; + variant?: Variant | 'inherit'; }; ``` diff --git a/plugins/home/src/alpha.tsx b/plugins/home/src/alpha.tsx index 74f96a2195..a92316c7f0 100644 --- a/plugins/home/src/alpha.tsx +++ b/plugins/home/src/alpha.tsx @@ -21,8 +21,14 @@ import { PageBlueprint, createFrontendPlugin, createRouteRef, + AppRootElementBlueprint, + identityApiRef, + storageApiRef, + ApiBlueprint, } from '@backstage/frontend-plugin-api'; import { compatWrapper } from '@backstage/core-compat-api'; +import { VisitListener } from './components/'; +import { visitsApiRef, VisitsStorageApi } from './api'; const rootRouteRef = createRouteRef(); @@ -48,7 +54,7 @@ const homePage = PageBlueprint.makeWithOverrides({ }, factory: (originalFactory, { inputs }) => { return originalFactory({ - defaultPath: '/home', + path: '/home', routeRef: rootRouteRef, loader: () => import('./components/').then(m => @@ -63,10 +69,37 @@ const homePage = PageBlueprint.makeWithOverrides({ }, }); +const visitListenerAppRootElement = AppRootElementBlueprint.make({ + name: 'visit-listener', + params: { + element: , + }, +}); + +const visitsApi = ApiBlueprint.make({ + name: 'visits', + params: defineParams => + defineParams({ + api: visitsApiRef, + deps: { + storageApi: storageApiRef, + identityApi: identityApiRef, + }, + factory: ({ storageApi, identityApi }) => + VisitsStorageApi.create({ storageApi, identityApi }), + }), +}); + /** * @alpha */ export default createFrontendPlugin({ pluginId: 'home', - extensions: [homePage], + info: { packageJson: () => import('../package.json') }, + extensions: [homePage, visitsApi, visitListenerAppRootElement], + routes: { + root: rootRouteRef, + }, }); + +export { homeTranslationRef } from './translation'; diff --git a/plugins/home/src/components/CustomHomepage/AddWidgetDialog.tsx b/plugins/home/src/components/CustomHomepage/AddWidgetDialog.tsx index 6519bfe901..4763401fe5 100644 --- a/plugins/home/src/components/CustomHomepage/AddWidgetDialog.tsx +++ b/plugins/home/src/components/CustomHomepage/AddWidgetDialog.tsx @@ -23,6 +23,8 @@ import ListItemAvatar from '@material-ui/core/ListItemAvatar'; import AddIcon from '@material-ui/icons/Add'; import ListItemText from '@material-ui/core/ListItemText'; import Typography from '@material-ui/core/Typography'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeTranslationRef } from '../../translation'; interface AddWidgetDialogProps { widgets: Widget[]; @@ -35,9 +37,10 @@ const getTitle = (widget: Widget) => { export const AddWidgetDialog = (props: AddWidgetDialogProps) => { const { widgets, handleAdd } = props; + const { t } = useTranslationRef(homeTranslationRef); return ( <> - Add new widget to dashboard + {t('addWidgetDialog.title')} {widgets.map(widget => { diff --git a/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx b/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx index d586f94d20..0496e923de 100644 --- a/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx +++ b/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx @@ -20,6 +20,8 @@ import DeleteIcon from '@material-ui/icons/Delete'; import AddIcon from '@material-ui/icons/Add'; import EditIcon from '@material-ui/icons/Edit'; import CancelIcon from '@material-ui/icons/Cancel'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeTranslationRef } from '../../translation'; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -55,6 +57,7 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => { restoreDefault, } = props; const styles = useStyles(); + const { t } = useTranslationRef(homeTranslationRef); return ( <> @@ -66,7 +69,7 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => { size="small" startIcon={} > - Edit + {t('customHomepageButtons.edit')} ) : ( <> @@ -78,7 +81,7 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => { size="small" startIcon={} > - Restore defaults + {t('customHomepageButtons.restoreDefaults')} )} {numWidgets > 0 && ( @@ -90,7 +93,7 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => { size="small" startIcon={} > - Clear all + {t('customHomepageButtons.clearAll')} )} {numWidgets > 0 && ( )} diff --git a/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx b/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx index da55ccc5f4..4b3d35ed62 100644 --- a/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx +++ b/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx @@ -50,6 +50,8 @@ import { WidgetSchema, } from './types'; import { CardConfig } from '@backstage/plugin-home-react'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeTranslationRef } from '../../translation'; // eslint-disable-next-line new-cap const ResponsiveGrid = WidthProvider(Responsive); @@ -222,6 +224,7 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { const getWidgetNameFromKey = (key: string) => { return key.split('__')[0]; }; + const { t } = useTranslationRef(homeTranslationRef); const handleAdd = (widget: Widget) => { const widgetId = `${widget.name}__${widgets.length + 1}${Math.random() @@ -321,7 +324,7 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { return ( <> - + { {!editMode && widgets.length === 0 && ( - No widgets added. Start by clicking the 'Add widget' button. + {t('customHomepage.noWidgets')} )} { const styles = useStyles(); const onClose = () => setSettingsDialogOpen(false); + const { t } = useTranslationRef(homeTranslationRef); return (
    @@ -98,10 +101,10 @@ export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => { > @@ -116,7 +119,7 @@ export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => { > {widget.settingsSchema && ( - + setSettingsDialogOpen(true)} @@ -128,7 +131,7 @@ export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => { )} {deletable !== false && ( - + handleRemove(id)}> diff --git a/plugins/home/src/components/CustomHomepage/types.ts b/plugins/home/src/components/CustomHomepage/types.ts index 6bbcb759d3..8d73d6d66d 100644 --- a/plugins/home/src/components/CustomHomepage/types.ts +++ b/plugins/home/src/components/CustomHomepage/types.ts @@ -45,6 +45,10 @@ export type CustomHomepageGridProps = { * Default layout for the homepage before users have modified it. */ config?: LayoutConfiguration[]; + /** + * Title displayed in the header of the grid. If not provided, no title will be shown. + */ + title?: string; /** * Height of grid row in pixels. * @defaultValue 60 @@ -95,7 +99,7 @@ export type CustomHomepageGridProps = { allowOverlap?: boolean; /** * Controls if widgets can collide with each other. If true, grid items won't change position when being dragged over. - * @defaultValue false + * @defaultValue true */ preventCollision?: boolean; }; diff --git a/plugins/home/src/components/StarredEntityListItem/StarredEntityListItem.tsx b/plugins/home/src/components/StarredEntityListItem/StarredEntityListItem.tsx index 28f199978d..60433fbddf 100644 --- a/plugins/home/src/components/StarredEntityListItem/StarredEntityListItem.tsx +++ b/plugins/home/src/components/StarredEntityListItem/StarredEntityListItem.tsx @@ -26,6 +26,8 @@ import { entityRouteRef } from '@backstage/plugin-catalog-react'; import { useRouteRef } from '@backstage/core-plugin-api'; import { FavoriteToggle } from '@backstage/core-components'; import { makeStyles } from '@material-ui/core/styles'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeTranslationRef } from '../../translation'; type EntityListItemProps = { entity: Entity; @@ -50,6 +52,7 @@ export const StarredEntityListItem = ({ }: EntityListItemProps) => { const classes = useStyles(); const catalogEntityRoute = useRouteRef(entityRouteRef); + const { t } = useTranslationRef(homeTranslationRef); let secondaryText = ''; if (showKind) { @@ -80,7 +83,7 @@ export const StarredEntityListItem = ({ > onToggleStarredEntity(entity)} /> diff --git a/plugins/home/src/components/VisitList/ItemName.tsx b/plugins/home/src/components/VisitList/ItemName.tsx index bf02b1ba07..7140caf61b 100644 --- a/plugins/home/src/components/VisitList/ItemName.tsx +++ b/plugins/home/src/components/VisitList/ItemName.tsx @@ -18,16 +18,28 @@ import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import { Visit } from '../../api/VisitsApi'; import { Link } from '@backstage/core-components'; +import { EntityRefLink } from '@backstage/plugin-catalog-react'; -const useStyles = makeStyles(_theme => ({ +const useStyles = makeStyles(theme => ({ name: { marginLeft: '0.8rem', marginRight: '0.8rem', + fontSize: theme.typography.body1.fontSize, }, })); export const ItemName = ({ visit }: { visit: Visit }) => { const classes = useStyles(); + if (visit.entityRef) + return ( + + ); + return ( ( - <> - - There are no visits to show yet. - - - Once you start using Backstage, your visits will appear here as a quick - link to carry on where you left off. - - -); +export const VisitListEmpty = () => { + const { t } = useTranslationRef(homeTranslationRef); + return ( + <> + + {t('visitList.empty.title')} + + + {t('visitList.empty.description')} + + + ); +}; diff --git a/plugins/home/src/components/VisitList/VisitListFew.tsx b/plugins/home/src/components/VisitList/VisitListFew.tsx index 26fdb92293..7f741f3af5 100644 --- a/plugins/home/src/components/VisitList/VisitListFew.tsx +++ b/plugins/home/src/components/VisitList/VisitListFew.tsx @@ -14,12 +14,17 @@ * limitations under the License. */ +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Typography from '@material-ui/core/Typography'; +import { homeTranslationRef } from '../../translation'; -export const VisitListFew = () => ( - <> - - The more pages you visit, the more pages will appear here. - - -); +export const VisitListFew = () => { + const { t } = useTranslationRef(homeTranslationRef); + return ( + <> + + {t('visitList.few.title')} + + + ); +}; diff --git a/plugins/home/src/homePageComponents/FeaturedDocsCard/Content.tsx b/plugins/home/src/homePageComponents/FeaturedDocsCard/Content.tsx index 475ef4b47e..a854203fdb 100644 --- a/plugins/home/src/homePageComponents/FeaturedDocsCard/Content.tsx +++ b/plugins/home/src/homePageComponents/FeaturedDocsCard/Content.tsx @@ -33,6 +33,8 @@ import { EntityFilterQuery } from '@backstage/catalog-client'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import { stringifyEntityRef } from '@backstage/catalog-model'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeTranslationRef } from '../../translation'; /** * Props customizing the component. @@ -88,7 +90,8 @@ const useStyles = makeStyles( export const Content = (props: FeaturedDocsCardProps): JSX.Element => { const { emptyState, filter, linkDestination, responseLimit, subLinkText } = props; - const linkText = subLinkText || 'LEARN MORE'; + const { t } = useTranslationRef(homeTranslationRef); + const linkText = subLinkText || t('featuredDocsCard.learnMoreTitle'); const styles = useStyles(); const catalogApi = useApi(catalogApiRef); const { @@ -153,14 +156,14 @@ export const Content = (props: FeaturedDocsCardProps): JSX.Element => { emptyState || ( - DOCS + {t('featuredDocsCard.empty.learnMoreLinkTitle')} } /> diff --git a/plugins/home/src/homePageComponents/QuickStart/Content.tsx b/plugins/home/src/homePageComponents/QuickStart/Content.tsx index 829b9ed191..5317312626 100644 --- a/plugins/home/src/homePageComponents/QuickStart/Content.tsx +++ b/plugins/home/src/homePageComponents/QuickStart/Content.tsx @@ -18,8 +18,10 @@ import { JSX } from 'react'; import { Link } from '@backstage/core-components'; import Typography from '@material-ui/core/Typography'; import Grid from '@material-ui/core/Grid'; -import { ContentModal } from './ContentModal'; +import { ContentModal } from '@backstage/plugin-home-react'; import { useStyles } from './styles'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeTranslationRef } from '../../translation'; /** * Props customizing the component. @@ -30,11 +32,15 @@ export type QuickStartCardProps = { /** The modal link title */ modalTitle?: string | JSX.Element; /** The link to docs title */ - docsLinkTitle?: string; + docsLinkTitle?: string | JSX.Element; /** The link to docs */ docsLink?: string; - /** The video to play on the card */ + /** The video to play on the card + * @deprecated This will be removed in the future, please use `additionalContent` instead + */ video?: JSX.Element; + /** Additional card content */ + additionalContent?: JSX.Element; /** A quickstart image to display on the card */ image: JSX.Element; /** The card description*/ @@ -50,14 +56,15 @@ export type QuickStartCardProps = { */ export const Content = (props: QuickStartCardProps): JSX.Element => { const styles = useStyles(); + const { t } = useTranslationRef(homeTranslationRef); return ( <> - {props.cardDescription || 'Get started with Backstage'} + {props.cardDescription || t('quickStart.description')} { variant="h6" className={styles.link} > - {props.docsLinkTitle || 'Learn more'} + {props.docsLinkTitle || t('quickStart.learnMoreLinkTitle')} - {props.video && props.video} + {(props.additionalContent && props.additionalContent) || + (props.video && props.video)} ); }; diff --git a/plugins/home/src/homePageComponents/QuickStart/QuickStart.stories.tsx b/plugins/home/src/homePageComponents/QuickStart/QuickStart.stories.tsx index 18375c9c27..c0963b88a1 100644 --- a/plugins/home/src/homePageComponents/QuickStart/QuickStart.stories.tsx +++ b/plugins/home/src/homePageComponents/QuickStart/QuickStart.stories.tsx @@ -18,6 +18,7 @@ import { QuickStartCard } from '../../plugin'; import { ComponentType, PropsWithChildren } from 'react'; import { wrapInTestApp } from '@backstage/test-utils'; import Grid from '@material-ui/core/Grid'; +import OpenInNewIcon from '@material-ui/icons/OpenInNew'; import ContentImage from './static/backstageSystemModel.png'; export default { @@ -61,6 +62,40 @@ export const Customized = () => { /> } cardDescription="Backstage system model will help you create new entities" + additionalContent={ +

    + This is a custom description for the Quick Start card. It can be + used to provide additional information or context about the Quick + Start process. +

    + } + /> +
    + ); +}; + +export const CustomDocLink = () => { + return ( + + + + Learn more with getting started docs + + } + docsLink="https://backstage.io/docs/getting-started" + image={ + quick start + } + cardDescription="Backstage system model will help you create new entities" /> ); diff --git a/plugins/home/src/homePageComponents/StarredEntities/Content.tsx b/plugins/home/src/homePageComponents/StarredEntities/Content.tsx index 80f5ee74b5..ffe1b36b36 100644 --- a/plugins/home/src/homePageComponents/StarredEntities/Content.tsx +++ b/plugins/home/src/homePageComponents/StarredEntities/Content.tsx @@ -29,6 +29,8 @@ import { ReactNode, useState } from 'react'; import useAsync from 'react-use/esm/useAsync'; import { StarredEntityListItem } from '../../components/StarredEntityListItem/StarredEntityListItem'; import { makeStyles } from '@material-ui/core/styles'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ tabs: { @@ -63,6 +65,7 @@ export const Content = ({ const catalogApi = useApi(catalogApiRef); const { starredEntities, toggleStarredEntity } = useStarredEntities(); const [activeTab, setActiveTab] = useState(0); + const { t } = useTranslationRef(homeTranslationRef); // Grab starred entities from catalog to ensure they still exist and also retrieve display titles const entities = useAsync(async () => { @@ -89,7 +92,7 @@ export const Content = ({ return ( {noStarredEntitiesMessage || - 'Click the star beside an entity name to add it to this list!'} + t('starredEntities.noStarredEntitiesMessage')} ); diff --git a/plugins/home/src/homePageComponents/VisitedByType/Actions.tsx b/plugins/home/src/homePageComponents/VisitedByType/Actions.tsx index a8e8bf7722..4c874f93a7 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Actions.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Actions.tsx @@ -17,6 +17,8 @@ import { useCallback } from 'react'; import Button from '@material-ui/core/Button'; import { useContext } from './Context'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeTranslationRef } from '../../translation'; export const Actions = () => { const { collapsed, setCollapsed, visits, numVisitsOpen, loading } = @@ -25,7 +27,10 @@ export const Actions = () => { () => setCollapsed(prevCollapsed => !prevCollapsed), [setCollapsed], ); - const label = collapsed ? 'View More' : 'View Less'; + const { t } = useTranslationRef(homeTranslationRef); + const label = collapsed + ? t('visitedByType.action.viewMore') + : t('visitedByType.action.viewLess'); if (!loading && visits.length <= numVisitsOpen) return <>; diff --git a/plugins/home/src/homePageComponents/WelcomeTitle/WelcomeTitle.tsx b/plugins/home/src/homePageComponents/WelcomeTitle/WelcomeTitle.tsx index 686d13a748..ee2e8704bd 100644 --- a/plugins/home/src/homePageComponents/WelcomeTitle/WelcomeTitle.tsx +++ b/plugins/home/src/homePageComponents/WelcomeTitle/WelcomeTitle.tsx @@ -23,13 +23,18 @@ import Typography from '@material-ui/core/Typography'; import { useEffect, useMemo } from 'react'; import useAsync from 'react-use/esm/useAsync'; import { getTimeBasedGreeting } from './timeUtil'; +import { Variant } from '@material-ui/core/styles/createTypography'; /** @public */ export type WelcomeTitleLanguageProps = { language?: string[]; + variant?: Variant | 'inherit'; }; -export const WelcomeTitle = ({ language }: WelcomeTitleLanguageProps) => { +export const WelcomeTitle = ({ + language, + variant = 'inherit', +}: WelcomeTitleLanguageProps) => { const identityApi = useApi(identityApiRef); const alertApi = useApi(alertApiRef); const greeting = useMemo(() => getTimeBasedGreeting(language), [language]); @@ -49,7 +54,7 @@ export const WelcomeTitle = ({ language }: WelcomeTitleLanguageProps) => { return ( - {`${greeting.greeting}${ + {`${greeting.greeting}${ profile?.displayName ? `, ${profile?.displayName}` : '' }!`} diff --git a/plugins/home/src/translation.ts b/plugins/home/src/translation.ts new file mode 100644 index 0000000000..4070dde28c --- /dev/null +++ b/plugins/home/src/translation.ts @@ -0,0 +1,81 @@ +/* + * 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 homeTranslationRef = createTranslationRef({ + id: 'home', + messages: { + addWidgetDialog: { + title: 'Add new widget to dashboard', + }, + customHomepageButtons: { + edit: 'Edit', + restoreDefaults: 'Restore defaults', + clearAll: 'Clear all', + addWidget: 'Add widget', + save: 'Save', + }, + customHomepage: { + noWidgets: "No widgets added. Start by clicking the 'Add widget' button.", + }, + widgetSettingsOverlay: { + editSettingsTooptip: 'Edit settings', + deleteWidgetTooltip: 'Delete widget', + submitButtonTitle: 'Submit', + cancelButtonTitle: 'Cancel', + }, + starredEntityListItem: { + removeFavoriteEntityTitle: 'Remove entity from favorites', + }, + visitList: { + empty: { + title: 'There are no visits to show yet.', + description: + 'Once you start using Backstage, your visits will appear here as a quick link to carry on where you left off.', + }, + few: { + title: 'The more pages you visit, the more pages will appear here.', + }, + }, + quickStart: { + title: 'Onboarding', + description: 'Get started with Backstage', + learnMoreLinkTitle: 'Learn more', + }, + starredEntities: { + noStarredEntitiesMessage: + 'Click the star beside an entity name to add it to this list!', + }, + visitedByType: { + action: { + viewMore: 'View more', + viewLess: 'View less', + }, + }, + featuredDocsCard: { + learnMoreTitle: 'LEARN MORE', + empty: { + title: 'No documents to show', + description: + 'Create your own document. Check out our Getting Started Information', + learnMoreLinkTitle: 'DOCS', + }, + }, + }, +}); diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 4147c855f8..38450c57fe 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,162 @@ # @backstage/plugin-kubernetes-backend +## 0.20.0-next.1 + +### Minor Changes + +- 759568d: **BREAKING CHANGE**: Removed support for the legacy backend system. This means that the deprecated `createRouter` and `KubernetesBuilder` and related types have been removed. Please refer to the [relevant documentation](https://backstage.io/docs/features/kubernetes/installation/#adding-kubernetes-backend-plugin) to configure the Kubernetes plugin. + + **BREAKING CHANGE**: The deprecated types `AuthenticationStrategy`, `AuthMetadata`, `ClusterDetails`, `CustomResource`, `CustomResourcesByEntity`, `FetchResponseWrapper`, `KubernetesBuilder`, `KubernetesBuilderReturn`, `KubernetesClustersSupplier`, `KubernetesCredential`, `KubernetesEnvironment`, `KubernetesFetcher`, `KubernetesObjectsProvider`, `KubernetesObjectTypes`, `KubernetesServiceLocator`,`ObjectFetchParams`, `ObjectToFetch`,`RouterOptions` and `ServiceLocatorRequestContext` should all now be imported from `@backstage/plugin-kubernetes-node`. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-node@0.3.3-next.0 + +## 0.19.9-next.0 + +### Patch Changes + +- 00ebaeb: Remove usage of the deprecated `loggerToWinstonLogger` from `@backstage/backend-common`. +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-kubernetes-node@0.3.3-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.17 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.6 + - @backstage/plugin-permission-common@0.9.1 + +## 0.19.8 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-kubernetes-node@0.3.2 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-kubernetes-common@0.9.6 + +## 0.19.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-kubernetes-node@0.3.2-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration-aws-node@0.1.17-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-kubernetes-common@0.9.6-next.0 + +## 0.19.7 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @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 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1 + +## 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 + +- f6f692c: Changed logging of cluster details to debug to minimise log clutter. +- 216c6b2: Updated dependency `@kubernetes/client-node` to `1.1.2`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/plugin-kubernetes-node@0.3.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/integration-aws-node@0.1.16 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.19.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + - @backstage/plugin-kubernetes-node@0.3.0-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + ## 0.19.6-next.2 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 95854d8e88..5f75b7f500 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-next.2", + "version": "0.20.0-next.1", "description": "A Backstage backend plugin that integrates towards Kubernetes", "backstage": { "role": "backend-plugin", @@ -27,23 +27,8 @@ "directory": "plugins/kubernetes-backend" }, "license": "Apache-2.0", - "exports": { - ".": "./src/index.ts", - "./alpha": "./src/alpha.ts", - "./package.json": "./package.json" - }, "main": "src/index.ts", "types": "src/index.ts", - "typesVersions": { - "*": { - "alpha": [ - "src/alpha.ts" - ], - "package.json": [ - "package.json" - ] - } - }, "files": [ "dist", "config.d.ts" @@ -62,7 +47,6 @@ "@aws-sdk/credential-providers": "^3.350.0", "@aws-sdk/signature-v4": "^3.347.0", "@azure/identity": "^4.0.0", - "@backstage/backend-common": "^0.25.0", "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", @@ -79,9 +63,7 @@ "@google-cloud/container": "^5.0.0", "@jest-mock/express": "^2.0.1", "@kubernetes/client-node": "1.1.2", - "@types/express": "^4.17.6", "@types/http-proxy-middleware": "^1.0.0", - "@types/luxon": "^3.0.0", "compression": "^1.7.4", "cors": "^2.8.5", "express": "^4.17.1", @@ -105,6 +87,8 @@ "@backstage/plugin-permission-backend": "workspace:^", "@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^", "@types/aws4": "^1.5.1", + "@types/express": "^4.17.6", + "@types/luxon": "^3.0.0", "msw": "^1.0.0", "supertest": "^7.0.0", "ws": "^8.18.0" diff --git a/plugins/kubernetes-backend/report.api.md b/plugins/kubernetes-backend/report.api.md index 345c53f65b..dfc93b9da6 100644 --- a/plugins/kubernetes-backend/report.api.md +++ b/plugins/kubernetes-backend/report.api.md @@ -3,147 +3,112 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AuthenticationStrategy as AuthenticationStrategy_2 } from '@backstage/plugin-kubernetes-node'; -import { AuthMetadata as AuthMetadata_2 } from '@backstage/plugin-kubernetes-node'; -import { AuthService } from '@backstage/backend-plugin-api'; +import { AuthenticationStrategy } from '@backstage/plugin-kubernetes-node'; +import { AuthMetadata } from '@backstage/plugin-kubernetes-node'; import { BackendFeature } from '@backstage/backend-plugin-api'; -import { BackstageCredentials } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; -import { ClusterDetails as ClusterDetails_2 } from '@backstage/plugin-kubernetes-node'; +import { ClusterDetails } from '@backstage/plugin-kubernetes-node'; import { Config } from '@backstage/config'; -import { CustomResource as CustomResource_2 } from '@backstage/plugin-kubernetes-node'; import { DiscoveryService } from '@backstage/backend-plugin-api'; -import { Duration } from 'luxon'; -import express from 'express'; import { HttpAuthService } from '@backstage/backend-plugin-api'; -import * as k8sAuthTypes from '@backstage/plugin-kubernetes-node'; -import { KubernetesClustersSupplier as KubernetesClustersSupplier_2 } from '@backstage/plugin-kubernetes-node'; -import { KubernetesCredential as KubernetesCredential_2 } from '@backstage/plugin-kubernetes-node'; -import { KubernetesFetcher as KubernetesFetcher_2 } from '@backstage/plugin-kubernetes-node'; -import { KubernetesObjectsProvider as KubernetesObjectsProvider_2 } from '@backstage/plugin-kubernetes-node'; +import * as k8sTypes from '@backstage/plugin-kubernetes-node'; +import { KubernetesClustersSupplier } from '@backstage/plugin-kubernetes-node'; +import { KubernetesCredential } from '@backstage/plugin-kubernetes-node'; import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common'; import type { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; -import { KubernetesServiceLocator as KubernetesServiceLocator_2 } from '@backstage/plugin-kubernetes-node'; -import { Logger } from 'winston'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { ObjectToFetch as ObjectToFetch_2 } from '@backstage/plugin-kubernetes-node'; -import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +import { ObjectToFetch } from '@backstage/plugin-kubernetes-node'; import { PermissionsService } from '@backstage/backend-plugin-api'; import { RequestHandler } from 'http-proxy-middleware'; -import { RootConfigService } from '@backstage/backend-plugin-api'; import { TokenCredential } from '@azure/identity'; // @public (undocumented) -export class AksStrategy implements AuthenticationStrategy_2 { +export class AksStrategy implements AuthenticationStrategy { // (undocumented) getCredential( - _: ClusterDetails_2, + _: ClusterDetails, requestAuth: KubernetesRequestAuth, - ): Promise; + ): Promise; // (undocumented) - presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; + presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata; // (undocumented) validateCluster(): Error[]; } // @public (undocumented) -export class AnonymousStrategy implements AuthenticationStrategy_2 { +export class AnonymousStrategy implements AuthenticationStrategy { // (undocumented) - getCredential(): Promise; + getCredential(): Promise; // (undocumented) - presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; + presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata; // (undocumented) validateCluster(): Error[]; } -// @public @deprecated (undocumented) -export type AuthenticationStrategy = k8sAuthTypes.AuthenticationStrategy; - -// @public @deprecated (undocumented) -export type AuthMetadata = k8sAuthTypes.AuthMetadata; - // @public (undocumented) -export class AwsIamStrategy implements AuthenticationStrategy_2 { +export class AwsIamStrategy implements AuthenticationStrategy { constructor(opts: { config: Config }); // (undocumented) - getCredential( - clusterDetails: ClusterDetails_2, - ): Promise; + getCredential(clusterDetails: ClusterDetails): Promise; // (undocumented) - presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; + presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata; // (undocumented) validateCluster(): Error[]; } // @public (undocumented) -export class AzureIdentityStrategy implements AuthenticationStrategy_2 { +export class AzureIdentityStrategy implements AuthenticationStrategy { constructor(logger: LoggerService, tokenCredential?: TokenCredential); // (undocumented) - getCredential(): Promise; + getCredential(): Promise; // (undocumented) - presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; + presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata; // (undocumented) validateCluster(): Error[]; } -// @public @deprecated (undocumented) -export type ClusterDetails = k8sAuthTypes.ClusterDetails; - -// @public @deprecated -export function createRouter(options: RouterOptions): Promise; - -// @public @deprecated (undocumented) -export type CustomResource = k8sAuthTypes.CustomResource; - -// @public @deprecated (undocumented) -export type CustomResourcesByEntity = k8sAuthTypes.CustomResourcesByEntity; - // @public (undocumented) -export const DEFAULT_OBJECTS: ObjectToFetch_2[]; +export const DEFAULT_OBJECTS: ObjectToFetch[]; // @public -export class DispatchStrategy implements AuthenticationStrategy_2 { +export class DispatchStrategy implements AuthenticationStrategy { constructor(options: DispatchStrategyOptions); // (undocumented) getCredential( - clusterDetails: ClusterDetails_2, + clusterDetails: ClusterDetails, auth: KubernetesRequestAuth, - ): Promise; + ): Promise; // (undocumented) - presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; + presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata; // (undocumented) - validateCluster(authMetadata: AuthMetadata_2): Error[]; + validateCluster(authMetadata: AuthMetadata): Error[]; } // @public (undocumented) export type DispatchStrategyOptions = { authStrategyMap: { - [key: string]: AuthenticationStrategy_2; + [key: string]: AuthenticationStrategy; }; }; -// @public @deprecated (undocumented) -export type FetchResponseWrapper = k8sAuthTypes.FetchResponseWrapper; - // @public (undocumented) -export class GoogleServiceAccountStrategy implements AuthenticationStrategy_2 { +export class GoogleServiceAccountStrategy implements AuthenticationStrategy { // (undocumented) - getCredential(): Promise; + getCredential(): Promise; // (undocumented) - presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; + presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata; // (undocumented) validateCluster(): Error[]; } // @public (undocumented) -export class GoogleStrategy implements AuthenticationStrategy_2 { +export class GoogleStrategy implements AuthenticationStrategy { // (undocumented) getCredential( - _: ClusterDetails_2, + _: ClusterDetails, requestAuth: KubernetesRequestAuth, - ): Promise; + ): Promise; // (undocumented) - presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; + presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata; // (undocumented) validateCluster(): Error[]; } @@ -154,185 +119,22 @@ export const HEADER_KUBERNETES_AUTH: string; // @public export const HEADER_KUBERNETES_CLUSTER: string; -// @public @deprecated (undocumented) -export class KubernetesBuilder { - constructor(env: KubernetesEnvironment); - // (undocumented) - addAuthStrategy(key: string, strategy: AuthenticationStrategy_2): this; - // (undocumented) - build(): KubernetesBuilderReturn; - // (undocumented) - protected buildAuthStrategyMap(): { - [key: string]: AuthenticationStrategy_2; - }; - // (undocumented) - protected buildCatalogRelationServiceLocator( - clusterSupplier: KubernetesClustersSupplier_2, - ): KubernetesServiceLocator_2; - // (undocumented) - protected buildClusterSupplier( - refreshInterval: Duration, - ): KubernetesClustersSupplier_2; - // (undocumented) - protected buildCustomResources(): CustomResource_2[]; - // (undocumented) - protected buildFetcher(): KubernetesFetcher_2; - // (undocumented) - protected buildHttpServiceLocator( - _clusterSupplier: KubernetesClustersSupplier_2, - ): KubernetesServiceLocator_2; - // (undocumented) - protected buildMultiTenantServiceLocator( - clusterSupplier: KubernetesClustersSupplier_2, - ): KubernetesServiceLocator_2; - // (undocumented) - protected buildObjectsProvider( - options: KubernetesObjectsProviderOptions, - ): KubernetesObjectsProvider_2; - // (undocumented) - protected buildProxy( - logger: LoggerService, - clusterSupplier: KubernetesClustersSupplier_2, - discovery: DiscoveryService, - httpAuth: HttpAuthService, - ): KubernetesProxy; - // (undocumented) - protected buildRouter( - objectsProvider: KubernetesObjectsProvider_2, - clusterSupplier: KubernetesClustersSupplier_2, - catalogApi: CatalogApi, - proxy: KubernetesProxy, - permissionApi: PermissionEvaluator, - authService: AuthService, - httpAuth: HttpAuthService, - ): express.Router; - // (undocumented) - protected buildServiceLocator( - method: ServiceLocatorMethod, - clusterSupplier: KubernetesClustersSupplier_2, - ): KubernetesServiceLocator_2; - // (undocumented) - protected buildSingleTenantServiceLocator( - clusterSupplier: KubernetesClustersSupplier_2, - ): KubernetesServiceLocator_2; - // (undocumented) - static createBuilder(env: KubernetesEnvironment): KubernetesBuilder; - // (undocumented) - protected readonly env: KubernetesEnvironment; - // (undocumented) - protected fetchClusterDetails( - clusterSupplier: KubernetesClustersSupplier_2, - options: { - credentials: BackstageCredentials; - }, - ): Promise; - // (undocumented) - protected getAuthStrategyMap(): { - [key: string]: AuthenticationStrategy_2; - }; - // (undocumented) - protected getClusterSupplier(): KubernetesClustersSupplier_2; - // (undocumented) - protected getFetcher(): KubernetesFetcher_2; - // (undocumented) - protected getObjectsProvider( - options: KubernetesObjectsProviderOptions, - ): KubernetesObjectsProvider_2; - // (undocumented) - protected getObjectTypesToFetch(): ObjectToFetch_2[] | undefined; - // (undocumented) - protected getProxy( - logger: LoggerService, - clusterSupplier: KubernetesClustersSupplier_2, - discovery: DiscoveryService, - httpAuth: HttpAuthService, - ): KubernetesProxy; - // (undocumented) - protected getServiceLocator(): KubernetesServiceLocator_2; - // (undocumented) - protected getServiceLocatorMethod(): ServiceLocatorMethod; - // (undocumented) - setAuthStrategyMap(authStrategyMap: { - [key: string]: AuthenticationStrategy_2; - }): void; - // (undocumented) - setClusterSupplier(clusterSupplier?: KubernetesClustersSupplier_2): this; - // (undocumented) - setDefaultClusterRefreshInterval(refreshInterval: Duration): this; - // (undocumented) - setFetcher(fetcher?: KubernetesFetcher_2): this; - // (undocumented) - setObjectsProvider(objectsProvider?: KubernetesObjectsProvider_2): this; - // (undocumented) - setProxy(proxy?: KubernetesProxy): this; - // (undocumented) - setServiceLocator(serviceLocator?: KubernetesServiceLocator_2): this; -} - -// @public @deprecated -export type KubernetesBuilderReturn = Promise<{ - router: express.Router; - clusterSupplier: KubernetesClustersSupplier_2; - customResources: CustomResource_2[]; - fetcher: KubernetesFetcher_2; - proxy: KubernetesProxy; - objectsProvider: KubernetesObjectsProvider_2; - serviceLocator: KubernetesServiceLocator_2; - authStrategyMap: { - [key: string]: AuthenticationStrategy_2; - }; -}>; - -// @public @deprecated (undocumented) -export type KubernetesClustersSupplier = - k8sAuthTypes.KubernetesClustersSupplier; - -// @public @deprecated (undocumented) -export type KubernetesCredential = k8sAuthTypes.KubernetesCredential; - -// @public @deprecated (undocumented) -export interface KubernetesEnvironment { - // (undocumented) - auth?: AuthService; - // (undocumented) - catalogApi: CatalogApi; - // (undocumented) - config: Config; - // (undocumented) - discovery: DiscoveryService; - // (undocumented) - httpAuth?: HttpAuthService; - // (undocumented) - logger: LoggerService; - // (undocumented) - permissions: PermissionEvaluator; -} - -// @public @deprecated (undocumented) -export type KubernetesFetcher = k8sAuthTypes.KubernetesFetcher; - -// @public @deprecated (undocumented) -export type KubernetesObjectsProvider = k8sAuthTypes.KubernetesObjectsProvider; - // @public (undocumented) export interface KubernetesObjectsProviderOptions { // (undocumented) config: Config; // (undocumented) - customResources: k8sAuthTypes.CustomResource[]; + customResources: k8sTypes.CustomResource[]; // (undocumented) - fetcher: k8sAuthTypes.KubernetesFetcher; + fetcher: k8sTypes.KubernetesFetcher; // (undocumented) logger: LoggerService; // (undocumented) - objectTypesToFetch?: k8sAuthTypes.ObjectToFetch[]; + objectTypesToFetch?: k8sTypes.ObjectToFetch[]; // (undocumented) - serviceLocator: k8sAuthTypes.KubernetesServiceLocator; + serviceLocator: k8sTypes.KubernetesServiceLocator; } -// @public @deprecated (undocumented) -export type KubernetesObjectTypes = k8sAuthTypes.KubernetesObjectTypes; - // @public const kubernetesPlugin: BackendFeature; export default kubernetesPlugin; @@ -357,58 +159,31 @@ export type KubernetesProxyOptions = { clusterSupplier: KubernetesClustersSupplier; authStrategy: AuthenticationStrategy; discovery: DiscoveryService; - httpAuth?: HttpAuthService; + httpAuth: HttpAuthService; }; -// @public @deprecated (undocumented) -export type KubernetesServiceLocator = k8sAuthTypes.KubernetesServiceLocator; - -// @public @deprecated (undocumented) -export type ObjectFetchParams = k8sAuthTypes.ObjectFetchParams; - // @public (undocumented) export type ObjectsByEntityRequest = KubernetesRequestBody; -// @public @deprecated (undocumented) -export type ObjectToFetch = k8sAuthTypes.ObjectToFetch; - // @public (undocumented) -export class OidcStrategy implements AuthenticationStrategy_2 { +export class OidcStrategy implements AuthenticationStrategy { // (undocumented) getCredential( - clusterDetails: ClusterDetails_2, + clusterDetails: ClusterDetails, authConfig: KubernetesRequestAuth, - ): Promise; + ): Promise; // (undocumented) - presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; + presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata; // (undocumented) - validateCluster(authMetadata: AuthMetadata_2): Error[]; -} - -// @public @deprecated (undocumented) -export interface RouterOptions { - // (undocumented) - catalogApi: CatalogApi; - // (undocumented) - clusterSupplier?: KubernetesClustersSupplier; - // (undocumented) - config: RootConfigService; - // (undocumented) - discovery: DiscoveryService; - // (undocumented) - logger: Logger; - // (undocumented) - permissions: PermissionEvaluator; + validateCluster(authMetadata: AuthMetadata): Error[]; } // @public (undocumented) -export class ServiceAccountStrategy implements AuthenticationStrategy_2 { +export class ServiceAccountStrategy implements AuthenticationStrategy { // (undocumented) - getCredential( - clusterDetails: ClusterDetails_2, - ): Promise; + getCredential(clusterDetails: ClusterDetails): Promise; // (undocumented) - presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; + presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata; // (undocumented) validateCluster(): Error[]; } @@ -420,10 +195,6 @@ export type ServiceLocatorMethod = | 'catalogRelation' | 'http'; -// @public @deprecated (undocumented) -export type ServiceLocatorRequestContext = - k8sAuthTypes.ServiceLocatorRequestContext; - // @public (undocumented) export type SigningCreds = { accessKeyId: string | undefined; diff --git a/plugins/kubernetes-backend/src/auth/DispatchStrategy.test.ts b/plugins/kubernetes-backend/src/auth/DispatchStrategy.test.ts index 773e464168..a53be59e14 100644 --- a/plugins/kubernetes-backend/src/auth/DispatchStrategy.test.ts +++ b/plugins/kubernetes-backend/src/auth/DispatchStrategy.test.ts @@ -19,8 +19,10 @@ import { KubernetesRequestAuth, } from '@backstage/plugin-kubernetes-common'; import { DispatchStrategy } from './DispatchStrategy'; -import { ClusterDetails } from '../types'; -import { AuthenticationStrategy } from './types'; +import { + ClusterDetails, + AuthenticationStrategy, +} from '@backstage/plugin-kubernetes-node'; describe('getCredential', () => { let strategy: DispatchStrategy; diff --git a/plugins/kubernetes-backend/src/auth/index.ts b/plugins/kubernetes-backend/src/auth/index.ts index bcdd620243..3fb0deff86 100644 --- a/plugins/kubernetes-backend/src/auth/index.ts +++ b/plugins/kubernetes-backend/src/auth/index.ts @@ -23,4 +23,3 @@ export * from './GoogleServiceAccountStrategy'; export * from './DispatchStrategy'; export * from './ServiceAccountStrategy'; export * from './OidcStrategy'; -export * from './types'; diff --git a/plugins/kubernetes-backend/src/auth/types.ts b/plugins/kubernetes-backend/src/auth/types.ts deleted file mode 100644 index 9b33265448..0000000000 --- a/plugins/kubernetes-backend/src/auth/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as k8sAuthTypes from '@backstage/plugin-kubernetes-node'; - -// TODO remove this re-export as a breaking change after a couple of releases -/** - * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead - */ -export type AuthenticationStrategy = k8sAuthTypes.AuthenticationStrategy; - -/** - * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead - */ -export type KubernetesCredential = k8sAuthTypes.KubernetesCredential; diff --git a/plugins/kubernetes-backend/src/cluster-locator/CatalogClusterLocator.test.ts b/plugins/kubernetes-backend/src/cluster-locator/CatalogClusterLocator.test.ts index 0d17314cef..fbab8473b7 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/CatalogClusterLocator.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/CatalogClusterLocator.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import '@backstage/backend-common'; import { ANNOTATION_KUBERNETES_AUTH_PROVIDER, ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE, diff --git a/plugins/kubernetes-backend/src/cluster-locator/CatalogClusterLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/CatalogClusterLocator.ts index f8e921828e..569b55950a 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/CatalogClusterLocator.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/CatalogClusterLocator.ts @@ -18,7 +18,10 @@ import { AuthService, BackstageCredentials, } from '@backstage/backend-plugin-api'; -import { ClusterDetails, KubernetesClustersSupplier } from '../types/types'; +import { + ClusterDetails, + KubernetesClustersSupplier, +} from '@backstage/plugin-kubernetes-node'; import { CATALOG_FILTER_EXISTS, CatalogApi } from '@backstage/catalog-client'; import { ANNOTATION_KUBERNETES_API_SERVER, diff --git a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts index 3f014b2869..134cefff2e 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import '@backstage/backend-common'; import { ConfigReader, Config } from '@backstage/config'; import { ANNOTATION_KUBERNETES_AUTH_PROVIDER, @@ -23,7 +22,7 @@ import { } from '@backstage/plugin-kubernetes-common'; import { ClusterDetails } from '@backstage/plugin-kubernetes-node'; import { ConfigClusterLocator } from './ConfigClusterLocator'; -import { AuthenticationStrategy } from '../auth'; +import { AuthenticationStrategy } from '@backstage/plugin-kubernetes-node'; describe('ConfigClusterLocator', () => { let authStrategy: jest.Mocked; diff --git a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts index 6403ec89a7..e7abda2bcc 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts @@ -21,8 +21,11 @@ import { ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID, ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER, } from '@backstage/plugin-kubernetes-common'; -import { ClusterDetails, KubernetesClustersSupplier } from '../types/types'; -import { AuthenticationStrategy } from '../auth'; +import { + ClusterDetails, + KubernetesClustersSupplier, + AuthenticationStrategy, +} from '@backstage/plugin-kubernetes-node'; export class ConfigClusterLocator implements KubernetesClustersSupplier { private readonly clusterDetails: ClusterDetails[]; diff --git a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.test.ts b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.test.ts index e6e19a6aaa..2b63814f62 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.test.ts @@ -15,7 +15,6 @@ */ import { ANNOTATION_KUBERNETES_AUTH_PROVIDER } from '@backstage/plugin-kubernetes-common'; -import '@backstage/backend-common'; import { ConfigReader, Config } from '@backstage/config'; import { GkeClusterLocator } from './GkeClusterLocator'; import * as container from '@google-cloud/container'; diff --git a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts index 3b3d74e17f..ed95b59217 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts @@ -20,7 +20,10 @@ import { ForwardedError } from '@backstage/errors'; import * as container from '@google-cloud/container'; import { Duration } from 'luxon'; import { runPeriodically } from '../service/runPeriodically'; -import { ClusterDetails, KubernetesClustersSupplier } from '../types/types'; +import { + ClusterDetails, + KubernetesClustersSupplier, +} from '@backstage/plugin-kubernetes-node'; import packageinfo from '../../package.json'; interface MatchResourceLabelEntry { diff --git a/plugins/kubernetes-backend/src/cluster-locator/index.test.ts b/plugins/kubernetes-backend/src/cluster-locator/index.test.ts index bc826db00d..925b9415f4 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/index.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/index.test.ts @@ -20,10 +20,13 @@ import { ANNOTATION_KUBERNETES_AUTH_PROVIDER, } from '@backstage/plugin-kubernetes-common'; import { getCombinedClusterSupplier } from './index'; -import { ClusterDetails } from '../types/types'; -import { AuthenticationStrategy, DispatchStrategy } from '../auth'; +import { + ClusterDetails, + AuthenticationStrategy, +} from '@backstage/plugin-kubernetes-node'; import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; +import { DispatchStrategy } from '../auth'; describe('getCombinedClusterSupplier', () => { it('should retrieve cluster details from config', async () => { diff --git a/plugins/kubernetes-backend/src/cluster-locator/index.ts b/plugins/kubernetes-backend/src/cluster-locator/index.ts index 7b8310b965..c545823c1a 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/index.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/index.ts @@ -17,8 +17,6 @@ import { CatalogApi } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; import { Duration } from 'luxon'; -import { ClusterDetails, KubernetesClustersSupplier } from '../types/types'; -import { AuthenticationStrategy } from '../auth/types'; import { ConfigClusterLocator } from './ConfigClusterLocator'; import { GkeClusterLocator } from './GkeClusterLocator'; import { CatalogClusterLocator } from './CatalogClusterLocator'; @@ -28,6 +26,11 @@ import { BackstageCredentials, LoggerService, } from '@backstage/backend-plugin-api'; +import { + AuthenticationStrategy, + ClusterDetails, + KubernetesClustersSupplier, +} from '@backstage/plugin-kubernetes-node'; class CombinedClustersSupplier implements KubernetesClustersSupplier { constructor( diff --git a/plugins/kubernetes-backend/src/index.test.ts b/plugins/kubernetes-backend/src/index.test.ts index 285d360387..9dc5e9fbfe 100644 --- a/plugins/kubernetes-backend/src/index.test.ts +++ b/plugins/kubernetes-backend/src/index.test.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import '@backstage/backend-common'; - describe('test', () => { it('unbreaks the test runner', () => { expect(true).toBeTruthy(); diff --git a/plugins/kubernetes-backend/src/plugin.ts b/plugins/kubernetes-backend/src/plugin.ts index 44d5df6484..8dfe53364b 100644 --- a/plugins/kubernetes-backend/src/plugin.ts +++ b/plugins/kubernetes-backend/src/plugin.ts @@ -20,8 +20,6 @@ import { } from '@backstage/backend-plugin-api'; import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; -import { KubernetesBuilder } from './service'; - import { type AuthenticationStrategy, kubernetesAuthStrategyExtensionPoint, @@ -39,6 +37,7 @@ import { kubernetesServiceLocatorExtensionPoint, type KubernetesServiceLocatorExtensionPoint, } from '@backstage/plugin-kubernetes-node'; +import { KubernetesBuilder } from './service/KubernetesBuilder'; class ObjectsProvider implements KubernetesObjectsProviderExtensionPoint { private objectsProvider: KubernetesObjectsProvider | undefined; diff --git a/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts b/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts index 462312a404..50c50d7ffa 100644 --- a/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts +++ b/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts @@ -18,30 +18,18 @@ import request from 'supertest'; import { mockCredentials, mockServices, - type ServiceMock, startTestBackend, } from '@backstage/backend-test-utils'; import { kubernetesObjectsProviderExtensionPoint } from '@backstage/plugin-kubernetes-node'; -import { - createBackendModule, - type PermissionsService, -} from '@backstage/backend-plugin-api'; +import { createBackendModule } from '@backstage/backend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { ExtendedHttpServer } from '@backstage/backend-defaults/rootHttpRouter'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; describe('resourcesRoutes', () => { let app: ExtendedHttpServer; - const permissionsMock: ServiceMock = - mockServices.permissions.mock({ - authorize: jest.fn(), - authorizeConditional: jest.fn(), - }); const startPermissionDeniedTestServer = async () => { - permissionsMock.authorize.mockResolvedValue([ - { result: AuthorizeResult.DENY }, - ]); const { server } = await startTestBackend({ features: [ mockServices.rootConfig.factory({ @@ -52,7 +40,7 @@ describe('resourcesRoutes', () => { }, }, }), - permissionsMock.factory, + mockServices.permissions.factory({ result: AuthorizeResult.DENY }), import('@backstage/plugin-kubernetes-backend'), ], }); diff --git a/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.test.ts b/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.test.ts index 093831164e..73dd38848c 100644 --- a/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.test.ts +++ b/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.test.ts @@ -14,9 +14,8 @@ * limitations under the License. */ -import '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; -import { ServiceLocatorRequestContext } from '../types/types'; +import { ServiceLocatorRequestContext } from '@backstage/plugin-kubernetes-node'; import { MultiTenantServiceLocator } from './MultiTenantServiceLocator'; describe('MultiTenantConfigClusterLocator', () => { diff --git a/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.ts b/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.ts index 12946d27b9..7a90f4fe85 100644 --- a/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.ts +++ b/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.ts @@ -20,7 +20,7 @@ import { KubernetesClustersSupplier, KubernetesServiceLocator, ServiceLocatorRequestContext, -} from '../types/types'; +} from '@backstage/plugin-kubernetes-node'; // This locator assumes that every service is located on every cluster // Therefore it will always return all clusters provided diff --git a/plugins/kubernetes-backend/src/service-locator/SingleTenantServiceLocator.test.ts b/plugins/kubernetes-backend/src/service-locator/SingleTenantServiceLocator.test.ts index 7e66ab96ea..398efbc71d 100644 --- a/plugins/kubernetes-backend/src/service-locator/SingleTenantServiceLocator.test.ts +++ b/plugins/kubernetes-backend/src/service-locator/SingleTenantServiceLocator.test.ts @@ -14,9 +14,8 @@ * limitations under the License. */ -import '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; -import { ServiceLocatorRequestContext } from '../types/types'; +import { ServiceLocatorRequestContext } from '@backstage/plugin-kubernetes-node'; import { SingleTenantServiceLocator } from './SingleTenantServiceLocator'; describe('SingleTenantConfigClusterLocator', () => { diff --git a/plugins/kubernetes-backend/src/service-locator/SingleTenantServiceLocator.ts b/plugins/kubernetes-backend/src/service-locator/SingleTenantServiceLocator.ts index f3ea2d49f1..f6885fc4ea 100644 --- a/plugins/kubernetes-backend/src/service-locator/SingleTenantServiceLocator.ts +++ b/plugins/kubernetes-backend/src/service-locator/SingleTenantServiceLocator.ts @@ -19,7 +19,7 @@ import { KubernetesClustersSupplier, KubernetesServiceLocator, ServiceLocatorRequestContext, -} from '../types/types'; +} from '@backstage/plugin-kubernetes-node'; // This locator assumes that service is located on one cluster // Therefore it will always return specified cluster provided in backstage.io/kubernetes-cluster annotation diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts index 3046232bad..4157cf9444 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts @@ -27,15 +27,14 @@ import { ClusterDetails, KubernetesFetcher, KubernetesServiceLocator, -} from '../types/types'; -import { KubernetesCredential } from '../auth/types'; + KubernetesCredential, +} from '@backstage/plugin-kubernetes-node'; import { HEADER_KUBERNETES_CLUSTER, HEADER_KUBERNETES_AUTH, } from './KubernetesProxy'; import { setupServer } from 'msw/node'; import { - ServiceMock, mockCredentials, mockServices, registerMswTestHooks, @@ -43,10 +42,7 @@ import { } from '@backstage/backend-test-utils'; import { rest } from 'msw'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; -import { - PermissionsService, - createBackendModule, -} from '@backstage/backend-plugin-api'; +import { createBackendModule } from '@backstage/backend-plugin-api'; import { AuthMetadata, KubernetesObjectsProvider, @@ -64,11 +60,6 @@ describe('API integration tests', () => { const happyK8SResult = { items: [{ clusterOne: { pods: [{ metadata: { name: 'pod1' } }] } }], }; - const permissionsMock: ServiceMock = - mockServices.permissions.mock({ - authorize: jest.fn(), - authorizeConditional: jest.fn(), - }); const minimalValidConfigService = mockServices.rootConfig.factory({ data: { kubernetes: { @@ -93,13 +84,10 @@ describe('API integration tests', () => { }, }); const startPermissionDeniedTestServer = async () => { - permissionsMock.authorize.mockResolvedValue([ - { result: AuthorizeResult.DENY }, - ]); const { server } = await startTestBackend({ features: [ minimalValidConfigService, - permissionsMock.factory, + mockServices.permissions.factory({ result: AuthorizeResult.DENY }), import('@backstage/plugin-kubernetes-backend'), ], }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts index 36e814bf3c..f86dd6cebb 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts @@ -41,7 +41,6 @@ import { } from '../auth'; import { getCombinedClusterSupplier } from '../cluster-locator'; -import { createLegacyAuthAdapters } from '@backstage/backend-common'; import { AuthService, BackstageCredentials, @@ -87,8 +86,8 @@ export interface KubernetesEnvironment { catalogApi: CatalogApi; discovery: DiscoveryService; permissions: PermissionEvaluator; - auth?: AuthService; - httpAuth?: HttpAuthService; + auth: AuthService; + httpAuth: HttpAuthService; } /** @@ -107,10 +106,6 @@ export type KubernetesBuilderReturn = Promise<{ authStrategyMap: { [key: string]: AuthenticationStrategy }; }>; -/** - * @deprecated Please migrate to the new backend system as this will be removed in the future. - * @public - * */ export class KubernetesBuilder { private clusterSupplier?: KubernetesClustersSupplier; private defaultClusterRefreshInterval: Duration = Duration.fromObject({ @@ -147,12 +142,6 @@ export class KubernetesBuilder { } as unknown as KubernetesBuilderReturn; } - const { auth, httpAuth } = createLegacyAuthAdapters({ - auth: this.env.auth, - httpAuth: this.env.httpAuth, - discovery: this.env.discovery, - }); - const customResources = this.buildCustomResources(); const fetcher = this.getFetcher(); @@ -165,7 +154,7 @@ export class KubernetesBuilder { logger, clusterSupplier, this.env.discovery, - httpAuth, + this.env.httpAuth, ); const serviceLocator = this.getServiceLocator(); @@ -185,8 +174,8 @@ export class KubernetesBuilder { this.env.catalogApi, proxy, permissions, - auth, - httpAuth, + this.env.auth, + this.env.httpAuth, ); return { @@ -268,14 +257,13 @@ export class KubernetesBuilder { refreshInterval: Duration, ): KubernetesClustersSupplier { const config = this.env.config; - const { auth } = createLegacyAuthAdapters(this.env); this.clusterSupplier = getCombinedClusterSupplier( config, this.env.catalogApi, new DispatchStrategy({ authStrategyMap: this.getAuthStrategyMap() }), this.env.logger, refreshInterval, - auth, + this.env.auth, ); return this.clusterSupplier; diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts index 3ca6c98281..f897b78e1f 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts @@ -21,8 +21,8 @@ import { KubernetesServiceLocator, ServiceLocatorRequestContext, ObjectToFetch, -} from '../types/types'; -import { KubernetesCredential } from '../auth/types'; + KubernetesCredential, +} from '@backstage/plugin-kubernetes-node'; import { KubernetesFanOutHandler, DEFAULT_OBJECTS, diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index 091ffebdf5..e525c74077 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -16,7 +16,7 @@ import { ANNOTATION_KUBERNETES_AUTH_PROVIDER } from '@backstage/plugin-kubernetes-common'; import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; -import { ObjectToFetch } from '../types/types'; +import { ObjectToFetch } from '@backstage/plugin-kubernetes-node'; import { MockedRequest, RestContext, diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index d960f01645..4b9b73e488 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -20,7 +20,7 @@ import { FetchResponseWrapper, KubernetesFetcher, ObjectFetchParams, -} from '../types/types'; +} from '@backstage/plugin-kubernetes-node'; import { ANNOTATION_KUBERNETES_AUTH_PROVIDER, SERVICEACCOUNT_CA_PATH, diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts index 3ed8b25c56..3287144299 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts @@ -22,7 +22,6 @@ import { registerMswTestHooks, } from '@backstage/backend-test-utils'; import { NotFoundError } from '@backstage/errors'; -import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { ANNOTATION_KUBERNETES_AUTH_PROVIDER, KubernetesRequestAuth, @@ -38,11 +37,11 @@ import { AddressInfo, WebSocket, WebSocketServer } from 'ws'; import { LocalKubectlProxyClusterLocator } from '../cluster-locator/LocalKubectlProxyLocator'; import { + ClusterDetails, + KubernetesClustersSupplier, AuthenticationStrategy, - AnonymousStrategy, KubernetesCredential, -} from '../auth'; -import { ClusterDetails, KubernetesClustersSupplier } from '../types/types'; +} from '@backstage/plugin-kubernetes-node'; import { APPLICATION_JSON, HEADER_KUBERNETES_AUTH, @@ -53,6 +52,7 @@ import { import type { Request } from 'express'; import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter'; +import { AnonymousStrategy } from '../auth'; const middleware = MiddlewareFactory.create({ logger: mockServices.logger.mock(), @@ -78,8 +78,8 @@ describe('KubernetesProxy', () => { >(), }; - const permissionApi = mockServices.permissions.mock(); - const mockDisocveryApi = mockServices.discovery.mock(); + const permissionApi = mockServices.permissions(); + const mockDiscoveryApi = mockServices.discovery.mock(); registerMswTestHooks(worker); @@ -154,11 +154,9 @@ describe('KubernetesProxy', () => { logger, clusterSupplier, authStrategy, - discovery: mockDisocveryApi, + discovery: mockDiscoveryApi, + httpAuth: mockServices.httpAuth.mock(), }); - permissionApi.authorize.mockResolvedValue([ - { result: AuthorizeResult.ALLOW }, - ]); }); it('should return a ERROR_NOT_FOUND if no clusters are found', async () => { @@ -544,7 +542,8 @@ describe('KubernetesProxy', () => { logger: mockServices.logger.mock(), clusterSupplier: clusterSupplier, authStrategy: strategy, - discovery: mockDisocveryApi, + discovery: mockDiscoveryApi, + httpAuth: mockServices.httpAuth.mock(), }); worker.use( @@ -666,7 +665,8 @@ describe('KubernetesProxy', () => { logger: mockServices.logger.mock(), clusterSupplier: new LocalKubectlProxyClusterLocator(), authStrategy: new AnonymousStrategy(), - discovery: mockDisocveryApi, + discovery: mockDiscoveryApi, + httpAuth: mockServices.httpAuth.mock(), }); worker.use( diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.ts index e176285a49..99d797a670 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.ts @@ -31,8 +31,11 @@ import type { Cluster } from '@kubernetes/client-node'; import { createProxyMiddleware, RequestHandler } from 'http-proxy-middleware'; import fs from 'fs-extra'; -import { AuthenticationStrategy } from '../auth'; -import { ClusterDetails, KubernetesClustersSupplier } from '../types/types'; +import { + AuthenticationStrategy, + ClusterDetails, + KubernetesClustersSupplier, +} from '@backstage/plugin-kubernetes-node'; import type { Request } from 'express'; import { IncomingHttpHeaders } from 'http'; @@ -42,10 +45,6 @@ import { LoggerService, PermissionsService, } from '@backstage/backend-plugin-api'; -import { - createLegacyAuthAdapters, - loggerToWinstonLogger, -} from '@backstage/backend-common'; export const APPLICATION_JSON: string = 'application/json'; @@ -83,7 +82,7 @@ export type KubernetesProxyOptions = { clusterSupplier: KubernetesClustersSupplier; authStrategy: AuthenticationStrategy; discovery: DiscoveryService; - httpAuth?: HttpAuthService; + httpAuth: HttpAuthService; }; /** @@ -102,13 +101,7 @@ export class KubernetesProxy { this.logger = options.logger; this.clusterSupplier = options.clusterSupplier; this.authStrategy = options.authStrategy; - - const legacy = createLegacyAuthAdapters({ - discovery: options.discovery, - httpAuth: options.httpAuth, - }); - - this.httpAuth = legacy.httpAuth; + this.httpAuth = options.httpAuth; } public createRequestHandler( @@ -153,8 +146,13 @@ export class KubernetesProxy { if (!middleware) { const logger = this.logger.child({ cluster: originalCluster.name }); middleware = createProxyMiddleware({ - // TODO: Add 'log' to LoggerService - logProvider: () => loggerToWinstonLogger(logger), + logProvider: () => ({ + log: logger.info.bind(logger), + debug: logger.debug.bind(logger), + info: logger.info.bind(logger), + warn: logger.warn.bind(logger), + error: logger.error.bind(logger), + }), ws: true, secure: !originalCluster.skipTLSVerify, changeOrigin: true, diff --git a/plugins/kubernetes-backend/src/service/index.ts b/plugins/kubernetes-backend/src/service/index.ts index 9c0911de37..56b4ab98d0 100644 --- a/plugins/kubernetes-backend/src/service/index.ts +++ b/plugins/kubernetes-backend/src/service/index.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -export * from './KubernetesBuilder'; export { DEFAULT_OBJECTS } from './KubernetesFanOutHandler'; export { HEADER_KUBERNETES_CLUSTER, @@ -25,4 +24,3 @@ export type { KubernetesProxyCreateRequestHandlerOptions, KubernetesProxyOptions, } from './KubernetesProxy'; -export * from './router'; diff --git a/plugins/kubernetes-backend/src/service/router.ts b/plugins/kubernetes-backend/src/service/router.ts index d1b54c5927..66fc131262 100644 --- a/plugins/kubernetes-backend/src/service/router.ts +++ b/plugins/kubernetes-backend/src/service/router.ts @@ -15,44 +15,29 @@ */ import { Logger } from 'winston'; -import { KubernetesClustersSupplier } from '../types/types'; +import { KubernetesClustersSupplier } from '@backstage/plugin-kubernetes-node'; import express from 'express'; import { KubernetesBuilder } from './KubernetesBuilder'; import { CatalogApi } from '@backstage/catalog-client'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { DiscoveryService, + HttpAuthService, RootConfigService, + AuthService, } from '@backstage/backend-plugin-api'; -/** - * @deprecated Please migrate to the new backend system as this will be removed in the future. - * @public - */ -export interface RouterOptions { +interface RouterOptions { logger: Logger; config: RootConfigService; catalogApi: CatalogApi; clusterSupplier?: KubernetesClustersSupplier; discovery: DiscoveryService; permissions: PermissionEvaluator; + auth: AuthService; + httpAuth: HttpAuthService; } -/** - * creates and configure a new router for handling the kubernetes backend APIs - * @param options - specifies the options required by this plugin - * @returns a new router - * @deprecated Please migrate to the new backend system as this will be removed in the future. - * ``` - * import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend'; - * const { router } = await KubernetesBuilder.createBuilder({ - * logger, - * config, - * }).build(); - * ``` - * - * @public - */ export async function createRouter( options: RouterOptions, ): Promise { diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index a6eae1f279..7427a5efb3 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -47,73 +47,3 @@ export interface KubernetesObjectsProviderOptions { * @public */ export type ObjectsByEntityRequest = KubernetesRequestBody; - -// TODO remove this re-export as a breaking change after a couple of releases -/** - * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead - */ -export type KubernetesObjectsProvider = k8sTypes.KubernetesObjectsProvider; - -/** - * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead - */ -export type CustomResourcesByEntity = k8sTypes.CustomResourcesByEntity; - -/** - * @public - * @deprecated Import it from \@backstage/plugin-kubernetes-node instead - */ -export type AuthMetadata = k8sTypes.AuthMetadata; - -/** - * @public - * @deprecated Import it from \@backstage/plugin-kubernetes-node instead - */ -export type ClusterDetails = k8sTypes.ClusterDetails; - -/** - * @public - * @deprecated Import it from \@backstage/plugin-kubernetes-node instead - */ -export type KubernetesClustersSupplier = k8sTypes.KubernetesClustersSupplier; - -/** - * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead - */ -export type KubernetesObjectTypes = k8sTypes.KubernetesObjectTypes; - -/** - * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead - */ -export type ObjectToFetch = k8sTypes.ObjectToFetch; - -/** - * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead - */ -export type CustomResource = k8sTypes.CustomResource; - -/** - * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead - */ -export type ObjectFetchParams = k8sTypes.ObjectFetchParams; - -/** - * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead - */ -export type FetchResponseWrapper = k8sTypes.FetchResponseWrapper; - -/** - * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead - */ -export type KubernetesFetcher = k8sTypes.KubernetesFetcher; - -/** - * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead - */ -export type ServiceLocatorRequestContext = - k8sTypes.ServiceLocatorRequestContext; - -/** - * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead - */ -export type KubernetesServiceLocator = k8sTypes.KubernetesServiceLocator; diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index aeaf6868ed..d25adab013 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,137 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-kubernetes-common@0.9.6 + - @backstage/plugin-kubernetes-react@0.5.10-next.0 + - @backstage/plugin-permission-react@0.4.36 + +## 0.0.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + +## 0.0.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-kubernetes-common@0.9.6 + - @backstage/plugin-kubernetes-react@0.5.9 + - @backstage/plugin-permission-react@0.4.36 + +## 0.0.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-kubernetes-common@0.9.6-next.0 + - @backstage/plugin-kubernetes-react@0.5.9-next.1 + +## 0.0.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-kubernetes-react@0.5.9-next.0 + - @backstage/plugin-permission-react@0.4.35 + +## 0.0.26 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/plugin-kubernetes-react@0.5.8 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-permission-react@0.4.35 + +## 0.0.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-kubernetes-react@0.5.8-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-permission-react@0.4.34 + +## 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 + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.5.7 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-permission-react@0.4.34 + +## 0.0.25-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/plugin-kubernetes-react@0.5.7-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + ## 0.0.25-next.2 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index c7d0013ad2..f0d69ee7fd 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-next.2", + "version": "0.0.28-next.1", "description": "A Backstage plugin that shows details of Kubernetes clusters", "backstage": { "role": "frontend-plugin", diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index 5527abeec2..64ea6455e4 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-kubernetes-common +## 0.9.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + +## 0.9.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + +## 0.9.5 + +### Patch Changes + +- 216c6b2: Updated dependency `@kubernetes/client-node` to `1.1.2`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/types@1.2.1 + ## 0.9.5-next.0 ### Patch Changes diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index 4956cdff76..cde58de050 100644 --- a/plugins/kubernetes-common/package.json +++ b/plugins/kubernetes-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-common", - "version": "0.9.5-next.0", + "version": "0.9.6", "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", "backstage": { "role": "common-library", diff --git a/plugins/kubernetes-node/CHANGELOG.md b/plugins/kubernetes-node/CHANGELOG.md index 43e94e275a..552646545b 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,88 @@ # @backstage/plugin-kubernetes-node +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.6 + +## 0.3.2 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-kubernetes-common@0.9.6 + +## 0.3.2-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-kubernetes-common@0.9.6-next.0 + +## 0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5 + +## 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 + +- 9cdf001: **BREAKING**: The `PinnipedHelper` class now expects a regular `LoggerService` instance from the new backend system, instead of a Winston logger. + +### Patch Changes + +- 216c6b2: Updated dependency `@kubernetes/client-node` to `1.1.2`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/types@1.2.1 + +## 0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + ## 0.3.0-next.1 ### Minor Changes diff --git a/plugins/kubernetes-node/knip-report.md b/plugins/kubernetes-node/knip-report.md index 7df8949921..1c25fa062b 100644 --- a/plugins/kubernetes-node/knip-report.md +++ b/plugins/kubernetes-node/knip-report.md @@ -1,5 +1,11 @@ # Knip report +## Unused dependencies (1) + +| Name | Location | Severity | +| :------ | :----------- | :------- | +| winston | package.json | error | + ## Unused devDependencies (1) | Name | Location | Severity | diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index ef81490e0a..f49590e79d 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-next.1", + "version": "0.3.3-next.0", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library", diff --git a/plugins/kubernetes-node/report.api.md b/plugins/kubernetes-node/report.api.md index af0c2c599a..5758db9ea1 100644 --- a/plugins/kubernetes-node/report.api.md +++ b/plugins/kubernetes-node/report.api.md @@ -5,7 +5,6 @@ ```ts import { AuthenticationStrategy as AuthenticationStrategy_2 } from '@backstage/plugin-kubernetes-node'; import { BackstageCredentials } from '@backstage/backend-plugin-api'; -import { ClusterDetails as ClusterDetails_2 } from '@backstage/plugin-kubernetes-node'; import { CustomResourceMatcher } from '@backstage/plugin-kubernetes-common'; import { Entity } from '@backstage/catalog-model'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; @@ -256,7 +255,7 @@ export class PinnipedHelper { constructor(logger: LoggerService); // (undocumented) tokenCredentialRequest( - clusterDetails: ClusterDetails_2, + clusterDetails: ClusterDetails, pinnipedParams: PinnipedParameters, ): Promise; } diff --git a/plugins/kubernetes-node/src/auth/PinnipedHelper.ts b/plugins/kubernetes-node/src/auth/PinnipedHelper.ts index 39421ecd62..d6c39d55de 100644 --- a/plugins/kubernetes-node/src/auth/PinnipedHelper.ts +++ b/plugins/kubernetes-node/src/auth/PinnipedHelper.ts @@ -15,9 +15,9 @@ */ import { LoggerService } from '@backstage/backend-plugin-api'; -import { ClusterDetails } from '@backstage/plugin-kubernetes-node'; import * as https from 'https'; import fetch, { RequestInit } from 'node-fetch'; +import { ClusterDetails } from '../types/types'; /** * diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index 51e96bbea9..f9f9761e9f 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,89 @@ # @backstage/plugin-kubernetes-react +## 0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.6 + +## 0.5.9 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-kubernetes-common@0.9.6 + +## 0.5.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-kubernetes-common@0.9.6-next.0 + +## 0.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5 + +## 0.5.8 + +### Patch Changes + +- 3ea5d1b: chore(deps): Update deprecated xterm to new package under @xterm +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5 + +## 0.5.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5 + +## 0.5.7 + +### Patch Changes + +- 599c89e: Add headlamp formatter +- 216c6b2: Updated dependency `@kubernetes/client-node` to `1.1.2`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.5.7-next.1 ### Patch Changes diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index 0dd828d88d..54a92a9b4f 100644 --- a/plugins/kubernetes-react/package.json +++ b/plugins/kubernetes-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-react", - "version": "0.5.7-next.1", + "version": "0.5.10-next.0", "description": "Web library for the kubernetes-react plugin", "backstage": { "role": "web-library", @@ -66,15 +66,15 @@ "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.11.3", "@material-ui/lab": "^4.0.0-alpha.61", + "@xterm/addon-attach": "^0.11.0", + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", "cronstrue": "^2.32.0", "js-yaml": "^4.1.0", "kubernetes-models": "^4.3.1", "lodash": "^4.17.21", "luxon": "^3.0.0", - "react-use": "^17.4.0", - "xterm": "^5.3.0", - "xterm-addon-attach": "^0.9.0", - "xterm-addon-fit": "^0.8.0" + "react-use": "^17.4.0" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminal.tsx b/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminal.tsx index eeaf535a05..a3ef823356 100644 --- a/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminal.tsx +++ b/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminal.tsx @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import 'xterm/css/xterm.css'; +import '@xterm/xterm/css/xterm.css'; import { discoveryApiRef, useApi } from '@backstage/core-plugin-api'; import { ClusterAttributes } from '@backstage/plugin-kubernetes-common'; import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; import { useRef, useEffect, useMemo, useState } from 'react'; -import { Terminal } from 'xterm'; -import { FitAddon } from 'xterm-addon-fit'; +import { Terminal } from '@xterm/xterm'; +import { FitAddon } from '@xterm/addon-fit'; import { PodExecTerminalAttachAddon } from './PodExecTerminalAttachAddon'; diff --git a/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminalAttachAddon.ts b/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminalAttachAddon.ts index dba58a6746..55171e1cb1 100644 --- a/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminalAttachAddon.ts +++ b/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminalAttachAddon.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { AttachAddon, IAttachOptions } from 'xterm-addon-attach'; +import { AttachAddon, IAttachOptions } from '@xterm/addon-attach'; export class PodExecTerminalAttachAddon extends AttachAddon { #textEncoder = new TextEncoder(); diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index b2c602a222..33a875f62d 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,191 @@ # @backstage/plugin-kubernetes +## 0.12.10-next.3 + +### Patch Changes + +- 3025cf5: Removed the kubernetes content padding to avoid double padding on k8s entity page +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.2 + - @backstage/core-compat-api@0.5.0-next.3 + - @backstage/core-components@0.17.5-next.2 + +## 0.12.10-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-kubernetes-common@0.9.6 + - @backstage/plugin-kubernetes-react@0.5.10-next.0 + - @backstage/plugin-permission-react@0.4.36 + +## 0.12.10-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-kubernetes-common@0.9.6 + - @backstage/plugin-kubernetes-react@0.5.10-next.0 + - @backstage/plugin-permission-react@0.4.36 + +## 0.12.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.12.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-kubernetes-common@0.9.6 + - @backstage/plugin-kubernetes-react@0.5.9 + - @backstage/plugin-permission-react@0.4.36 + +## 0.12.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-kubernetes-common@0.9.6-next.0 + - @backstage/plugin-kubernetes-react@0.5.9-next.1 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 0.12.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-kubernetes-react@0.5.9-next.0 + - @backstage/plugin-permission-react@0.4.35 + +## 0.12.8 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- 3ea5d1b: chore(deps): Update deprecated xterm to new package under @xterm +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/plugin-kubernetes-react@0.5.8 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-permission-react@0.4.35 + +## 0.12.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/plugin-kubernetes-react@0.5.8-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-permission-react@0.4.34 + +## 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 + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 216c6b2: Updated dependency `@kubernetes/client-node` to `1.1.2`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/plugin-kubernetes-react@0.5.7 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-permission-react@0.4.34 + +## 0.12.7-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/plugin-kubernetes-react@0.5.7-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + ## 0.12.7-next.2 ### Patch Changes diff --git a/plugins/kubernetes/knip-report.md b/plugins/kubernetes/knip-report.md index a20fd463aa..bb11f63d0d 100644 --- a/plugins/kubernetes/knip-report.md +++ b/plugins/kubernetes/knip-report.md @@ -7,12 +7,12 @@ | @kubernetes-models/apimachinery | package.json | error | | @kubernetes-models/base | package.json | error | | @kubernetes/client-node | package.json | error | -| xterm-addon-attach | package.json | error | +| @xterm/addon-attach | package.json | error | | kubernetes-models | package.json | error | -| xterm-addon-fit | package.json | error | +| @xterm/addon-fit | package.json | error | +| @xterm/xterm | package.json | error | | cronstrue | package.json | error | | js-yaml | package.json | error | | lodash | package.json | error | | luxon | package.json | error | -| xterm | package.json | error | diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 60c232bc51..d60d5c1afa 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.12.7-next.2", + "version": "0.12.10-next.3", "description": "A Backstage plugin that integrates towards Kubernetes", "backstage": { "role": "frontend-plugin", @@ -71,14 +71,14 @@ "@kubernetes-models/base": "^5.0.0", "@kubernetes/client-node": "1.1.2", "@material-ui/core": "^4.12.2", + "@xterm/addon-attach": "^0.11.0", + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", "cronstrue": "^2.2.0", "js-yaml": "^4.0.0", "kubernetes-models": "^4.1.0", "lodash": "^4.17.21", - "luxon": "^3.0.0", - "xterm": "^5.2.1", - "xterm-addon-attach": "^0.9.0", - "xterm-addon-fit": "^0.8.0" + "luxon": "^3.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/kubernetes/report-alpha.api.md b/plugins/kubernetes/report-alpha.api.md index e6cc199ae8..e8ad7bb2ff 100644 --- a/plugins/kubernetes/report-alpha.api.md +++ b/plugins/kubernetes/report-alpha.api.md @@ -5,10 +5,12 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { defaultEntityContentGroups } from '@backstage/plugin-catalog-react/alpha'; import { Entity } from '@backstage/catalog-model'; import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; @@ -27,60 +29,60 @@ const _default: FrontendPlugin< name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:kubernetes/auth-providers': ExtensionDefinition<{ kind: 'api'; name: 'auth-providers'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:kubernetes/cluster-link-formatter': ExtensionDefinition<{ kind: 'api'; name: 'cluster-link-formatter'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'api:kubernetes/proxy': ExtensionDefinition<{ kind: 'api'; name: 'proxy'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'entity-content:kubernetes/kubernetes': ExtensionDefinition<{ kind: 'entity-content'; @@ -98,35 +100,31 @@ const _default: FrontendPlugin< group?: string | false | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { optional: true; } > - | ConfigurableExtensionDataRef< - string, - 'catalog.entity-content-title', - {} - > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< string, 'catalog.entity-content-group', { @@ -135,10 +133,13 @@ const _default: FrontendPlugin< >; inputs: {}; params: { + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; + defaultTitle?: [Error: `Use the 'title' param instead`]; + title: string; + defaultGroup?: [Error: `Use the 'group' param instead`]; + group?: keyof defaultEntityContentGroups | (string & {}); loader: () => Promise; - defaultPath: string; - defaultTitle: string; - defaultGroup?: keyof defaultEntityContentGroups | (string & {}); routeRef?: RouteRef; filter?: string | EntityPredicate | ((entity: Entity) => boolean); }; @@ -153,9 +154,9 @@ const _default: FrontendPlugin< path?: string | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { @@ -164,7 +165,8 @@ const _default: FrontendPlugin< >; inputs: {}; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise; routeRef?: RouteRef; }; diff --git a/plugins/kubernetes/src/KubernetesContent.tsx b/plugins/kubernetes/src/KubernetesContent.tsx index 5a9df819c8..a8d62a5a3d 100644 --- a/plugins/kubernetes/src/KubernetesContent.tsx +++ b/plugins/kubernetes/src/KubernetesContent.tsx @@ -29,12 +29,7 @@ import { DetectedError, detectErrors, } from '@backstage/plugin-kubernetes-common'; -import { - Content, - EmptyState, - Page, - Progress, -} from '@backstage/core-components'; +import { EmptyState, Progress } from '@backstage/core-components'; import { RequireKubernetesPermissions } from './RequireKubernetesPermissions'; type KubernetesContentProps = { @@ -63,93 +58,87 @@ export const KubernetesContent = ({ : new Map(); return ( - - - - - {kubernetesObjects === undefined && error === undefined && ( - - )} + + + {kubernetesObjects === undefined && error === undefined && } - {/* errors retrieved from the kubernetes clusters */} - {clustersWithErrors.length > 0 && ( - - - - - - )} + {/* errors retrieved from the kubernetes clusters */} + {clustersWithErrors.length > 0 && ( + + + + + + )} - {/* other errors */} - {error !== undefined && ( - - - - - - )} + {/* other errors */} + {error !== undefined && ( + + + + + + )} - {kubernetesObjects && ( - - - + {kubernetesObjects && ( + + + + + + Your Clusters + + + {kubernetesObjects?.items.length <= 0 && ( + + + + - - Your Clusters - - - {kubernetesObjects?.items.length <= 0 && ( - - - - + )} + {kubernetesObjects?.items.length > 0 && + kubernetesObjects?.items.map((item, i) => { + const podsWithErrors = new Set( + detectedErrors + .get(item.cluster.name) + ?.filter(de => de.sourceRef.kind === 'Pod') + .map(de => de.sourceRef.name), + ); + + return ( + + - )} - {kubernetesObjects?.items.length > 0 && - kubernetesObjects?.items.map((item, i) => { - const podsWithErrors = new Set( - detectedErrors - .get(item.cluster.name) - ?.filter(de => de.sourceRef.kind === 'Pod') - .map(de => de.sourceRef.name), - ); - - return ( - - - - ); - })} - - - )} - - - - + ); + })} + + + )} + + ); }; diff --git a/plugins/kubernetes/src/alpha/apis.tsx b/plugins/kubernetes/src/alpha/apis.tsx index 082a2da6c7..116da5251c 100644 --- a/plugins/kubernetes/src/alpha/apis.tsx +++ b/plugins/kubernetes/src/alpha/apis.tsx @@ -16,7 +16,6 @@ import { ApiBlueprint, - createApiFactory, discoveryApiRef, fetchApiRef, } from '@backstage/frontend-plugin-api'; @@ -41,8 +40,8 @@ import { } from '@backstage/core-plugin-api'; export const kubernetesApiExtension = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: kubernetesApiRef, deps: { discoveryApi: discoveryApiRef, @@ -56,13 +55,12 @@ export const kubernetesApiExtension = ApiBlueprint.make({ kubernetesAuthProvidersApi, }), }), - }, }); export const kubernetesProxyApi = ApiBlueprint.make({ name: 'proxy', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: kubernetesProxyApiRef, deps: { kubernetesApi: kubernetesApiRef, @@ -72,13 +70,12 @@ export const kubernetesProxyApi = ApiBlueprint.make({ kubernetesApi, }), }), - }, }); export const kubernetesAuthProvidersApi = ApiBlueprint.make({ name: 'auth-providers', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: kubernetesAuthProvidersApiRef, deps: { gitlabAuthApi: gitlabAuthApiRef, @@ -109,13 +106,12 @@ export const kubernetesAuthProvidersApi = ApiBlueprint.make({ }); }, }), - }, }); export const kubernetesClusterLinkFormatterApi = ApiBlueprint.make({ name: 'cluster-link-formatter', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: kubernetesClusterLinkFormatterApiRef, deps: { googleAuthApi: googleAuthApiRef }, factory: deps => { @@ -126,5 +122,4 @@ export const kubernetesClusterLinkFormatterApi = ApiBlueprint.make({ }); }, }), - }, }); diff --git a/plugins/kubernetes/src/alpha/entityContents.tsx b/plugins/kubernetes/src/alpha/entityContents.tsx index ad35b401be..d75893c6e4 100644 --- a/plugins/kubernetes/src/alpha/entityContents.tsx +++ b/plugins/kubernetes/src/alpha/entityContents.tsx @@ -21,9 +21,9 @@ import { isKubernetesAvailable } from '../Router'; export const entityKubernetesContent = EntityContentBlueprint.make({ name: 'kubernetes', params: { - defaultPath: '/kubernetes', - defaultTitle: 'Kubernetes', - defaultGroup: 'deployment', + path: '/kubernetes', + title: 'Kubernetes', + group: 'deployment', filter: isKubernetesAvailable, loader: () => import('./KubernetesContentPage').then(m => diff --git a/plugins/kubernetes/src/alpha/pages.tsx b/plugins/kubernetes/src/alpha/pages.tsx index 83a2e1aa94..6a2d98ddcc 100644 --- a/plugins/kubernetes/src/alpha/pages.tsx +++ b/plugins/kubernetes/src/alpha/pages.tsx @@ -23,7 +23,7 @@ import { rootCatalogKubernetesRouteRef } from '../plugin'; export const kubernetesPage = PageBlueprint.make({ params: { - defaultPath: '/kubernetes', + path: '/kubernetes', // you can reuse the existing routeRef // by wrapping into the convertLegacyRouteRef. routeRef: convertLegacyRouteRef(rootCatalogKubernetesRouteRef), 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/mcp-actions-backend/.eslintrc.js b/plugins/mcp-actions-backend/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/mcp-actions-backend/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/mcp-actions-backend/CHANGELOG.md b/plugins/mcp-actions-backend/CHANGELOG.md new file mode 100644 index 0000000000..563f2bdd0b --- /dev/null +++ b/plugins/mcp-actions-backend/CHANGELOG.md @@ -0,0 +1,63 @@ +# @backstage/plugin-mcp-actions-backend + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.1.1 + +### Patch Changes + +- 6bc0799: Fixed the example in the README for generating a static token by adding a subject field +- Updated dependencies + - @backstage/backend-defaults@0.11.1 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-node@1.17.2 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## 0.1.1-next.0 + +### Patch Changes + +- 6bc0799: Fixed the example in the README for generating a static token by adding a subject field +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/catalog-client@1.10.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.1.0 + +### Minor Changes + +- 4ed0fb6: Initial implementation of an `mcp-actions` backend + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 diff --git a/plugins/mcp-actions-backend/README.md b/plugins/mcp-actions-backend/README.md new file mode 100644 index 0000000000..c44bd7f0a6 --- /dev/null +++ b/plugins/mcp-actions-backend/README.md @@ -0,0 +1,135 @@ +# MCP Actions Backend + +This plugin exposes Backstage actions as MCP (Model Context Protocol) tools, allowing AI clients to discover and invoke registered actions in your Backstage backend. + +## Installation + +This plugin is installed via the `@backstage/plugin-mcp-actions-backend` package. To install it to your backend package, run the following command: + +```bash +# From your root directory +yarn --cwd packages/backend add @backstage/plugin-mcp-actions-backend +``` + +Then add the plugin to your backend in `packages/backend/src/index.ts`: + +```ts +const backend = createBackend(); +// ... +backend.add(import('@backstage/plugin-mcp-actions-backend')); +``` + +## Configuration + +### Configuring Actions Registry + +The MCP Actions Backend exposes actions that are registered with the Actions Registry. You can register actions from specific plugins by configuring the `pluginSources` in your app configuration: + +```yaml +backend: + actions: + pluginSources: + - 'catalog' + - 'my-custom-plugin' +``` + +Actions from these plugins will be discovered and exposed as MCP tools. Each action must be registered using the Actions Registry Service in the respective plugin: + +```ts +// In your plugin +import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha'; + +export const myPlugin = createBackendPlugin({ + pluginId: 'my-custom-plugin', + register(env) { + env.registerInit({ + deps: { + actionsRegistry: actionsRegistryServiceRef, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'greet-user', + title: 'Greet User', + description: 'Generate a personalized greeting', + schema: { + input: z => + z.object({ + name: z.string().describe('The name of the person to greet'), + }), + output: z => + z.object({ + greeting: z.string().describe('The generated greeting'), + }), + }, + action: async ({ input }) => ({ + output: { greeting: `Hello ${input.name}!` }, + }), + }); + }, + }); + }, +}); +``` + +### Authentication Configuration + +By default, the Backstage backend requires authentication for all requests. + +#### External Access with Static Tokens + +> This is meant to be a temporary workaround until work on [device authentication](https://github.com/backstage/backstage/pull/27680) is completed. +> This will make authentication for MCP clients and CLI's in Backstage easier than having to configure static tokens. + +Configure external access with static tokens in your app configuration: + +```yaml +backend: + auth: + externalAccess: + - type: static + options: + token: ${MCP_TOKEN} + subject: mcp-clients + accessRestrictions: + - plugin: mcp-actions + - plugin: catalog +``` + +Generate a secure token: + +```bash +node -p 'require("crypto").randomBytes(24).toString("base64")' +``` + +Set the `MCP_TOKEN` environment variable with this token, and configure your MCP client to use it in the [Authorization header](#configuring-mcp-clients) + +## Configuring MCP Clients + +The MCP server supports both Server-Sent Events (SSE) and Streamable HTTP protocols. + +The SSE protocol is deprecated, and should be avoided as it will be removed in a future release. + +- `Streamable HTTP`: `http://localhost:7007/api/mcp-actions/v1` +- `SSE`: `http://localhost:7007/api/mcp-actions/v1/sse` + +There's a few different ways to configure MCP tools, but here's a snippet of the most common. + +```json +{ + "mcpServers": { + "backstage-actions": { + // you can also replace this with the public / internal URL of the deployed backend. + "url": "http://localhost:7007/api/mcp-actions/v1", + "headers": { + "Authorization": "Bearer ${MCP_TOKEN}" + } + } + } +} +``` + +## Development + +This plugin backend can be started in a standalone mode from directly in this package with `yarn start`. It is a limited setup that is most convenient when developing the plugin backend itself. + +If you want to run the entire project, including the frontend, run `yarn start` from the root directory. diff --git a/plugins/mcp-actions-backend/catalog-info.yaml b/plugins/mcp-actions-backend/catalog-info.yaml new file mode 100644 index 0000000000..1a0199210e --- /dev/null +++ b/plugins/mcp-actions-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-mcp-actions-backend + title: '@backstage/plugin-mcp-actions-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/mcp-actions-backend/dev/index.ts b/plugins/mcp-actions-backend/dev/index.ts new file mode 100644 index 0000000000..74a6d814f8 --- /dev/null +++ b/plugins/mcp-actions-backend/dev/index.ts @@ -0,0 +1,84 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createBackend } from '@backstage/backend-defaults'; +import { createBackendPlugin } from '@backstage/backend-plugin-api'; +import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha'; +import { mockServices } from '@backstage/backend-test-utils'; +import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; + +const backend = createBackend(); + +backend.add(mockServices.auth.factory()); +backend.add(mockServices.httpAuth.factory()); + +// TEMPLATE NOTE: +// Rather than using a real catalog you can use a mock with a fixed set of entities. +backend.add( + catalogServiceMock.factory({ + entities: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'sample', + title: 'Sample Component', + }, + spec: { + type: 'service', + }, + }, + ], + }), +); + +backend.add( + createBackendPlugin({ + pluginId: 'local', + register({ registerInit }) { + registerInit({ + deps: { + actionsRegistry: actionsRegistryServiceRef, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'make-greeting', + title: 'Test Action', + description: 'Test Action', + schema: { + input: z => + z.object({ + name: z.string(), + }), + output: z => + z.object({ + greeting: z.string(), + }), + }, + action: async ({ input }) => ({ + output: { + greeting: `Hello ${input.name}!`, + }, + }), + }); + }, + }); + }, + }), +); + +backend.add(import('../src')); + +backend.start(); diff --git a/plugins/mcp-actions-backend/knip-report.md b/plugins/mcp-actions-backend/knip-report.md new file mode 100644 index 0000000000..830018776f --- /dev/null +++ b/plugins/mcp-actions-backend/knip-report.md @@ -0,0 +1,9 @@ +# Knip report + +## Unused dependencies (2) + +| Name | Location | Severity | +| :------------------------ | :----------- | :------- | +| @backstage/catalog-client | package.json | error | +| zod | package.json | error | + diff --git a/plugins/mcp-actions-backend/package.json b/plugins/mcp-actions-backend/package.json new file mode 100644 index 0000000000..ea3463b509 --- /dev/null +++ b/plugins/mcp-actions-backend/package.json @@ -0,0 +1,53 @@ +{ + "name": "@backstage/plugin-mcp-actions-backend", + "version": "0.1.2-next.0", + "backstage": { + "role": "backend-plugin", + "pluginId": "mcp-actions", + "pluginPackages": [ + "@backstage/plugin-mcp-actions-backend" + ] + }, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/mcp-actions-backend" + }, + "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/backend-defaults": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/catalog-client": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", + "@backstage/types": "workspace:^", + "@modelcontextprotocol/sdk": "^1.12.3", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", + "zod": "^3.22.4" + }, + "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "@types/express": "^4.17.6" + } +} diff --git a/plugins/kubernetes-backend/report-alpha.api.md b/plugins/mcp-actions-backend/report.api.md similarity index 61% rename from plugins/kubernetes-backend/report-alpha.api.md rename to plugins/mcp-actions-backend/report.api.md index 77bd4e1774..2f1b9fcbf0 100644 --- a/plugins/kubernetes-backend/report-alpha.api.md +++ b/plugins/mcp-actions-backend/report.api.md @@ -1,13 +1,13 @@ -## API Report File for "@backstage/plugin-kubernetes-backend" +## API Report File for "@backstage/plugin-mcp-actions-backend" > 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'; -// @alpha (undocumented) -const _feature: BackendFeature; -export default _feature; +// @public +const mcpPlugin: BackendFeature; +export default mcpPlugin; // (No @packageDocumentation comment for this package) ``` diff --git a/canon-docs/src/components/Banner/index.ts b/plugins/mcp-actions-backend/src/index.ts similarity index 86% rename from canon-docs/src/components/Banner/index.ts rename to plugins/mcp-actions-backend/src/index.ts index 31f4aa88b0..75fa097c85 100644 --- a/canon-docs/src/components/Banner/index.ts +++ b/plugins/mcp-actions-backend/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { Banner } from './Banner'; +export { mcpPlugin as default } from './plugin'; diff --git a/plugins/mcp-actions-backend/src/plugin.test.ts b/plugins/mcp-actions-backend/src/plugin.test.ts new file mode 100644 index 0000000000..ace988cfc1 --- /dev/null +++ b/plugins/mcp-actions-backend/src/plugin.test.ts @@ -0,0 +1,165 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { mcpPlugin } from './plugin'; +import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha'; +import { createBackendPlugin } from '@backstage/backend-plugin-api'; +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; +import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'; +import { ListToolsResultSchema } from '@modelcontextprotocol/sdk/types'; + +describe('Mcp Backend', () => { + const mockPluginWithActions = createBackendPlugin({ + pluginId: 'local', + register({ registerInit }) { + registerInit({ + deps: { actionsRegistry: actionsRegistryServiceRef }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'make-greeting', + title: 'Make Greeting', + description: 'Make a greeting', + schema: { + input: z => z.object({ name: z.string() }), + output: z => z.object({ greeting: z.string() }), + }, + action: async ({ input }) => ({ + output: { greeting: `Hello ${input.name}!` }, + }), + }); + }, + }); + }, + }); + + const getContext = async () => { + const { server } = await startTestBackend({ + features: [ + mcpPlugin, + mockPluginWithActions, + mockServices.rootConfig.factory({ + data: { + backend: { + actions: { + pluginSources: ['local'], + }, + }, + }, + }), + ], + }); + + const client = new Client({ + name: 'test client', + version: '1.0', + }); + + const address = server.address(); + if (typeof address !== 'object' || !('port' in address!)) { + throw new Error('server broke'); + } + + return { + client, + serverAddress: `http://localhost:${address.port}`, + }; + }; + + it('should support streamable spec', async () => { + const { client, serverAddress } = await getContext(); + const transport = new StreamableHTTPClientTransport( + new URL(`${serverAddress}/api/mcp-actions/v1`), + ); + + await client.connect(transport); + + const result = await client.request( + { + method: 'tools/list', + }, + ListToolsResultSchema, + ); + + expect(result.tools).toEqual([ + { + annotations: { + destructiveHint: true, + idempotentHint: false, + openWorldHint: false, + readOnlyHint: false, + title: 'Make Greeting', + }, + description: 'Make a greeting', + inputSchema: { + $schema: 'http://json-schema.org/draft-07/schema#', + additionalProperties: false, + properties: { + name: { + type: 'string', + }, + }, + required: ['name'], + type: 'object', + }, + name: 'make-greeting', + }, + ]); + }); + + it('should support sse spec', async () => { + const { client, serverAddress } = await getContext(); + const transport = new SSEClientTransport( + new URL(`${serverAddress}/api/mcp-actions/v1/sse`), + ); + + await client.connect(transport); + + const result = await client.request( + { + method: 'tools/list', + }, + ListToolsResultSchema, + ); + + await client.close(); + + expect(result.tools).toEqual([ + { + annotations: { + destructiveHint: true, + idempotentHint: false, + openWorldHint: false, + readOnlyHint: false, + title: 'Make Greeting', + }, + description: 'Make a greeting', + inputSchema: { + $schema: 'http://json-schema.org/draft-07/schema#', + additionalProperties: false, + properties: { + name: { + type: 'string', + }, + }, + required: ['name'], + type: 'object', + }, + name: 'make-greeting', + }, + ]); + }); +}); diff --git a/plugins/mcp-actions-backend/src/plugin.ts b/plugins/mcp-actions-backend/src/plugin.ts new file mode 100644 index 0000000000..f1e89417e9 --- /dev/null +++ b/plugins/mcp-actions-backend/src/plugin.ts @@ -0,0 +1,72 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { json, Router } from 'express'; +import { McpService } from './services/McpService'; +import { createStreamableRouter } from './routers/createStreamableRouter'; +import { createSseRouter } from './routers/createSseRouter'; +import { + actionsRegistryServiceRef, + actionsServiceRef, +} from '@backstage/backend-plugin-api/alpha'; + +/** + * mcpPlugin backend plugin + * + * @public + */ +export const mcpPlugin = createBackendPlugin({ + pluginId: 'mcp-actions', + register(env) { + env.registerInit({ + deps: { + logger: coreServices.logger, + auth: coreServices.auth, + httpAuth: coreServices.httpAuth, + httpRouter: coreServices.httpRouter, + actions: actionsServiceRef, + registry: actionsRegistryServiceRef, + }, + async init({ actions, logger, httpRouter, httpAuth }) { + const mcpService = await McpService.create({ + actions, + }); + + const sseRouter = createSseRouter({ + mcpService, + httpAuth, + }); + + const streamableRouter = createStreamableRouter({ + mcpService, + httpAuth, + logger, + }); + + const router = Router(); + router.use(json()); + + router.use('/v1/sse', sseRouter); + router.use('/v1', streamableRouter); + + httpRouter.use(router); + }, + }); + }, +}); diff --git a/plugins/mcp-actions-backend/src/routers/createSseRouter.ts b/plugins/mcp-actions-backend/src/routers/createSseRouter.ts new file mode 100644 index 0000000000..19bcf288e9 --- /dev/null +++ b/plugins/mcp-actions-backend/src/routers/createSseRouter.ts @@ -0,0 +1,73 @@ +/* + * 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 PromiseRouter from 'express-promise-router'; +import { Router } from 'express'; +import { McpService } from '../services/McpService'; +import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; +import { HttpAuthService } from '@backstage/backend-plugin-api'; + +/** + * Legacy SSE endpoint for older clients, hopefully will not be needed for much longer. + */ +export const createSseRouter = ({ + mcpService, + httpAuth, +}: { + mcpService: McpService; + httpAuth: HttpAuthService; +}): Router => { + const router = PromiseRouter(); + const transportsToSessionId = new Map(); + + router.get('/', async (req, res) => { + const server = mcpService.getServer({ + credentials: await httpAuth.credentials(req), + }); + + const transport = new SSEServerTransport( + `${req.originalUrl}/messages`, + res, + ); + + transportsToSessionId.set(transport.sessionId, transport); + + res.on('close', () => { + transportsToSessionId.delete(transport.sessionId); + }); + + await server.connect(transport); + }); + + router.post('/messages', async (req, res) => { + const sessionId = req.query.sessionId as string; + + if (!sessionId) { + res.status(400).contentType('text/plain').write('sessionId is required'); + return; + } + + const transport = transportsToSessionId.get(sessionId); + if (transport) { + await transport.handlePostMessage(req, res, req.body); + } else { + res + .status(400) + .contentType('text/plain') + .write(`No transport found for sessionId "${sessionId}"`); + } + }); + return router; +}; diff --git a/plugins/mcp-actions-backend/src/routers/createStreamableRouter.ts b/plugins/mcp-actions-backend/src/routers/createStreamableRouter.ts new file mode 100644 index 0000000000..9d6fd1840b --- /dev/null +++ b/plugins/mcp-actions-backend/src/routers/createStreamableRouter.ts @@ -0,0 +1,100 @@ +/* + * 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 PromiseRouter from 'express-promise-router'; +import { Router } from 'express'; +import { McpService } from '../services/McpService'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import { HttpAuthService, LoggerService } from '@backstage/backend-plugin-api'; +import { isError } from '@backstage/errors'; + +export const createStreamableRouter = ({ + mcpService, + httpAuth, + logger, +}: { + mcpService: McpService; + logger: LoggerService; + httpAuth: HttpAuthService; +}): Router => { + const router = PromiseRouter(); + + router.post('/', async (req, res) => { + try { + const server = mcpService.getServer({ + credentials: await httpAuth.credentials(req), + }); + + const transport = new StreamableHTTPServerTransport({ + // stateless implementation for now, so that we can support multiple + // instances of the server backend, and avoid sticky sessions. + sessionIdGenerator: undefined, + }); + + await server.connect(transport); + await transport.handleRequest(req, res, req.body); + + res.on('close', () => { + transport.close(); + server.close(); + }); + } catch (error) { + if (isError(error)) { + logger.error(error.message); + } + + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: '2.0', + error: { + code: -32603, + message: 'Internal server error', + }, + id: null, + }); + } + } + }); + + router.get('/', async (_, res) => { + // We only support POST requests, so we return a 405 error for all other methods. + res.writeHead(405).end( + JSON.stringify({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Method not allowed.', + }, + id: null, + }), + ); + }); + + router.delete('/', async (_, res) => { + // We only support POST requests, so we return a 405 error for all other methods. + res.writeHead(405).end( + JSON.stringify({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Method not allowed.', + }, + id: null, + }), + ); + }); + + return router; +}; diff --git a/plugins/mcp-actions-backend/src/services/McpService.test.ts b/plugins/mcp-actions-backend/src/services/McpService.test.ts new file mode 100644 index 0000000000..e3339be1d5 --- /dev/null +++ b/plugins/mcp-actions-backend/src/services/McpService.test.ts @@ -0,0 +1,191 @@ +/* + * 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 } from '@backstage/backend-test-utils'; +import { McpService } from './McpService'; +import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; +import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'; +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { + CallToolResultSchema, + ListToolsResultSchema, +} from '@modelcontextprotocol/sdk/types.js'; + +describe('McpService', () => { + it('should list the available actions as tools in the mcp backend', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockActionsRegistry.register({ + name: 'mock-action', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({ input: z.string() }), + output: z => z.object({ output: z.string() }), + }, + action: async () => ({ output: { output: 'test' } }), + }); + + const mcpService = await McpService.create({ + actions: mockActionsRegistry, + }); + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + }); + + const client = new Client({ + name: 'test client', + version: '1.0', + }); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { + method: 'tools/list', + }, + ListToolsResultSchema, + ); + + expect(result.tools).toEqual([ + { + annotations: { + destructiveHint: true, + idempotentHint: false, + openWorldHint: false, + readOnlyHint: false, + title: 'Test', + }, + description: 'Test', + inputSchema: { + $schema: 'http://json-schema.org/draft-07/schema#', + additionalProperties: false, + properties: { + input: { + type: 'string', + }, + }, + required: ['input'], + type: 'object', + }, + name: 'mock-action', + }, + ]); + }); + + it('should call the action when the tool is invoked', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockAction = jest.fn(async () => ({ output: { output: 'test' } })); + + mockActionsRegistry.register({ + name: 'mock-action', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({ input: z.string() }), + output: z => z.object({ output: z.string() }), + }, + action: mockAction, + }); + + const mcpService = await McpService.create({ + actions: mockActionsRegistry, + }); + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + }); + + const client = new Client({ + name: 'test client', + version: '1.0', + }); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { + method: 'tools/call', + params: { name: 'mock-action', arguments: { input: 'test' } }, + }, + CallToolResultSchema, + ); + + expect(mockAction).toHaveBeenCalledWith( + expect.objectContaining({ + credentials: mockCredentials.user(), + input: { input: 'test' }, + logger: expect.anything(), + }), + ); + + expect(result.content).toEqual([ + { + type: 'text', + text: [ + '```json', + JSON.stringify({ output: 'test' }, null, 2), + '```', + ].join('\n'), + }, + ]); + }); + + it('should return an error when the action is not found', async () => { + const mcpService = await McpService.create({ + actions: actionsRegistryServiceMock(), + }); + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + }); + + const client = new Client({ + name: 'test client', + version: '1.0', + }); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + await expect( + client.request( + { + method: 'tools/call', + params: { name: 'mock-action', arguments: { input: 'test' } }, + }, + CallToolResultSchema, + ), + ).rejects.toThrow('Action "mock-action" not found'); + }); +}); diff --git a/plugins/mcp-actions-backend/src/services/McpService.ts b/plugins/mcp-actions-backend/src/services/McpService.ts new file mode 100644 index 0000000000..9c44e6d4c1 --- /dev/null +++ b/plugins/mcp-actions-backend/src/services/McpService.ts @@ -0,0 +1,98 @@ +/* + * 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 { BackstageCredentials } from '@backstage/backend-plugin-api'; +import { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js'; +import { + ListToolsRequestSchema, + CallToolRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { JsonObject } from '@backstage/types'; +import { ActionsService } from '@backstage/backend-plugin-api/alpha'; +import { version } from '@backstage/plugin-mcp-actions-backend/package.json'; +import { NotFoundError } from '@backstage/errors'; + +export class McpService { + constructor(private readonly actions: ActionsService) {} + + static async create({ actions }: { actions: ActionsService }) { + return new McpService(actions); + } + + getServer({ credentials }: { credentials: BackstageCredentials }) { + const server = new McpServer( + { + name: 'backstage', + // TODO: this version will most likely change in the future. + version, + }, + { capabilities: { tools: {} } }, + ); + + server.setRequestHandler(ListToolsRequestSchema, async () => { + // TODO: switch this to be configuration based later + const { actions } = await this.actions.list({ credentials }); + + return { + tools: actions.map(action => ({ + inputSchema: action.schema.input, + // todo(blam): this is unfortunately not supported by most clients yet. + // When this is provided you need to provide structuredContent instead. + // outputSchema: action.schema.output, + name: action.name, + description: action.description, + annotations: { + title: action.title, + destructiveHint: action.attributes.destructive, + idempotentHint: action.attributes.idempotent, + readOnlyHint: action.attributes.readOnly, + openWorldHint: false, + }, + })), + }; + }); + + server.setRequestHandler(CallToolRequestSchema, async ({ params }) => { + const { actions } = await this.actions.list({ credentials }); + const action = actions.find(a => a.name === params.name); + + if (!action) { + throw new NotFoundError(`Action "${params.name}" not found`); + } + + const { output } = await this.actions.invoke({ + id: action.id, + input: params.arguments as JsonObject, + credentials, + }); + + return { + // todo(blam): unfortunately structuredContent is not supported by most clients yet. + // so the validation for the output happens in the default actions registry + // and we return it as json text instead for now. + content: [ + { + type: 'text', + text: ['```json', JSON.stringify(output, null, 2), '```'].join( + '\n', + ), + }, + ], + }; + }); + + return server; + } +} diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index f0baa3195d..39e0d0e3d0 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,143 @@ # @backstage/plugin-notifications-backend-module-email +## 0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-notifications-node@0.2.18-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/integration-aws-node@0.1.17 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.10 + +## 0.3.11 + +### Patch Changes + +- f92c9fc: Add optional config for `ses` mail options with `sourceArn`, `fromArn`, `configurationSetName` +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-notifications-node@0.2.17 + +## 0.3.11-next.1 + +### Patch Changes + +- f92c9fc: Add optional config for `ses` mail options with `sourceArn`, `fromArn`, `configurationSetName` + +## 0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration-aws-node@0.1.17-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-notifications-common@0.0.10-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-notifications-node@0.2.17-next.0 + +## 0.3.10 + +### Patch Changes + +- 8a150bf: Internal changes to switch to the non-alpha `catalogServiceRef` +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-notifications-common@0.0.9 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-notifications-node@0.2.16 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + +## 0.3.10-next.2 + +### Patch Changes + +- 8a150bf: Internal changes to switch to the non-alpha `catalogServiceRef` +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.1 + +## 0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-notifications-node@0.2.16-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.0 + +## 0.3.9 + +### Patch Changes + +- aa3a63a: Enable the ability to configure the endpoint for the SES connection used in the notifications email module. This enables the configuration of alternate endpoints as required, for example for local testing or alternative stacks. +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/integration-aws-node@0.1.16 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/plugin-notifications-node@0.2.15 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + +## 0.3.9-next.3 + +### Patch Changes + +- aa3a63a: Enable the ability to configure the endpoint for the SES connection used in the notifications email module. This enables the configuration of alternate endpoints as required, for example for local testing or alternative stacks. +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.15-next.2 + ## 0.3.9-next.2 ### Patch Changes diff --git a/plugins/notifications-backend-module-email/README.md b/plugins/notifications-backend-module-email/README.md index ed5bc22af3..c1ead48876 100644 --- a/plugins/notifications-backend-module-email/README.md +++ b/plugins/notifications-backend-module-email/README.md @@ -79,6 +79,11 @@ notifications: # Who to send email for broadcast notifications broadcastConfig: receiver: 'users' + # Optional SES config + # sesConfig: + # sourceArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com' + # fromArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com' + # configurationSetName: 'custom-config' # How many emails to send concurrently, defaults to 2 concurrencyLimit: 10 # How much to throttle between emails, defaults to 100ms diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts index bce7a4b332..7116826c5d 100644 --- a/plugins/notifications-backend-module-email/config.d.ts +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -64,6 +64,10 @@ export interface Config { * AWS account ID to use */ accountId?: string; + /** + * AWS endpoint to use, defaults to standard AWS endpoint based on region + */ + endpoint?: string; /** * AWS region to use */ @@ -128,6 +132,23 @@ export interface Config { */ receiverEmails?: string[]; }; + /** + * Optional SES config for mail options. Allows for delegated sender + */ + sesConfig?: { + /** + * ARN of the identity to use as the source of the email + */ + sourceArn?: string; + /** + * ARN of the identity to use for the "From"/sender address of the email + */ + fromArn?: string; + /** + * Name of the configuration set to use when sending email via ses + */ + configurationSetName?: string; + }; cache?: { /** * Email cache TTL, defaults to 1 hour diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 566a446aa3..cc75d06224 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-next.2", + "version": "0.3.12-next.0", "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.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts index 1408abcf9c..ebe1b90f35 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts @@ -442,4 +442,64 @@ describe('NotificationsEmailProcessor', () => { to: 'mock@backstage.io', }); }); + + it('should send email with ses config', async () => { + const SES_SENDMAIL_CONFIG = { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + email: { + transportConfig: { + transport: 'ses', + region: 'us-west-2', + }, + sender: 'backstage@backstage.io', + replyTo: 'no-reply@backstage.io', + sesConfig: { + sourceArn: + 'arn:aws:ses:us-west-2:123456789012:identity/example.com', + fromArn: + 'arn:aws:ses:us-west-2:123456789012:identity/example.com', + }, + }, + }, + }, + }; + (createTransport as jest.Mock).mockReturnValue(mockTransport); + const processor = new NotificationsEmailProcessor( + logger, + mockServices.rootConfig({ data: SES_SENDMAIL_CONFIG }), + catalogServiceMock({ entities: [DEFAULT_ENTITIES_RESPONSE.items[0]] }), + auth, + ); + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { title: 'notification' }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { title: 'notification' }, + }, + ); + + expect(sendmailMock).toHaveBeenCalledWith({ + from: 'backstage@backstage.io', + html: '

    https://example.org/notifications

    ', + replyTo: 'no-reply@backstage.io', + subject: 'notification', + text: 'https://example.org/notifications', + to: 'mock@backstage.io', + ses: { + SourceArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com', + FromArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com', + }, + }); + }); }); diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index d165973424..39a0c5da02 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'; @@ -51,6 +52,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private readonly transportConfig: Config; private readonly sender: string; private readonly replyTo?: string; + private readonly sesConfig?: Config; private readonly cacheTtl: number; private readonly concurrencyLimit: number; private readonly throttleInterval: number; @@ -62,7 +64,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, @@ -75,6 +77,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { emailProcessorConfig.getOptionalConfig('broadcastConfig'); this.sender = emailProcessorConfig.getString('sender'); this.replyTo = emailProcessorConfig.getOptionalString('replyTo'); + this.sesConfig = emailProcessorConfig.getOptionalConfig('sesConfig'); this.concurrencyLimit = emailProcessorConfig.getOptionalNumber('concurrencyLimit') ?? 2; this.throttleInterval = emailProcessorConfig.has('throttleInterval') @@ -152,10 +155,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 +162,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { ], fields: ['spec.profile.email'], }, - { token }, + { credentials: await this.auth.getOwnServiceCredentials() }, ); const ret = compact([ ...new Set( @@ -188,11 +187,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; @@ -297,6 +294,24 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return contentParts.join('\n\n'); } + private async getSesOptions() { + if (!this.sesConfig) { + return undefined; + } + const ses: Record = {}; + const sourceArn = this.sesConfig.getOptionalString('sourceArn'); + const fromArn = this.sesConfig.getOptionalString('fromArn'); + const configurationSetName = this.sesConfig.getOptionalString( + 'configurationSetName', + ); + + if (sourceArn) ses.SourceArn = sourceArn; + if (fromArn) ses.FromArn = fromArn; + if (configurationSetName) ses.ConfigurationSetName = configurationSetName; + + return Object.keys(ses).length > 0 ? ses : undefined; + } + private async sendPlainEmail(notification: Notification, emails: string[]) { const mailOptions = { from: this.sender, @@ -304,6 +319,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { html: this.getHtmlContent(notification), text: this.getTextContent(notification), replyTo: this.replyTo, + ses: await this.getSesOptions(), }; await this.sendMails(mailOptions, emails); @@ -321,6 +337,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { html: await this.templateRenderer?.getHtml?.(notification), text: await this.templateRenderer?.getText?.(notification), replyTo: this.replyTo, + ses: await this.getSesOptions(), }; await this.sendMails(mailOptions, emails); diff --git a/plugins/notifications-backend-module-email/src/processor/transports/ses.ts b/plugins/notifications-backend-module-email/src/processor/transports/ses.ts index 0be23078ab..3ac351307e 100644 --- a/plugins/notifications-backend-module-email/src/processor/transports/ses.ts +++ b/plugins/notifications-backend-module-email/src/processor/transports/ses.ts @@ -30,6 +30,7 @@ export const createSesTransport = async ( apiVersion: config.getOptionalString('apiVersion') ?? '2010-12-01', credentials: credentials.sdkCredentialProvider, region: config.getOptionalString('region'), + endpoint: config.getOptionalString('endpoint'), }, ]); return createTransport({ diff --git a/plugins/notifications-backend-module-slack/CHANGELOG.md b/plugins/notifications-backend-module-slack/CHANGELOG.md index 2cdd8848a4..277e77a95e 100644 --- a/plugins/notifications-backend-module-slack/CHANGELOG.md +++ b/plugins/notifications-backend-module-slack/CHANGELOG.md @@ -1,5 +1,120 @@ # @backstage/plugin-notifications-backend-module-slack +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-notifications-node@0.2.18-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.10 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-notifications-node@0.2.17 + +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-notifications-common@0.0.10-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-notifications-node@0.2.17-next.0 + +## 0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-common@0.0.9 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-notifications-node@0.2.16 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 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 + +- 4f10768: Fix slack notification processor to handle a notification with an empty description +- f6480c7: Fix dataloader caching, and use the proper catalog service ref +- a1c5bbb: Added email-based Slack User ID lookup if `metadata.annotations.slack.com/bot-notify` is missing from user entity +- e099d0a: Notifications which mention user entity refs are now replaced with Slack compatible mentions. + + Example: `Welcome <@user:default/billy>!` -> `Welcome <@U123456890>!` + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-notifications-node@0.2.15 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + +## 0.1.1-next.3 + +### Patch Changes + +- f6480c7: Fix dataloader caching, and use the proper catalog service ref +- e099d0a: Notifications which mention user entity refs are now replaced with Slack compatible mentions. + + Example: `Welcome <@user:default/billy>!` -> `Welcome <@U123456890>!` + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.15-next.2 + ## 0.1.1-next.2 ### Patch Changes diff --git a/plugins/notifications-backend-module-slack/knip-report.md b/plugins/notifications-backend-module-slack/knip-report.md new file mode 100644 index 0000000000..eb139623ce --- /dev/null +++ b/plugins/notifications-backend-module-slack/knip-report.md @@ -0,0 +1,17 @@ +# Knip report + +## Unused dependencies (2) + +| Name | Location | Severity | +| :----------- | :----------- | :------- | +| @slack/types | package.json | error | +| @slack/bolt | package.json | error | + +## Unused devDependencies (3) + +| Name | Location | Severity | +| :-------------------- | :----------- | :------- | +| @backstage/test-utils | package.json | error | +| @faker-js/faker | package.json | error | +| msw | package.json | error | + diff --git a/plugins/notifications-backend-module-slack/package.json b/plugins/notifications-backend-module-slack/package.json index ce08a5f002..dcc2a1adcc 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-next.2", + "version": "0.1.4-next.0", "description": "The slack backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", @@ -35,10 +35,10 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-notifications-common": "workspace:^", "@backstage/plugin-notifications-node": "workspace:^", "@backstage/types": "workspace:^", diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts index 2ee328f832..45a74cee70 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts @@ -109,7 +109,6 @@ const DEFAULT_ENTITIES_RESPONSE = { describe('SlackNotificationProcessor', () => { const logger = mockServices.logger.mock(); const auth = mockServices.auth(); - const discovery = mockServices.discovery(); const config = mockServices.rootConfig({ data: { app: { @@ -136,7 +135,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -199,7 +197,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -275,7 +272,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -310,7 +306,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -366,7 +361,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(broadcastConfig, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -405,7 +399,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -429,7 +422,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: [DEFAULT_ENTITIES_RESPONSE.items[2]], @@ -469,7 +461,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -515,7 +506,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -600,7 +590,6 @@ describe('SlackNotificationProcessor', () => { const slack = new WebClient(); const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -641,7 +630,6 @@ describe('SlackNotificationProcessor', () => { const slack = new WebClient(); const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts index 774150676c..c587580657 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts @@ -14,12 +14,7 @@ * limitations under the License. */ -import { - AuthService, - DiscoveryService, - LoggerService, -} from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; +import { AuthService, LoggerService } from '@backstage/backend-plugin-api'; import { Entity, isUserEntity, @@ -39,25 +34,26 @@ import { ChatPostMessageArguments, WebClient } from '@slack/web-api'; import DataLoader from 'dataloader'; import pThrottle from 'p-throttle'; import { ANNOTATION_SLACK_BOT_NOTIFY } from './constants'; -import { toChatPostMessageArgs } from './util'; +import { ExpiryMap, toChatPostMessageArgs } from './util'; +import { CatalogService } from '@backstage/plugin-catalog-node'; export class SlackNotificationProcessor implements NotificationProcessor { private readonly logger: LoggerService; - private readonly catalog: CatalogApi; + private readonly catalog: CatalogService; private readonly auth: AuthService; private readonly slack: WebClient; private readonly sendNotifications; private readonly messagesSent: Counter; private readonly messagesFailed: Counter; private readonly broadcastChannels?: string[]; + private readonly entityLoader: DataLoader; static fromConfig( config: Config, options: { auth: AuthService; - discovery: DiscoveryService; logger: LoggerService; - catalog: CatalogApi; + catalog: CatalogService; slack?: WebClient; broadcastChannels?: string[]; }, @@ -79,9 +75,8 @@ export class SlackNotificationProcessor implements NotificationProcessor { private constructor(options: { slack: WebClient; auth: AuthService; - discovery: DiscoveryService; logger: LoggerService; - catalog: CatalogApi; + catalog: CatalogService; broadcastChannels?: string[]; }) { const { auth, catalog, logger, slack, broadcastChannels } = options; @@ -91,6 +86,31 @@ export class SlackNotificationProcessor implements NotificationProcessor { this.slack = slack; this.broadcastChannels = broadcastChannels; + this.entityLoader = new DataLoader( + async entityRefs => { + return await this.catalog + .getEntitiesByRefs( + { + entityRefs: entityRefs.slice(), + fields: [ + `kind`, + `spec.profile.email`, + `metadata.annotations.${ANNOTATION_SLACK_BOT_NOTIFY}`, + ], + }, + { credentials: await this.auth.getOwnServiceCredentials() }, + ) + .then(r => r.items); + }, + { + name: 'SlackNotificationProcessor.entityLoader', + cacheMap: new ExpiryMap(durationToMilliseconds({ minutes: 10 })), + maxBatchSize: 100, + batchScheduleFn: cb => + setTimeout(cb, durationToMilliseconds({ milliseconds: 10 })), + }, + ); + const meter = metrics.getMeter('default'); this.messagesSent = meter.createCounter( 'notifications.processors.slack.sent.count', @@ -193,7 +213,6 @@ export class SlackNotificationProcessor implements NotificationProcessor { }), ); - console.log('dispatching message'); await this.sendNotifications(outbound); return options; @@ -261,31 +280,6 @@ export class SlackNotificationProcessor implements NotificationProcessor { }; } - async getEntities( - entityRefs: readonly string[], - ): Promise<(Entity | undefined)[]> { - const { token } = await this.auth.getPluginRequestToken({ - onBehalfOf: await this.auth.getOwnServiceCredentials(), - targetPluginId: 'catalog', - }); - - const response = await this.catalog.getEntitiesByRefs( - { - entityRefs: entityRefs.slice(), - fields: [ - `kind`, - `spec.profile.email`, - `metadata.annotations.${ANNOTATION_SLACK_BOT_NOTIFY}`, - ], - }, - { - token, - }, - ); - - return response.items; - } - async replaceUserRefsWithSlackIds( text?: string, ): Promise { @@ -327,13 +321,8 @@ export class SlackNotificationProcessor implements NotificationProcessor { async getSlackNotificationTarget( entityRef: string, ): Promise { - const entityLoader = new DataLoader( - entityRefs => this.getEntities(entityRefs), - ); - const entity = await entityLoader.load(entityRef); - + const entity = await this.entityLoader.load(entityRef); if (!entity) { - console.log(`Entity not found: ${entityRef}`); throw new NotFoundError(`Entity not found: ${entityRef}`); } diff --git a/plugins/notifications-backend-module-slack/src/lib/util.ts b/plugins/notifications-backend-module-slack/src/lib/util.ts index 2d3a03bd2c..24f9f6a964 100644 --- a/plugins/notifications-backend-module-slack/src/lib/util.ts +++ b/plugins/notifications-backend-module-slack/src/lib/util.ts @@ -90,3 +90,42 @@ function getColor(severity: NotificationSeverity | undefined) { return '#00A699'; // Neutral color } } + +// Simple expiry map for the data loader, which only expects a map that implements set, get, and delete and clear +export class ExpiryMap extends Map { + #ttlMs: number; + #timestamps: Map = new Map(); + + constructor(ttlMs: number) { + super(); + this.#ttlMs = ttlMs; + } + + set(key: K, value: V) { + const result = super.set(key, value); + this.#timestamps.set(key, Date.now()); + return result; + } + + get(key: K) { + if (!this.has(key)) { + return undefined; + } + const timestamp = this.#timestamps.get(key)!; + if (Date.now() - timestamp > this.#ttlMs) { + this.delete(key); + return undefined; + } + return super.get(key); + } + + delete(key: K) { + this.#timestamps.delete(key); + return super.delete(key); + } + + clear() { + this.#timestamps.clear(); + return super.clear(); + } +} diff --git a/plugins/notifications-backend-module-slack/src/module.ts b/plugins/notifications-backend-module-slack/src/module.ts index cb096dabba..ff783ef104 100644 --- a/plugins/notifications-backend-module-slack/src/module.ts +++ b/plugins/notifications-backend-module-slack/src/module.ts @@ -17,9 +17,9 @@ import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; -import { CatalogClient } from '@backstage/catalog-client'; import { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node'; import { SlackNotificationProcessor } from './lib/SlackNotificationProcessor'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; /** * The Slack notification processor for use with the notifications plugin. @@ -35,21 +35,16 @@ export const notificationsModuleSlack = createBackendModule({ deps: { auth: coreServices.auth, config: coreServices.rootConfig, - discovery: coreServices.discovery, logger: coreServices.logger, + catalog: catalogServiceRef, notifications: notificationsProcessingExtensionPoint, }, - async init({ auth, config, discovery, logger, notifications }) { - const catalogClient = new CatalogClient({ - discoveryApi: discovery, - }); - + async init({ auth, config, logger, catalog, notifications }) { notifications.addProcessor( SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, - catalog: catalogClient, + catalog, }), ); }, diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index 34435c659f..f6150320c6 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,189 @@ # @backstage/plugin-notifications-backend +## 0.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-notifications-node@0.2.18-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-signals-node@0.1.23-next.0 + +## 0.5.8 + +### Patch Changes + +- 4401dfb: Allow defining default notification settings via configuration +- 9a5a73f: Fix `addTopic` migration when `user_settings` present +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-notifications-node@0.2.17 + - @backstage/plugin-signals-node@0.1.22 + +## 0.5.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-notifications-common@0.0.10-next.0 + - @backstage/plugin-signals-node@0.1.22-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-notifications-node@0.2.17-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.5.8-next.0 + +### Patch Changes + +- 9a5a73f: Fix `addTopic` migration when `user_settings` present +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-notifications-node@0.2.16 + - @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 + - @backstage/plugin-signals-node@0.1.21 + +## 0.5.7 + +### Patch Changes + +- 41d4d6e: Notifications are now automatically deleted after 1 year by default. + + There is a new scheduled task that runs every 24 hours to delete notifications older than 1 year. + This can be configured by setting the `notifications.retention` in the `app-config.yaml` file. + + ```yaml + notifications: + retention: 1y + ``` + + If the retention is set to false, notifications will not be automatically deleted. + +- 8a150bf: Internal changes to switch to the non-alpha `catalogServiceRef` +- 1fb5f06: Adds ability for user to turn on/off notifications for specific topics within an origin. +- ef9ab82: Notifications API will now return user as null always for broadcast notifications +- Updated dependencies + - @backstage/plugin-notifications-common@0.0.9 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-notifications-node@0.2.16 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-signals-node@0.1.21 + +## 0.5.7-next.2 + +### Patch Changes + +- 8a150bf: Internal changes to switch to the non-alpha `catalogServiceRef` +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.1 + - @backstage/plugin-signals-node@0.1.21-next.1 + +## 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 + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/plugin-notifications-node@0.2.15 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-signals-node@0.1.20 + +## 0.5.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.15-next.2 + - @backstage/plugin-signals-node@0.1.20-next.2 + ## 0.5.6-next.1 ### Patch Changes diff --git a/plugins/notifications-backend/config.d.ts b/plugins/notifications-backend/config.d.ts index 5dd795f8d3..2b6f874f72 100644 --- a/plugins/notifications-backend/config.d.ts +++ b/plugins/notifications-backend/config.d.ts @@ -28,5 +28,26 @@ export interface Config { * Throttle duration between notification sending, defaults to 50ms */ throttleInterval?: HumanDuration | string; + /** + * Default settings for user specific notification settings + */ + defaultSettings?: { + channels?: { + id: string; + origins?: { + id: string; + enabled: boolean; + topics?: { + id: string; + enabled: boolean; + }[]; + }[]; + }[]; + }; + /* + * Time to keep the notifications in the database, defaults to 365 days. + * Can be disabled by setting to false. + */ + retention?: HumanDuration | string | false; }; } diff --git a/plugins/notifications-backend/migrations/20250317_addTopic.js b/plugins/notifications-backend/migrations/20250317_addTopic.js new file mode 100644 index 0000000000..e17b8d0789 --- /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).nullable(); + table.dropUnique([], '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, + }) + .update({ settings_key_hash: hash }); + } + + await knex.schema.alterTable('user_settings', table => { + table.string('settings_key_hash', 64).notNullable().alter(); + table.unique(['settings_key_hash'], 'user_settings_unique_idx'); + }); +}; + +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 6929b362b9..112d671cc8 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-next.1", + "version": "0.5.9-next.0", "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..d8ae3faf57 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: [], }, ], }, @@ -221,6 +228,13 @@ describe.each(databases.eachSupportedId())( ]); }); + it('should return null value for user for broadcast notifications', async () => { + await storage.saveBroadcast({ ...testNotification1, read: new Date() }); + const notifications = await storage.getNotifications({ user }); + expect(notifications).toHaveLength(1); + expect(notifications[0].user).toBeNull(); + }); + it('should return read notifications for user', async () => { await storage.saveNotification(testNotification1); await storage.saveBroadcast(testNotification2); @@ -291,7 +305,7 @@ describe.each(databases.eachSupportedId())( user: otherUser, }); expect(otherUserNotifications.map(idOnly)).toEqual([id0, id2]); - expect(otherUserNotifications[1].user).toBe(otherUser); + expect(otherUserNotifications[1].user).toBeNull(); }); it('should allow searching for notifications', async () => { @@ -628,7 +642,7 @@ describe.each(databases.eachSupportedId())( expect(notification?.user).toBeNull(); await storage.markRead({ ids: [id1], user }); notification = await storage.getNotification({ id: id1, user }); - expect(notification?.user).toBe(user); + expect(notification?.user).toBeNull(); const otherNotification = await storage.getNotification({ id: id1, @@ -791,5 +805,24 @@ describe.each(databases.eachSupportedId())( }); }); }); + + describe('clearNotifications', () => { + it('should clear notifications older than specified days', async () => { + const oldDate = new Date(); + oldDate.setDate(oldDate.getDate() - 10); // 10 days ago + await storage.saveNotification({ + ...testNotification1, + created: oldDate, + }); + await storage.saveNotification(testNotification2); + + const result = await storage.clearNotifications({ + maxAge: { days: 5 }, + }); // Clear notifications older than 5 days + expect(result.deletedCount).toBe(1); // Only the first notification should be cleared + const remainingNotifications = await storage.getNotifications({ user }); + expect(remainingNotifications.map(idOnly)).toEqual([id2]); + }); + }); }, ); diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts index b258f38e7a..94131ca257 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts @@ -30,6 +30,8 @@ import { NotificationSeverity, } from '@backstage/plugin-notifications-common'; import { Knex } from 'knex'; +import crypto from 'crypto'; +import { durationToMilliseconds, HumanDuration } from '@backstage/types'; const migrationsDir = resolvePackagePath( '@backstage/plugin-notifications-backend', @@ -105,6 +107,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; @@ -138,7 +150,7 @@ export class DatabaseNotificationsStore implements NotificationsStore { private mapToNotifications = (rows: any[]): Notification[] => { return rows.map(row => ({ id: row.id, - user: row.user, + user: row.type === 'broadcast' ? null : row.user, created: new Date(row.created), saved: row.saved, read: row.read, @@ -169,10 +181,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: [] }, @@ -220,7 +253,7 @@ export class DatabaseNotificationsStore implements NotificationsStore { join.andOnVal('user', '=', user); } }) - .select(NOTIFICATION_COLUMNS); + .select([...NOTIFICATION_COLUMNS, this.db.raw("'broadcast' as type")]); }; private getNotificationsBaseQuery = ( @@ -229,7 +262,7 @@ export class DatabaseNotificationsStore implements NotificationsStore { const { user, orderField } = options; const subQuery = this.db('notification') - .select(NOTIFICATION_COLUMNS) + .select([...NOTIFICATION_COLUMNS, this.db.raw("'entity' as type")]) .unionAll([this.getBroadcastUnion(user)]) .as('notifications'); @@ -299,7 +332,10 @@ export class DatabaseNotificationsStore implements NotificationsStore { async getNotifications(options: NotificationGetOptions) { const notificationQuery = this.getNotificationsBaseQuery(options); - const notifications = await notificationQuery.select(NOTIFICATION_COLUMNS); + const notifications = await notificationQuery.select([ + ...NOTIFICATION_COLUMNS, + 'type', + ]); return this.mapToNotifications(notifications); } @@ -430,7 +466,7 @@ export class DatabaseNotificationsStore implements NotificationsStore { .select('*') .from( this.db('notification') - .select(NOTIFICATION_COLUMNS) + .select([...NOTIFICATION_COLUMNS, this.db.raw("'entity' as type")]) .unionAll([this.getBroadcastUnion(options.user)]) .as('notifications'), ) @@ -518,10 +554,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 +586,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 +599,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, + }); + }); }); }); @@ -575,4 +657,24 @@ export class DatabaseNotificationsStore implements NotificationsStore { .distinct(['topic']); return { topics: topics.map(row => row.topic) }; } + + async clearNotifications(options: { + maxAge: HumanDuration; + }): Promise<{ deletedCount: number }> { + const ms = durationToMilliseconds(options.maxAge); + const now = new Date(new Date().getTime() - ms); + const notificationsCount = await this.db('notification') + .where(builder => { + builder.where('created', '<=', now).whereNull('updated'); + }) + .orWhere('updated', '<=', now) + .delete(); + const broadcastsCount = await this.db('broadcast') + .where(builder => { + builder.where('created', '<=', now).whereNull('updated'); + }) + .orWhere('updated', '<=', now) + .delete(); + return { deletedCount: notificationsCount + broadcastsCount }; + } } diff --git a/plugins/notifications-backend/src/database/NotificationsStore.ts b/plugins/notifications-backend/src/database/NotificationsStore.ts index a1f4eb6999..1f25b28bbe 100644 --- a/plugins/notifications-backend/src/database/NotificationsStore.ts +++ b/plugins/notifications-backend/src/database/NotificationsStore.ts @@ -20,6 +20,7 @@ import { NotificationSeverity, NotificationStatus, } from '@backstage/plugin-notifications-common'; +import { HumanDuration } from '@backstage/types'; /** @internal */ export type EntityOrder = { @@ -99,6 +100,10 @@ export interface NotificationsStore { user: string; }): Promise<{ origins: string[] }>; + getUserNotificationTopics(options: { + user: string; + }): Promise<{ topics: { origin: string; topic: string }[] }>; + getNotificationSettings(options: { user: string; }): Promise; @@ -109,4 +114,8 @@ export interface NotificationsStore { }): Promise; getTopics(options: TopicGetOptions): Promise<{ topics: string[] }>; + + clearNotifications(options: { + maxAge: HumanDuration; + }): Promise<{ deletedCount: number }>; } diff --git a/plugins/notifications-backend/src/plugin.ts b/plugins/notifications-backend/src/plugin.ts index 84a200bae1..5f430318b5 100644 --- a/plugins/notifications-backend/src/plugin.ts +++ b/plugins/notifications-backend/src/plugin.ts @@ -25,7 +25,9 @@ import { notificationsProcessingExtensionPoint, NotificationsProcessingExtensionPoint, } from '@backstage/plugin-notifications-node'; -import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; +import { DatabaseNotificationsStore } from './database'; +import { NotificationCleaner } from './service/NotificationCleaner.ts'; class NotificationsProcessingExtensionPointImpl implements NotificationsProcessingExtensionPoint @@ -69,6 +71,7 @@ export const notificationsPlugin = createBackendPlugin({ signals: signalsServiceRef, config: coreServices.rootConfig, catalog: catalogServiceRef, + scheduler: coreServices.scheduler, }, async init({ auth, @@ -80,7 +83,10 @@ export const notificationsPlugin = createBackendPlugin({ signals, config, catalog, + scheduler, }) { + const store = await DatabaseNotificationsStore.create({ database }); + httpRouter.use( await createRouter({ auth, @@ -88,7 +94,7 @@ export const notificationsPlugin = createBackendPlugin({ userInfo, logger, config, - database, + store, catalog, signals, processors: processingExtensions.processors, @@ -98,6 +104,14 @@ export const notificationsPlugin = createBackendPlugin({ path: '/health', allow: 'unauthenticated', }); + + const cleaner = new NotificationCleaner( + config, + scheduler, + logger, + store, + ); + await cleaner.initTaskRunner(); }, }); }, diff --git a/plugins/notifications-backend/src/service/NotificationCleaner.test.ts b/plugins/notifications-backend/src/service/NotificationCleaner.test.ts new file mode 100644 index 0000000000..bfd3cc62b9 --- /dev/null +++ b/plugins/notifications-backend/src/service/NotificationCleaner.test.ts @@ -0,0 +1,118 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerService, SchedulerService } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { mockServices } from '@backstage/backend-test-utils'; +import { NotificationsStore } from '../database'; +import { NotificationCleaner } from './NotificationCleaner.ts'; + +describe('NotificationCleaner', () => { + let mockConfig: Config; + let mockScheduler: SchedulerService; + let mockLogger: LoggerService; + let mockDatabase: NotificationsStore; + + beforeEach(() => { + mockConfig = mockServices.rootConfig(); + mockScheduler = mockServices.scheduler.mock(); + mockLogger = mockServices.logger.mock(); + mockDatabase = { + clearNotifications: jest.fn(), + } as unknown as NotificationsStore; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('initNotificationCleaner', () => { + it('should initialize the notification cleaner with the correct schedule', async () => { + const mockTaskRunner = { + run: jest.fn(), + }; + mockScheduler.createScheduledTaskRunner = jest + .fn() + .mockReturnValue(mockTaskRunner); + + const cleaner = new NotificationCleaner( + mockConfig, + mockScheduler, + mockLogger, + mockDatabase, + ); + expect(cleaner).toBeInstanceOf(NotificationCleaner); + await cleaner.initTaskRunner(); + + expect(mockScheduler.createScheduledTaskRunner).toHaveBeenCalled(); + expect(mockTaskRunner.run).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'notification-cleaner', + fn: expect.any(Function), + }), + ); + }); + + it('should not create a task runner if retention is disabled', async () => { + mockConfig = mockServices.rootConfig({ + data: { notifications: { retention: false } }, + }); + const cleaner = new NotificationCleaner( + mockConfig, + mockScheduler, + mockLogger, + mockDatabase, + ); + await cleaner.initTaskRunner(); + + expect(mockScheduler.createScheduledTaskRunner).not.toHaveBeenCalled(); + expect(mockLogger.info).toHaveBeenCalledWith( + 'Notification retention is disabled, skipping notification cleaner task', + ); + }); + }); + + describe('clearNotifications', () => { + it('should clear notifications', async () => { + mockDatabase.clearNotifications = jest + .fn() + .mockResolvedValue({ deletedCount: 1 }); + const mockTaskRunner = { + run: jest.fn().mockImplementation(({ fn }) => fn()), + }; + mockScheduler.createScheduledTaskRunner = jest + .fn() + .mockReturnValue(mockTaskRunner); + + const cleaner = new NotificationCleaner( + mockConfig, + mockScheduler, + mockLogger, + mockDatabase, + ); + await cleaner.initTaskRunner(); + + expect(mockLogger.info).toHaveBeenCalledWith( + 'Starting notification cleaner task', + ); + expect(mockLogger.info).toHaveBeenCalledWith( + 'Notification cleaner task completed successfully, deleted 1 notifications', + ); + expect(mockDatabase.clearNotifications).toHaveBeenCalledWith({ + maxAge: { years: 1 }, + }); + }); + }); +}); diff --git a/plugins/notifications-backend/src/service/NotificationCleaner.ts b/plugins/notifications-backend/src/service/NotificationCleaner.ts new file mode 100644 index 0000000000..6733a843b5 --- /dev/null +++ b/plugins/notifications-backend/src/service/NotificationCleaner.ts @@ -0,0 +1,91 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskScheduleDefinition, +} from '@backstage/backend-plugin-api'; +import { Config, readDurationFromConfig } from '@backstage/config'; +import { NotificationsStore } from '../database'; +import { HumanDuration } from '@backstage/types'; +import { ForwardedError } from '@backstage/errors'; + +export class NotificationCleaner { + private readonly retention: HumanDuration = { years: 1 }; + private readonly enabled: boolean = true; + + constructor( + config: Config, + private readonly scheduler: SchedulerService, + private readonly logger: LoggerService, + private readonly database: NotificationsStore, + ) { + if (config.has('notifications.retention')) { + const retentionConfig = config.get('notifications.retention'); + if (typeof retentionConfig === 'boolean' && !retentionConfig) { + logger.info( + 'Notification retention is disabled, skipping notification cleaner task', + ); + this.enabled = false; + return; + } + this.retention = readDurationFromConfig(config, { + key: 'notifications.retention', + }); + } + } + + async initTaskRunner() { + if (!this.enabled) { + return; + } + + const schedule: SchedulerServiceTaskScheduleDefinition = { + frequency: { cron: '0 0 * * *' }, + timeout: { hours: 1 }, + initialDelay: { hours: 1 }, + scope: 'global', + }; + + const taskRunner = this.scheduler.createScheduledTaskRunner(schedule); + await taskRunner.run({ + id: 'notification-cleaner', + fn: async () => { + await this.clearNotifications( + this.logger, + this.database, + this.retention, + ); + }, + }); + } + + private async clearNotifications( + logger: LoggerService, + database: NotificationsStore, + retention: HumanDuration, + ) { + logger.info('Starting notification cleaner task'); + try { + const result = await database.clearNotifications({ maxAge: retention }); + logger.info( + `Notification cleaner task completed successfully, deleted ${result.deletedCount} notifications`, + ); + } catch (error) { + throw new ForwardedError('Notification cleaner task failed', error); + } + } +} 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..6c74fed41c 100644 --- a/plugins/notifications-backend/src/service/router.test.ts +++ b/plugins/notifications-backend/src/service/router.test.ts @@ -29,8 +29,10 @@ import { NotificationSendOptions } from '@backstage/plugin-notifications-node'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; import { DatabaseService } from '@backstage/backend-plugin-api'; import { v4 as uuid } from 'uuid'; +import { DatabaseNotificationsStore } from '../database'; const databases = TestDatabases.create(); +let store: DatabaseNotificationsStore; async function createDatabase( databaseId: TestDatabaseId, @@ -51,7 +53,24 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { const auth = mockServices.auth(); const config = mockServices.rootConfig({ - data: { app: { baseUrl: 'http://localhost' } }, + data: { + app: { baseUrl: 'http://localhost' }, + notifications: { + defaultSettings: { + channels: [ + { + id: 'Web', + origins: [ + { + id: 'external:test-service2', + enabled: false, + }, + ], + }, + ], + }, + }, + }, }); const catalog = catalogServiceMock({ @@ -83,6 +102,9 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { beforeAll(async () => { database = await createDatabase(databaseId); + store = await DatabaseNotificationsStore.create({ + database, + }); }); describe('POST /notifications', () => { @@ -93,7 +115,7 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { beforeAll(async () => { const router = await createRouter({ logger: mockServices.logger.mock(), - database, + store, signals: signalService, userInfo, config, @@ -307,9 +329,19 @@ 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(); + // Insert a notification with a origin + await client('notification').insert({ + id: uuid(), + user: 'user:default/mock', + origin: 'external:test-service', + title: 'Test notification', + created: new Date(), + severity: 'normal', + }); await client('user_settings').insert({ + settings_key_hash: 'hash', user: 'user:default/mock', channel: 'Web', origin: 'external:test-service', @@ -332,7 +364,98 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { const notifications = await client('notification') .where('user', 'user:default/mock') .select(); - expect(notifications).toHaveLength(0); + // This should not create a new notification since the origin is disabled + expect(notifications).toHaveLength(1); + }); + + it('should not send to user entity if topic is disabled in settings', async () => { + const client = await database.getClient(); + // Insert a notification with a topic + await client('notification').insert({ + id: uuid(), + user: 'user:default/mock', + origin: 'external:test-service', + topic: 'test-topic', + title: 'Test notification', + created: new Date(), + severity: 'normal', + }); + 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(); + // This should not create a new notification since the topic is disabled + expect(notifications).toHaveLength(1); + }); + + 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 () => { @@ -380,7 +503,7 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { beforeAll(async () => { const router = await createRouter({ logger: mockServices.logger.mock(), - database, + store, signals: signalService, userInfo, config, @@ -470,7 +593,7 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { beforeAll(async () => { const router = await createRouter({ logger: mockServices.logger.mock(), - database, + store, signals: signalService, userInfo, config, @@ -485,11 +608,33 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { jest.resetAllMocks(); const client = await database.getClient(); await client('user_settings').del(); + await client('notification').del(); + + await client('notification').insert({ + id: uuid(), + user: 'user:default/mock', + origin: 'external:test-service', + topic: 'test-topic', + title: 'Test notification', + created: new Date(), + severity: 'normal', + }); + + await client('notification').insert({ + id: uuid(), + user: 'user:default/mock', + origin: 'external:test-service2', + title: 'Test notification', + topic: 'test-topic2', + created: new Date(), + severity: 'normal', + }); }); - it('should return user settings', async () => { + it('should return origin settings correctly', 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 +647,76 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { channels: [ { id: 'Web', - origins: [{ enabled: false, id: 'external:test-service' }], + origins: expect.arrayContaining([ + { + enabled: false, + id: 'external:test-service', + topics: [{ enabled: false, id: 'test-topic' }], + }, + { + enabled: false, + id: 'external:test-service2', + topics: [{ enabled: false, id: 'test-topic2' }], + }, + ]), + }, + ], + }); + }); + + it('should return topic settings correctly', 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 request(app).get('/settings'); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + channels: [ + { + id: 'Web', + origins: expect.arrayContaining([ + { + enabled: true, + id: 'external:test-service', + topics: [{ enabled: false, id: 'test-topic' }], + }, + { + enabled: false, + id: 'external:test-service2', + topics: [{ enabled: false, id: 'test-topic2' }], + }, + ]), + }, + ], + }); + }); + + it('should return default user settings from config', async () => { + const response = await request(app).get('/settings'); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + channels: [ + { + id: 'Web', + origins: expect.arrayContaining([ + { + enabled: true, + id: 'external:test-service', + topics: [{ enabled: true, id: 'test-topic' }], + }, + { + enabled: false, + id: 'external:test-service2', + topics: [{ enabled: false, id: 'test-topic2' }], + }, + ]), }, ], }); @@ -517,7 +731,7 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { beforeAll(async () => { const router = await createRouter({ logger: mockServices.logger.mock(), - database, + store, signals: signalService, userInfo, config, diff --git a/plugins/notifications-backend/src/service/router.ts b/plugins/notifications-backend/src/service/router.ts index 7f0279b437..5805092096 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -17,13 +17,13 @@ import express, { Request, Response } from 'express'; import Router from 'express-promise-router'; import { - DatabaseNotificationsStore, normalizeSeverity, NotificationGetOptions, + NotificationsStore, 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, @@ -31,13 +31,13 @@ import { import { InputError, NotFoundError } from '@backstage/errors'; import { AuthService, - DatabaseService, HttpAuthService, LoggerService, UserInfoService, } from '@backstage/backend-plugin-api'; import { SignalsService } from '@backstage/plugin-signals-node'; import { + ChannelSetting, isNotificationsEnabledFor, NewNotificationSignal, Notification, @@ -45,6 +45,7 @@ import { NotificationSettings, notificationSeverities, NotificationStatus, + OriginSetting, } from '@backstage/plugin-notifications-common'; import { parseEntityOrderFieldParams } from './parseEntityOrderFieldParams'; import { getUsersForEntityRef } from './getUsersForEntityRef'; @@ -56,12 +57,12 @@ import pThrottle from 'p-throttle'; export interface RouterOptions { logger: LoggerService; config: Config; - database: DatabaseService; + store: NotificationsStore; auth: AuthService; httpAuth: HttpAuthService; userInfo: UserInfoService; signals?: SignalsService; - catalog: CatalogApi; + catalog: CatalogService; processors?: NotificationProcessor[]; } @@ -72,7 +73,7 @@ export async function createRouter( const { config, logger, - database, + store, auth, httpAuth, userInfo, @@ -82,7 +83,6 @@ export async function createRouter( } = options; const WEB_NOTIFICATION_CHANNEL = 'Web'; - const store = await DatabaseNotificationsStore.create({ database }); const frontendBaseUrl = config.getString('app.baseUrl'); const concurrencyLimit = config.getOptionalNumber('notifications.concurrencyLimit') ?? 10; @@ -97,6 +97,8 @@ export async function createRouter( limit: concurrencyLimit, interval: throttleInterval, }); + const defaultNotificationSettings: NotificationSettings | undefined = + config.getOptional('notifications.defaultSettings'); const getUser = async (req: Request) => { const credentials = await httpAuth.credentials(req, { allow: ['user'] }); @@ -108,36 +110,115 @@ export async function createRouter( return [WEB_NOTIFICATION_CHANNEL, ...processors.map(p => p.getName())]; }; - const getNotificationSettings = async (user: string) => { + const getTopicSettings = ( + topic: any, + existingOrigin: OriginSetting | undefined, + defaultOriginSettings: OriginSetting | undefined, + defaultEnabled: boolean, + ) => { + const existingTopic = existingOrigin?.topics?.find( + t => t.id.toLowerCase() === topic.topic.toLowerCase(), + ); + const defaultTopicSettings = defaultOriginSettings?.topics?.find( + t => t.id.toLowerCase() === topic.topic.toLowerCase(), + ); + + return { + id: topic.topic, + enabled: existingTopic + ? existingTopic.enabled + : defaultTopicSettings?.enabled ?? defaultEnabled, + }; + }; + + const getOriginSettings = ( + originId: string, + existingChannel: ChannelSetting | undefined, + defaultChannelSettings: ChannelSetting | undefined, + topics: { origin: string; topic: string }[], + ) => { + const existingOrigin = existingChannel?.origins?.find( + o => o.id.toLowerCase() === originId.toLowerCase(), + ); + + const defaultOriginSettings = defaultChannelSettings?.origins?.find( + c => c.id.toLowerCase() === originId.toLowerCase(), + ); + + const defaultEnabled = existingOrigin + ? existingOrigin.enabled + : defaultOriginSettings?.enabled ?? true; + + return { + id: originId, + enabled: defaultEnabled, + topics: topics + .filter(t => t.origin === originId) + .map(t => + getTopicSettings( + t, + existingOrigin, + defaultOriginSettings, + defaultEnabled, + ), + ), + }; + }; + + const getChannelSettings = ( + channelId: string, + settings: NotificationSettings, + origins: string[], + topics: { origin: string; topic: string }[], + ) => { + const existingChannel = settings.channels.find( + c => c.id.toLowerCase() === channelId.toLowerCase(), + ); + const defaultChannelSettings = defaultNotificationSettings?.channels?.find( + c => c.id.toLowerCase() === channelId.toLowerCase(), + ); + + return { + id: channelId, + origins: origins.map(originId => + getOriginSettings( + originId, + existingChannel, + defaultChannelSettings, + topics, + ), + ), + }; + }; + + const getNotificationSettings = async ( + user: string, + ): Promise => { 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 +235,7 @@ export async function createRouter( user, origin, channel: processor.getName(), + topic: payload.topic ?? null, }); if (!enabled) { continue; @@ -508,6 +590,7 @@ export async function createRouter( user, channel: WEB_NOTIFICATION_CHANNEL, origin: userNotification.origin, + topic: userNotification.payload.topic ?? null, }); let ret = notification; @@ -622,7 +705,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-backend/src/tests/migrations.test.ts b/plugins/notifications-backend/src/tests/migrations.test.ts new file mode 100644 index 0000000000..aa3ba7ae9a --- /dev/null +++ b/plugins/notifications-backend/src/tests/migrations.test.ts @@ -0,0 +1,99 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Knex } from 'knex'; +import { TestDatabases } from '@backstage/backend-test-utils'; +import fs from 'fs'; + +const migrationsDir = `${__dirname}/../../migrations`; +const migrationsFiles = fs.readdirSync(migrationsDir).sort(); + +async function migrateUpOnce(knex: Knex): Promise { + await knex.migrate.up({ directory: migrationsDir }); +} + +async function migrateDownOnce(knex: Knex): Promise { + await knex.migrate.down({ directory: migrationsDir }); +} + +async function migrateUntilBefore(knex: Knex, target: string): Promise { + const index = migrationsFiles.indexOf(target); + if (index === -1) { + throw new Error(`Migration ${target} not found`); + } + for (let i = 0; i < index; i++) { + await migrateUpOnce(knex); + } +} + +jest.setTimeout(60_000); + +describe('migrations', () => { + const databases = TestDatabases.create(); + + it.each(databases.eachSupportedId())( + '20221109192547_search_add_original_value_column.js, %p', + async databaseId => { + const knex = await databases.init(databaseId); + + await migrateUntilBefore(knex, '20250317_addTopic.js'); + + await knex + .insert({ + user: 'user1', + channel: 'channel1', + origin: 'origin1', + enabled: true, + }) + .into('user_settings'); + + await migrateUpOnce(knex); + + let rows = await knex('user_settings'); + let normalized = rows.map(r => ({ ...r, enabled: !!r.enabled })); + + expect(normalized).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + user: 'user1', + channel: 'channel1', + origin: 'origin1', + enabled: true, + settings_key_hash: + '73f97aff883b8b08a7f4e366234ef4f86827702b0016574ac4c1bf313c703d15', + topic: null, + }), + ]), + ); + + await migrateDownOnce(knex); + + rows = await knex('user_settings'); + normalized = rows.map(r => ({ ...r, enabled: !!r.enabled })); + + expect(normalized).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + user: 'user1', + channel: 'channel1', + origin: 'origin1', + enabled: true, + }), + ]), + ); + }, + ); +}); diff --git a/plugins/notifications-common/CHANGELOG.md b/plugins/notifications-common/CHANGELOG.md index 3fd44c9e93..cd8ad5b094 100644 --- a/plugins/notifications-common/CHANGELOG.md +++ b/plugins/notifications-common/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-notifications-common +## 0.0.10 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + +## 0.0.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + +## 0.0.9 + +### 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.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..66473d0237 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.10", "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 3b0c21e7d7..bd37c413d2 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,90 @@ # @backstage/plugin-notifications-node +## 0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-signals-node@0.1.23-next.0 + +## 0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-signals-node@0.1.22 + +## 0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-notifications-common@0.0.10-next.0 + - @backstage/plugin-signals-node@0.1.22-next.0 + +## 0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-notifications-common@0.0.9 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-signals-node@0.1.21 + +## 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 + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/catalog-client@1.10.0 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-signals-node@0.1.20 + +## 0.2.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-signals-node@0.1.20-next.2 + ## 0.2.15-next.1 ### Patch Changes diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index ca66d819b9..13a0889913 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-next.1", + "version": "0.2.18-next.0", "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 f171b8a148..006f015f10 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,175 @@ # @backstage/plugin-notifications +## 0.5.8-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.8-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-signals-react@0.0.15 + +## 0.5.8-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-signals-react@0.0.15 + +## 0.5.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.5.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-signals-react@0.0.15 + +## 0.5.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-notifications-common@0.0.10-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + - @backstage/plugin-signals-react@0.0.15-next.0 + +## 0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.9 + - @backstage/plugin-signals-react@0.0.14 + +## 0.5.6 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- b00c160: Remove React import form notification and scaffolder plugin +- 1fb5f06: Adds ability for user to turn on/off notifications for specific topics within an origin. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-notifications-common@0.0.9 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.14 + +## 0.5.6-next.2 + +### Patch Changes + +- b00c160: Remove React import form notification and scaffolder plugin +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-signals-react@0.0.13 + +## 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 + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/core-components@0.17.2 + - @backstage/core-compat-api@0.4.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-signals-react@0.0.13 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + +## 0.5.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-signals-react@0.0.13-next.0 + ## 0.5.5-next.2 ### Patch Changes diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index 0a9d383056..be8efc386d 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.5.5-next.2", + "version": "0.5.8-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "notifications", diff --git a/plugins/notifications/report-alpha.api.md b/plugins/notifications/report-alpha.api.md index 734dd0a201..f98e01d937 100644 --- a/plugins/notifications/report-alpha.api.md +++ b/plugins/notifications/report-alpha.api.md @@ -5,7 +5,9 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; @@ -23,15 +25,15 @@ const _default: FrontendPlugin< name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; }>; 'page:notifications': ExtensionDefinition<{ kind: 'page'; @@ -43,9 +45,9 @@ const _default: FrontendPlugin< path?: string | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< RouteRef, 'core.routing.ref', { @@ -54,7 +56,8 @@ const _default: FrontendPlugin< >; inputs: {}; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise; routeRef?: RouteRef; }; 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..6d32150c35 100644 --- a/plugins/notifications/src/alpha.tsx +++ b/plugins/notifications/src/alpha.tsx @@ -17,7 +17,6 @@ import { ApiBlueprint, PageBlueprint, - createApiFactory, createFrontendPlugin, discoveryApiRef, fetchApiRef, @@ -31,7 +30,7 @@ import { NotificationsClient, notificationsApiRef } from './api'; const page = PageBlueprint.make({ params: { - defaultPath: '/notifications', + path: '/notifications', routeRef: convertLegacyRouteRef(rootRouteRef), loader: () => import('./components/NotificationsPage').then(m => ( @@ -41,19 +40,19 @@ const page = PageBlueprint.make({ }); const api = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: notificationsApiRef, deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef }, factory: ({ discoveryApi, fetchApi }) => new NotificationsClient({ discoveryApi, fetchApi }), }), - }, }); /** @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..f4f6c61493 --- /dev/null +++ b/plugins/notifications/src/components/UserNotificationSettingsCard/OriginRow.tsx @@ -0,0 +1,90 @@ +/* + * 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 { + 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..ed2ca8fd05 --- /dev/null +++ b/plugins/notifications/src/components/UserNotificationSettingsCard/TopicRow.tsx @@ -0,0 +1,79 @@ +/* + * 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 { + 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 766eb1b7ca..45d776604c 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,118 @@ # @backstage/plugin-org-react +## 0.1.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + +## 0.1.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + +## 0.1.40 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + +## 0.1.40-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## 0.1.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.8 + +## 0.1.39 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + +## 0.1.39-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.7 + +## 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 + +- Updated dependencies + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + +## 0.1.38-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + ## 0.1.38-next.2 ### Patch Changes diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index 8659c22098..6c8fa528a4 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-next.2", + "version": "0.1.41-next.1", "backstage": { "role": "web-library", "pluginId": "org", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 5dd3fe3463..ecc3976693 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,192 @@ # @backstage/plugin-org +## 0.6.42-next.3 + +### Patch Changes + +- 8b1bf6e: Updated README instructions for the new frontend system +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.2 + - @backstage/core-compat-api@0.5.0-next.3 + - @backstage/core-components@0.17.5-next.2 + +## 0.6.42-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.6.42-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.6.42-next.0 + +### Patch Changes + +- 43cbb10: Added OwnershipCard, ComponentsGrid and UserProfileCard components to the `overridableComponents`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.6.41 + +### Patch Changes + +- 3ab9b96: Updated card extensions for the new frontend system to use the new entity predicates, and switch the card type to `info` for the user and group profile cards. +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-catalog-common@1.1.5 + +## 0.6.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 0.6.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.6.40 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- 4fe364f: Org plugin support i18n +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.6.40-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-catalog-common@1.1.4 + +## 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 + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 02e8af1: Enhance user profile card configuration: + + - Added a new optional `maxRelations` numerical configuration that controls over how many user groups are shown directly on the profile card: + - If the setting is omitted, all relations will be shown. + - If `maxRelations` is set to `0`, the list of user groups is not displayed. + - If `maxRelations` is set to a positive number, up to that many groups are displayed. + - If the user belongs to more groups than the specified limit, a clickable link appears that opens a dialog showing all associated user groups. + - A complementary boolean configuration, `hideIcons`, was added to optionally hide the visual icons associated with each group in the displayed list. + - Usage example: + ```yaml + # Example in app-config.yaml + app: + extensions: + - entity-card:org/user-profile: + config: + maxRelations: 5 # (optional) Show up to 5 groups on the card + hideIcons: true # (optional) Hide the group icons + ``` + +- 08ba448: display entity-ref in GroupProfileCard so groups can easily determine their Group ID +- 18e84c9: Fixed missing spec.profile field on MyGroupsSidebarItem.tsx so the group spec.profile.displayName is shown on the sidebar" +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.6.39-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + ## 0.6.39-next.2 ### Patch Changes diff --git a/plugins/org/README-alpha.md b/plugins/org/README-alpha.md index 30c4bce1c2..35258800f2 100644 --- a/plugins/org/README-alpha.md +++ b/plugins/org/README-alpha.md @@ -48,9 +48,8 @@ And below is an example of how a user page looks with the user profile and owner ```yaml # app-config.yaml app: - experimental: - # Auto discovering all plugins extensions - packages: all + # Auto discovering all plugins extensions + packages: all extensions: # Enabling the org plugin cards - entity-card:org/group-profile diff --git a/plugins/org/package.json b/plugins/org/package.json index 5337c5ec0f..d83e3a0c15 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.6.39-next.2", + "version": "0.6.42-next.3", "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 e5d574ed5f..35e3ec9661 100644 --- a/plugins/org/report-alpha.api.md +++ b/plugins/org/report-alpha.api.md @@ -3,14 +3,16 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { EntityCardType } from '@backstage/plugin-catalog-react/alpha'; import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +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< @@ -31,22 +33,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -72,22 +74,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -113,22 +115,22 @@ const _default: FrontendPlugin< type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { @@ -143,40 +145,54 @@ const _default: FrontendPlugin< }; }>; 'entity-card:org/user-profile': ExtensionDefinition<{ - kind: 'entity-card'; - name: 'user-profile'; config: { + maxRelations: number | undefined; + hideIcons: boolean; + } & { filter: EntityPredicate | undefined; type: 'content' | 'summary' | 'info' | undefined; }; configInput: { + hideIcons?: boolean | undefined; + maxRelations?: number | undefined; + } & { filter?: EntityPredicate | undefined; type?: 'content' | 'summary' | 'info' | undefined; }; output: - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< + | ExtensionDataRef + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< EntityCardType, 'catalog.entity-card-type', { optional: true; } >; - inputs: {}; + inputs: { + [x: string]: ExtensionInput< + ExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }; + kind: 'entity-card'; + name: 'user-profile'; params: { loader: () => Promise; filter?: string | EntityPredicate | ((entity: Entity) => boolean); @@ -187,5 +203,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/report.api.md b/plugins/org/report.api.md index d3e82478f7..7d58d96263 100644 --- a/plugins/org/report.api.md +++ b/plugins/org/report.api.md @@ -9,6 +9,13 @@ import { IconComponent } from '@backstage/core-plugin-api'; import { InfoCardVariants } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react/jsx-runtime'; +// @public (undocumented) +export type ComponentsGridClassKey = + | 'card' + | 'bold' + | 'smallFont' + | 'entityTypeBox'; + // @public (undocumented) export const EntityGroupProfileCard: (props: { variant?: InfoCardVariants; @@ -42,6 +49,8 @@ export type EntityRelationAggregation = 'direct' | 'aggregated'; export const EntityUserProfileCard: (props: { variant?: InfoCardVariants; showLinks?: boolean; + maxRelations?: number; + hideIcons?: boolean; }) => JSX_2.Element; // @public (undocumented) @@ -94,9 +103,26 @@ export const OwnershipCard: (props: { entityLimit?: number; }) => JSX_2.Element; +// @public (undocumented) +export type OwnershipCardClassKey = + | 'card' + | 'cardContent' + | 'list' + | 'listItemText' + | 'listItemSecondaryAction' + | 'grid'; + // @public (undocumented) export const UserProfileCard: (props: { variant?: InfoCardVariants; showLinks?: boolean; + maxRelations?: number; + hideIcons?: boolean; }) => JSX_2.Element; + +// @public (undocumented) +export type UserProfileCardClassKey = + | 'closeButton' + | 'moreButton' + | 'dialogPaper'; ``` diff --git a/plugins/org/src/alpha.tsx b/plugins/org/src/alpha.tsx index beb4f573af..92ae7d8801 100644 --- a/plugins/org/src/alpha.tsx +++ b/plugins/org/src/alpha.tsx @@ -26,7 +26,8 @@ import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; const EntityGroupProfileCard = EntityCardBlueprint.make({ name: 'group-profile', params: { - filter: 'kind:group', + type: 'info', + filter: { kind: 'group' }, loader: async () => import('./components/Cards/Group/GroupProfile/GroupProfileCard').then(m => compatWrapper(), @@ -38,7 +39,7 @@ const EntityGroupProfileCard = EntityCardBlueprint.make({ const EntityMembersListCard = EntityCardBlueprint.make({ name: 'members-list', params: { - filter: 'kind:group', + filter: { kind: 'group' }, loader: async () => import('./components/Cards/Group/MembersList/MembersListCard').then(m => compatWrapper(), @@ -50,7 +51,7 @@ const EntityMembersListCard = EntityCardBlueprint.make({ const EntityOwnershipCard = EntityCardBlueprint.make({ name: 'ownership', params: { - filter: 'kind:group,user', + filter: { kind: { $in: ['group', 'user'] } }, loader: async () => import('./components/Cards/OwnershipCard/OwnershipCard').then(m => compatWrapper(), @@ -59,20 +60,36 @@ const EntityOwnershipCard = EntityCardBlueprint.make({ }); /** @alpha */ -const EntityUserProfileCard = EntityCardBlueprint.make({ +const EntityUserProfileCard = EntityCardBlueprint.makeWithOverrides({ name: 'user-profile', - params: { - filter: 'kind:user', - loader: async () => - import('./components/Cards/User/UserProfileCard/UserProfileCard').then( - m => compatWrapper(), - ), + config: { + schema: { + maxRelations: z => z.number().optional(), + hideIcons: z => z.boolean().default(false), + }, + }, + factory(originalFactory, { config }) { + return originalFactory({ + type: 'info', + filter: { kind: 'user' }, + loader: async () => + import('./components/Cards/User/UserProfileCard/UserProfileCard').then( + m => + compatWrapper( + , + ), + ), + }); }, }); /** @alpha */ export default createFrontendPlugin({ pluginId: 'org', + info: { packageJson: () => import('../package.json') }, extensions: [ EntityGroupProfileCard, EntityMembersListCard, @@ -83,3 +100,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/ComponentsGrid.tsx b/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx index d6f2c2c360..0c177f31a8 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx @@ -31,32 +31,41 @@ import { catalogIndexRouteRef } from '../../../routes'; import { useGetEntities } from './useGetEntities'; import { EntityRelationAggregation } from '../types'; -const useStyles = makeStyles(theme => - createStyles({ - card: { - border: `1px solid ${theme.palette.divider}`, - boxShadow: theme.shadows[2], - borderRadius: '4px', - padding: theme.spacing(2), - transition: `${theme.transitions.duration.standard}ms`, - '&:hover': { - boxShadow: theme.shadows[4], +/** @public */ +export type ComponentsGridClassKey = + | 'card' + | 'bold' + | 'smallFont' + | 'entityTypeBox'; + +const useStyles = makeStyles( + theme => + createStyles({ + card: { + border: `1px solid ${theme.palette.divider}`, + boxShadow: theme.shadows[2], + borderRadius: '4px', + padding: theme.spacing(2), + transition: `${theme.transitions.duration.standard}ms`, + '&:hover': { + boxShadow: theme.shadows[4], + }, + height: '100%', }, - height: '100%', - }, - bold: { - fontWeight: theme.typography.fontWeightBold, - }, - smallFont: { - fontSize: theme.typography.body2.fontSize, - }, - entityTypeBox: { - background: (props: { type: string }) => - theme.getPageTheme({ themeId: props.type }).backgroundImage, - color: (props: { type: string }) => - theme.getPageTheme({ themeId: props.type }).fontColor, - }, - }), + bold: { + fontWeight: theme.typography.fontWeightBold, + }, + smallFont: { + fontSize: theme.typography.body2.fontSize, + }, + entityTypeBox: { + background: (props: { type: string }) => + theme.getPageTheme({ themeId: props.type }).backgroundImage, + color: (props: { type: string }) => + theme.getPageTheme({ themeId: props.type }).fontColor, + }, + }), + { name: 'PluginOrgComponentsGrid' }, ); const EntityCountTile = ({ diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index f5b01e918a..6ea0f0cbc9 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -14,10 +14,6 @@ * limitations under the License. */ -import { - GetEntitiesRequest, - GetEntitiesResponse, -} from '@backstage/catalog-client'; import { Entity, GroupEntity, UserEntity } from '@backstage/catalog-model'; import { catalogApiRef, EntityProvider } from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; @@ -104,18 +100,6 @@ const items = [ }, ] as Entity[]; -const getEntitiesMock = ( - request?: GetEntitiesRequest, -): Promise => { - const filterKinds = - Array.isArray(request?.filter) && Array.isArray(request?.filter[0].kind) - ? request?.filter[0].kind ?? [] - : []; // we expect the request to be like { filter: [{ kind: ['API','System'], 'relations.ownedBy': [group:default/my-team], .... }]. If changed in OwnerShipCard, let's change in also here - return Promise.resolve({ - items: items.filter(item => filterKinds.find(k => k === item.kind)), - } as GetEntitiesResponse); -}; - describe('OwnershipCard', () => { const groupEntity: GroupEntity = { apiVersion: 'backstage.io/v1alpha1', @@ -157,7 +141,8 @@ describe('OwnershipCard', () => { }; it('displays entity counts', async () => { - const catalogApi = catalogApiMock.mock({ getEntities: getEntitiesMock }); + const catalogApi = catalogApiMock({ entities: items }); + const mockedGetEntities = jest.spyOn(catalogApi, 'getEntities'); const { getByText } = await renderInTestApp( @@ -172,7 +157,7 @@ describe('OwnershipCard', () => { }, ); - expect(catalogApi.getEntities).toHaveBeenCalledWith({ + expect(mockedGetEntities).toHaveBeenCalledWith({ filter: [ { kind: ['Component', 'API', 'System'], @@ -207,7 +192,7 @@ describe('OwnershipCard', () => { }); it('applies CustomFilterDefinition', async () => { - const catalogApi = catalogApiMock.mock({ getEntities: getEntitiesMock }); + const catalogApi = catalogApiMock({ entities: items }); const { getByText } = await renderInTestApp( @@ -240,7 +225,7 @@ describe('OwnershipCard', () => { }); it('links to the catalog with the group filter', async () => { - const catalogApi = catalogApiMock.mock({ getEntities: getEntitiesMock }); + const catalogApi = catalogApiMock({ entities: items }); const { getByText } = await renderInTestApp( @@ -263,7 +248,7 @@ describe('OwnershipCard', () => { }); it('links to the catalog with the user and groups filters from an user profile', async () => { - const catalogApi = catalogApiMock.mock({ getEntities: getEntitiesMock }); + const catalogApi = catalogApiMock({ entities: items }); const { getByText } = await renderInTestApp( @@ -288,7 +273,7 @@ describe('OwnershipCard', () => { describe('OwnershipCard relations', () => { it('shows relations toggle', async () => { - const catalogApi = catalogApiMock.mock({ getEntities: getEntitiesMock }); + const catalogApi = catalogApiMock({ entities: items }); const { getByTitle } = await renderInTestApp( @@ -307,7 +292,7 @@ describe('OwnershipCard', () => { }); it('hides relations toggle', async () => { - const catalogApi = catalogApiMock.mock({ getEntities: getEntitiesMock }); + const catalogApi = catalogApiMock({ entities: items }); const rendered = await renderInTestApp( @@ -326,7 +311,7 @@ describe('OwnershipCard', () => { }); it('overrides relation type', async () => { - const catalogApi = catalogApiMock.mock({ getEntities: getEntitiesMock }); + const catalogApi = catalogApiMock({ entities: items }); const { getByTitle } = await renderInTestApp( @@ -345,7 +330,7 @@ describe('OwnershipCard', () => { }); it('defaults to aggregated for User entity kind', async () => { - const catalogApi = catalogApiMock.mock({ getEntities: getEntitiesMock }); + const catalogApi = catalogApiMock({ entities: items }); const { getByLabelText } = await renderInTestApp( @@ -364,7 +349,7 @@ describe('OwnershipCard', () => { }); it('defaults to direct for all entity kinds except User', async () => { - const catalogApi = catalogApiMock.mock({ getEntities: getEntitiesMock }); + const catalogApi = catalogApiMock({ entities: items }); const { getByLabelText } = await renderInTestApp( @@ -383,7 +368,7 @@ describe('OwnershipCard', () => { }); it('defaults to provided relationsType', async () => { - const catalogApi = catalogApiMock.mock({ getEntities: getEntitiesMock }); + const catalogApi = catalogApiMock({ entities: items }); const { getByLabelText } = await renderInTestApp( diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index d179573b09..cf34800bf4 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -22,45 +22,62 @@ import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; import ListItemText from '@material-ui/core/ListItemText'; import Switch from '@material-ui/core/Switch'; import Tooltip from '@material-ui/core/Tooltip'; -import { makeStyles } from '@material-ui/core/styles'; +import { createStyles, 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: { - maxHeight: '100%', +/** @public */ +export type OwnershipCardClassKey = + | 'card' + | 'cardContent' + | 'list' + | 'listItemText' + | 'listItemSecondaryAction' + | 'grid'; + +const useStyles = makeStyles( + theme => + createStyles({ + card: { + maxHeight: '100%', + }, + cardContent: { + display: 'flex', + flexDirection: 'column', + overflow: 'hidden', + }, + list: { + [theme.breakpoints.down('xs')]: { + padding: `0 0 12px`, + }, + }, + listItemText: { + [theme.breakpoints.down('xs')]: { + paddingRight: 0, + paddingLeft: 0, + }, + }, + listItemSecondaryAction: { + [theme.breakpoints.down('xs')]: { + width: '100%', + top: 'auto', + right: 'auto', + position: 'relative', + transform: 'unset', + }, + }, + grid: { + overflowY: 'auto', + marginTop: 0, + }, + }), + { + name: 'PluginOrgOwnershipCard', }, - cardContent: { - display: 'flex', - flexDirection: 'column', - overflow: 'hidden', - }, - list: { - [theme.breakpoints.down('xs')]: { - padding: `0 0 12px`, - }, - }, - listItemText: { - [theme.breakpoints.down('xs')]: { - paddingRight: 0, - paddingLeft: 0, - }, - }, - listItemSecondaryAction: { - [theme.breakpoints.down('xs')]: { - width: '100%', - top: 'auto', - right: 'auto', - position: 'relative', - transform: 'unset', - }, - }, - grid: { - overflowY: 'auto', - marginTop: 0, - }, -})); +); /** @public */ export const OwnershipCard = (props: { @@ -83,6 +100,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 +116,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/OwnershipCard/index.ts b/plugins/org/src/components/Cards/OwnershipCard/index.ts index dc0cef0226..df92d6214e 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/index.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export * from './OwnershipCard'; +export type { ComponentsGridClassKey } from './ComponentsGrid'; diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx index d8537e217e..b5932dfb8e 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx @@ -22,6 +22,7 @@ import { import { renderInTestApp } from '@backstage/test-utils'; import { UserProfileCard } from './UserProfileCard'; import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; describe('UserSummary Test', () => { const userEntity: UserEntity = { @@ -70,6 +71,100 @@ describe('UserSummary Test', () => { ); expect(screen.getByText('Super awesome human')).toBeInTheDocument(); }); + + describe('displayed user relations limit', () => { + const entity = { + ...userEntity, + relations: [ + { type: 'memberOf', targetRef: 'group:default/team-a' }, + { type: 'memberOf', targetRef: 'group:default/team-b' }, + { type: 'memberOf', targetRef: 'group:default/team-c' }, + { type: 'memberOf', targetRef: 'group:default/team-d' }, + { type: 'memberOf', targetRef: 'group:default/team-e' }, + ], + }; + + it('Should limit the number of displayed groups in the card', async () => { + await renderInTestApp( + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ); + + // should show the first 3 groups + expect(screen.getByText('team-a')).toBeInTheDocument(); + expect(screen.getByText('team-b')).toBeInTheDocument(); + expect(screen.getByText('team-c')).toBeInTheDocument(); + + // should not show the rest + expect(screen.queryByText('team-d')).not.toBeInTheDocument(); + expect(screen.queryByText('team-e')).not.toBeInTheDocument(); + }); + + it('Show more groups button when there are more user relations than the maximum', async () => { + await renderInTestApp( + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ); + + // clicking the button should open a dialog listing all groups + await userEvent.click(screen.getByText('...More (2)')); + + expect(screen.getByText('team-d')).toBeInTheDocument(); + expect(screen.getByText('team-e')).toBeInTheDocument(); + }); + + it('Hide more groups button when limit value is less than or equal to user relations', async () => { + await renderInTestApp( + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ); + + expect(screen.getByText('team-a')).toBeInTheDocument(); + expect(screen.getByText('team-b')).toBeInTheDocument(); + expect(screen.getByText('team-c')).toBeInTheDocument(); + expect(screen.getByText('team-d')).toBeInTheDocument(); + expect(screen.getByText('team-e')).toBeInTheDocument(); + expect(screen.queryByText('...More (0)')).not.toBeInTheDocument(); + }); + + it('Hide all groups when max relations is equals to zero', async () => { + await renderInTestApp( + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ); + + expect(screen.queryByText('team-a')).not.toBeInTheDocument(); + expect(screen.queryByText('team-b')).not.toBeInTheDocument(); + expect(screen.queryByText('team-c')).not.toBeInTheDocument(); + expect(screen.queryByText('team-d')).not.toBeInTheDocument(); + expect(screen.queryByText('team-e')).not.toBeInTheDocument(); + expect(screen.queryByText('...More (5)')).not.toBeInTheDocument(); + }); + }); }); describe('Edit Button', () => { diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx index 77188c6fb4..ba3ee9d561 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -25,14 +25,22 @@ import { InfoCardVariants, Link, } from '@backstage/core-components'; +import { createStyles, makeStyles } from '@material-ui/core/styles'; import Box from '@material-ui/core/Box'; import Grid from '@material-ui/core/Grid'; +import BaseButton from '@material-ui/core/ButtonBase'; import IconButton from '@material-ui/core/IconButton'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemText from '@material-ui/core/ListItemText'; import Tooltip from '@material-ui/core/Tooltip'; +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogActions from '@material-ui/core/DialogActions'; +import CloseIcon from '@material-ui/icons/Close'; import { EntityRefLinks, getEntityRelations, @@ -46,6 +54,36 @@ import GroupIcon from '@material-ui/icons/Group'; 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'; + +/** @public */ +export type UserProfileCardClassKey = + | 'closeButton' + | 'moreButton' + | 'dialogPaper'; + +const useStyles = makeStyles( + theme => + createStyles({ + closeButton: { + position: 'absolute', + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500], + }, + moreButton: { + display: 'contents', + color: theme.palette.primary.main, + }, + dialogPaper: { + minHeight: 400, + }, + }), + { name: 'PluginOrgUserProfileCard' }, +); + const CardTitle = (props: { title?: string }) => props.title ? ( @@ -58,10 +96,26 @@ const CardTitle = (props: { title?: string }) => export const UserProfileCard = (props: { variant?: InfoCardVariants; showLinks?: boolean; + maxRelations?: number; + hideIcons?: boolean; }) => { + const { maxRelations, hideIcons } = props; + + const classes = useStyles(); const { entity: user } = useEntity(); + const [isAllGroupsDialogOpen, setIsAllGroupsDialogOpen] = useState(false); + const { t } = useTranslationRef(orgTranslationRef); + + const toggleAllGroupsDialog = useCallback( + () => + setIsAllGroupsDialogOpen( + prevIsViewAllGroupsDialogOpen => !prevIsViewAllGroupsDialogOpen, + ), + [], + ); + if (!user) { - return User not found; + return {t('userProfileCard.userNotFound')}; } const entityMetadataEditUrl = @@ -86,8 +140,8 @@ export const UserProfileCard = (props: { <> {entityMetadataEditUrl && ( @@ -107,7 +161,7 @@ export const UserProfileCard = (props: { {profile?.email && ( - + @@ -117,24 +171,73 @@ export const UserProfileCard = (props: { )} - - - - - - - - - - - + {maxRelations === undefined || maxRelations > 0 ? ( + + + + + + + + + {maxRelations && memberOfRelations.length > maxRelations ? ( + <> + , + + {t('userProfileCard.moreGroupButtonTitle', { + number: String( + memberOfRelations.length - maxRelations, + ), + })} + + + ) : null} + + + ) : null} {props?.showLinks && } + + + + {t('userProfileCard.allGroupDialog.title', { + name: user.metadata.name, + })} + + + + + + + + + + + ); }; diff --git a/plugins/org/src/overridableComponents.ts b/plugins/org/src/overridableComponents.ts index 11381ff7d7..b4c5f31212 100644 --- a/plugins/org/src/overridableComponents.ts +++ b/plugins/org/src/overridableComponents.ts @@ -16,12 +16,21 @@ import { Overrides } from '@material-ui/core/styles/overrides'; import { StyleRules } from '@material-ui/core/styles/withStyles'; -import { MemberComponentClassKey, MembersListCardClassKey } from './components'; +import { + ComponentsGridClassKey, + MemberComponentClassKey, + MembersListCardClassKey, + OwnershipCardClassKey, + UserProfileCardClassKey, +} from './components'; /** @public */ export type CatalogReactComponentsNameToClassKey = { PluginOrgMemberComponent: MemberComponentClassKey; PluginOrgMembersListCardComponent: MembersListCardClassKey; + PluginOrgOwnershipCard: OwnershipCardClassKey; + PluginOrgComponentsGrid: ComponentsGridClassKey; + PluginOrgUserProfileCard: UserProfileCardClassKey; }; /** @public */ 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 71dbc1bfe5..4f6bd6c191 100644 --- a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md +++ b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md @@ -1,5 +1,84 @@ # @backstage/plugin-permission-backend-module-allow-all-policy +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-permission-common@0.9.1 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1 + +## 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 + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + +## 0.2.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + ## 0.2.8-next.1 ### 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 fb56a68093..446a8a4e59 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-next.1", + "version": "0.2.11-next.0", "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 6205e7e819..2bdcc96b2c 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,101 @@ # @backstage/plugin-permission-backend +## 0.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.1 + +## 0.7.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.7.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.7.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1 + +## 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 + +- 4da2965: Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests. +- cf8fd51: **BREAKING** Removed support for the legacy backend system, please [migrate to the new backend system](https://backstage.io/docs/backend-system/building-backends/migrating) + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.7.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + ## 0.7.0-next.1 ### Minor Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 2a2348a263..6ad3640147 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-next.1", + "version": "0.7.3-next.0", "backstage": { "role": "backend-plugin", "pluginId": "permission", diff --git a/plugins/permission-common/CHANGELOG.md b/plugins/permission-common/CHANGELOG.md index d23bb180f0..d3d9d320ca 100644 --- a/plugins/permission-common/CHANGELOG.md +++ b/plugins/permission-common/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-permission-common +## 0.9.1 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3 + +## 0.9.1-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3-next.0 + +## 0.9.0 + +### Minor Changes + +- 4da2965: Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests. + +### Patch Changes + +- 37328b1: Fixed an issue causing `PermissionClient` to throw an error when authorizing basic permissions with the `permission.EXPERIMENTAL_enableBatchedRequests` config enabled. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.9.0-next.0 ### Minor Changes diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index 19fd84bb5a..e12c557d4d 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-common", - "version": "0.9.0-next.0", + "version": "0.9.1", "description": "Isomorphic types and client for Backstage permissions and authorization", "backstage": { "role": "common-library", diff --git a/plugins/permission-common/src/PermissionClient.test.ts b/plugins/permission-common/src/PermissionClient.test.ts index b55e5da94d..4ddfadadda 100644 --- a/plugins/permission-common/src/PermissionClient.test.ts +++ b/plugins/permission-common/src/PermissionClient.test.ts @@ -407,7 +407,7 @@ describe('PermissionClient', () => { }, { id: req.body.items[1].id, - result: [AuthorizeResult.DENY], + result: AuthorizeResult.DENY, }, { id: req.body.items[2].id, diff --git a/plugins/permission-common/src/PermissionClient.ts b/plugins/permission-common/src/PermissionClient.ts index 93c3d5152f..61adc9279a 100644 --- a/plugins/permission-common/src/PermissionClient.ts +++ b/plugins/permission-common/src/PermissionClient.ts @@ -60,14 +60,16 @@ const authorizePermissionResponseSchema: z.ZodSchema = z.union([ @@ -240,9 +242,13 @@ export class PermissionClient implements PermissionEvaluator { const { id } = request[query.permission.name]; const item = responsesById[id]; - return { - result: query.resourceRef ? item.result.shift()! : item.result[0], - }; + + if (Array.isArray(item.result)) { + return { + result: query.resourceRef ? item.result.shift()! : item.result[0], + }; + } + return { result: item.result }; }); } diff --git a/plugins/permission-common/src/types/deprecated.ts b/plugins/permission-common/src/types/deprecated.ts new file mode 100644 index 0000000000..e2a4e404e4 --- /dev/null +++ b/plugins/permission-common/src/types/deprecated.ts @@ -0,0 +1,30 @@ +/* + * 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 { EvaluatePermissionRequest, EvaluatePermissionResponse } from './api'; +import { AuthorizeRequestOptions } from './permission'; + +/** + * A client interacting with the permission backend can implement this authorizer interface. + * @public + * @deprecated Use {@link @backstage/plugin-permission-common#PermissionEvaluator} instead + */ +export interface PermissionAuthorizer { + authorize( + requests: EvaluatePermissionRequest[], + options?: AuthorizeRequestOptions, + ): Promise; +} diff --git a/plugins/permission-common/src/types/index.ts b/plugins/permission-common/src/types/index.ts index f5380541df..60a588ffd2 100644 --- a/plugins/permission-common/src/types/index.ts +++ b/plugins/permission-common/src/types/index.ts @@ -48,8 +48,8 @@ export type { BasicPermission, PermissionAttributes, Permission, - PermissionAuthorizer, PermissionBase, ResourcePermission, AuthorizeRequestOptions, } from './permission'; +export type { PermissionAuthorizer } from './deprecated'; diff --git a/plugins/permission-common/src/types/permission.ts b/plugins/permission-common/src/types/permission.ts index eb1c0eb50d..69a0b0ebbd 100644 --- a/plugins/permission-common/src/types/permission.ts +++ b/plugins/permission-common/src/types/permission.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { EvaluatePermissionRequest, EvaluatePermissionResponse } from './api'; - /** * The attributes related to a given permission; these should be generic and widely applicable to * all permissions in the system. @@ -88,18 +86,6 @@ export type ResourcePermission = } >; -/** - * A client interacting with the permission backend can implement this authorizer interface. - * @public - * @deprecated Use {@link @backstage/plugin-permission-common#PermissionEvaluator} instead - */ -export interface PermissionAuthorizer { - authorize( - requests: EvaluatePermissionRequest[], - options?: AuthorizeRequestOptions, - ): Promise; -} - /** * Options for authorization requests. * @public diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 13c3f7907d..3a855bf38f 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,94 @@ # @backstage/plugin-permission-node +## 0.10.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.1 + +## 0.10.2 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + +## 0.10.2-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + +## 0.10.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0 + +## 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 + +- 4da2965: Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.10.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 0.10.0-next.1 ### Minor Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index f60f50f305..af101a7500 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-next.1", + "version": "0.10.3-next.0", "description": "Common permission and authorization utilities for backend plugins", "backstage": { "role": "node-library", diff --git a/plugins/permission-node/src/index.ts b/plugins/permission-node/src/index.ts index 32d71ddeb3..ab97b57021 100644 --- a/plugins/permission-node/src/index.ts +++ b/plugins/permission-node/src/index.ts @@ -21,5 +21,5 @@ */ export * from './integration'; export * from './policy'; -export * from './types'; +export type { PermissionRule, PermissionRuleset } from './types'; export { ServerPermissionClient } from './ServerPermissionClient'; diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index 4ee937866b..522e60bac5 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -30,9 +30,8 @@ import { PermissionCriteria, PolicyDecision, } from '@backstage/plugin-permission-common'; -import { PermissionRule, PermissionRuleset } from '../types'; +import { NoInfer, PermissionRule, PermissionRuleset } from '../types'; import { - NoInfer, createGetRule, isAndCriteria, isNotCriteria, diff --git a/plugins/permission-node/src/integration/createPermissionRule.ts b/plugins/permission-node/src/integration/createPermissionRule.ts index ba6f41145b..36f8205cf8 100644 --- a/plugins/permission-node/src/integration/createPermissionRule.ts +++ b/plugins/permission-node/src/integration/createPermissionRule.ts @@ -18,10 +18,9 @@ import { PermissionCriteria, PermissionRuleParams, } from '@backstage/plugin-permission-common'; -import { PermissionRule } from '../types'; +import { NoInfer, PermissionRule } from '../types'; import { z } from 'zod'; import { PermissionResourceRef } from './createPermissionResourceRef'; -import { NoInfer } from './util'; /** * @public diff --git a/plugins/permission-node/src/integration/util.ts b/plugins/permission-node/src/integration/util.ts index 9be90e15fc..c2fa200237 100644 --- a/plugins/permission-node/src/integration/util.ts +++ b/plugins/permission-node/src/integration/util.ts @@ -22,14 +22,6 @@ import { } from '@backstage/plugin-permission-common'; import { PermissionRule } from '../types'; -/** - * Prevent use of type parameter from contributing to type inference. - * - * https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-980401795 - * @ignore - */ -export type NoInfer = T extends infer S ? S : never; - /** * Utility function used to parse a PermissionCriteria * @param criteria - a PermissionCriteria diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index b1585d1827..578117a4f1 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -19,7 +19,15 @@ import type { PermissionRuleParams, } from '@backstage/plugin-permission-common'; import { z } from 'zod'; -import { NoInfer } from './integration/util'; + +/** + * Prevent use of type parameter from contributing to type inference. + * + * https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-980401795 + * + * @ignore + */ +export type NoInfer = T extends infer S ? S : never; /** * A conditional rule that can be provided in an diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index d68a3a1566..df0bc44dc3 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-permission-react +## 0.4.36 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/core-plugin-api@1.10.9 + +## 0.4.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/core-plugin-api@1.10.9-next.0 + +## 0.4.35 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.8 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-common@0.9.0 + +## 0.4.34 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/config@1.3.2 + ## 0.4.34-next.1 ### Patch Changes diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index f52f0eb58b..6e15c5fdeb 100644 --- a/plugins/permission-react/package.json +++ b/plugins/permission-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-react", - "version": "0.4.34-next.1", + "version": "0.4.36", "backstage": { "role": "web-library", "pluginId": "permission", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index c7c9c22f77..03dd482958 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,80 @@ # @backstage/plugin-proxy-backend +## 0.6.5-next.1 + +### Patch Changes + +- 0810cd8: correct rewrite rule to avoid extra subpath in proxy path + +## 0.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-proxy-node@0.1.7-next.0 + - @backstage/types@1.2.1 + +## 0.6.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-proxy-node@0.1.6 + +## 0.6.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-proxy-node@0.1.6-next.0 + +## 0.6.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/types@1.2.1 + - @backstage/plugin-proxy-node@0.1.5 + +## 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/types@1.2.1 + - @backstage/plugin-proxy-node@0.1.4 + +## 0.6.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/types@1.2.1 + - @backstage/plugin-proxy-node@0.1.4-next.2 + ## 0.6.2-next.1 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 60d0464c4a..332b36db68 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-next.1", + "version": "0.6.5-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-backend/src/service/router.ts b/plugins/proxy-backend/src/service/router.ts index e58bccd7af..3fb33eb572 100644 --- a/plugins/proxy-backend/src/service/router.ts +++ b/plugins/proxy-backend/src/service/router.ts @@ -292,8 +292,7 @@ export async function createRouter( logger: options.logger, }; - const externalUrl = await options.discovery.getExternalBaseUrl('proxy'); - const { pathname: pathPrefix } = new URL(externalUrl); + const pathPrefix = '/api/proxy'; const proxyConfig: ProxyConfig = { ...(options.additionalEndpoints ?? {}), diff --git a/plugins/proxy-node/CHANGELOG.md b/plugins/proxy-node/CHANGELOG.md index 6d1a68beb7..167c8742fb 100644 --- a/plugins/proxy-node/CHANGELOG.md +++ b/plugins/proxy-node/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/plugin-proxy-node +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + +## 0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1 + +## 0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + +## 0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + +## 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + +## 0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + ## 0.1.4-next.1 ### Patch Changes diff --git a/plugins/proxy-node/package.json b/plugins/proxy-node/package.json index 20db83410b..92e0ffde7d 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-next.1", + "version": "0.1.7-next.0", "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 4575bae2c7..cf763e0270 100644 --- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -1,5 +1,119 @@ # @backstage/plugin-scaffolder-backend-module-azure +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + +## 0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## 0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.2.10 + +### Patch Changes + +- e89d7b6: Migrating `azure` actions to using the new `zod` schema format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.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 + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + ## 0.2.9-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index 9507c527d7..6df38b028b 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-next.2", + "version": "0.2.12-next.0", "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 cd1207a738..4d4595c79c 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,132 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-cloud +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.1 + +## 0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.1 + +## 0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.1-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.2.10 + +### Patch Changes + +- ca9fdc0: Migrate `bitbucket-cloud` to new actions format +- 9c8ff0c: Update pull request creation filter to include .gitignore files in the created pull request +- 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.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.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 + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.3 + ## 0.2.9-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index 79ea0f1131..cb73f625dd 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-next.2", + "version": "0.2.12-next.0", "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 ad54b35865..813609a611 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,122 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-server +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + +## 0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## 0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.2.10 + +### Patch Changes + +- d8169fc: Migrate the actions to the new format +- 9c8ff0c: Update pull request creation filter to include .gitignore files in the created pull request +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.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 + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + ## 0.2.9-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-server/knip-report.md b/plugins/scaffolder-backend-module-bitbucket-server/knip-report.md index 2661c35327..b9ed10af2c 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/knip-report.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/knip-report.md @@ -1,2 +1,8 @@ # Knip report +## Unused dependencies (1) + +| Name | Location | Severity | +| :-- | :----------- | :------- | +| zod | package.json | error | + diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index c092f10207..a02789f992 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-next.2", + "version": "0.2.12-next.0", "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 c8bfd2fc7b..1ddf48c9f9 100644 --- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,142 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket +## 0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.12-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.12-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + +## 0.3.12 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11 + +## 0.3.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.2 + +## 0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.1 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## 0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.3.11 + +### Patch Changes + +- 7f710d2: Migrating `bitbucket` actions to use the new `zod` format +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10 + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## 0.3.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.2 + +## 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 + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.3.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.3 + ## 0.3.10-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json index 699858702c..be7e72dcb8 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-next.2", + "version": "0.3.13-next.0", "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 06756b19fd..9555bc61cc 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,121 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + +## 0.3.11 + +### Patch Changes + +- 642282d: Added support for new link format for on-prem Confluence +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## 0.3.11-next.2 + +### Patch Changes + +- 642282d: Added support for new link format for on-prem Confluence +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## 0.3.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## 0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.3.10 + +### Patch Changes + +- 4a86bca: Migrate actions to new format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.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 + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.3.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + ## 0.3.9-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/README.md b/plugins/scaffolder-backend-module-confluence-to-markdown/README.md index d3722f1227..7ea832b80b 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/README.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/README.md @@ -106,7 +106,7 @@ spec: properties: confluenceUrls: type: array - description: Urls for Confluence doc to be converted to markdown. In format /display// or /spaces//pages// for Confluence cloud + description: Urls for Confluence doc to be converted to markdown. In format /display// or /spaces//pages// items: type: string ui:options: diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index de6045689f..6e05ed6dc9 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-next.2", + "version": "0.3.12-next.0", "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-confluence-to-markdown/src/actions/confluence/helpers.test.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts index 42d83c18a8..9acf40d6c2 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts @@ -50,6 +50,17 @@ describe('createConfluenceVariables', () => { expect(title).toEqual('Cloud+Page+Title'); expect(titleWithSpaces).toEqual('Cloud Page Title'); }); + + it('should return values for Confluence Url using spaces (non-cloud)', () => { + const url = + 'https://confluence.example.com/spaces/SPACEKEY/pages/1234567/Confluence+Page+Title'; + + const { spacekey, title, titleWithSpaces } = createConfluenceVariables(url); + + expect(spacekey).toEqual('SPACEKEY'); + expect(title).toEqual('Confluence+Page+Title'); + expect(titleWithSpaces).toEqual('Confluence Page Title'); + }); }); describe('getConfluenceConfig', () => { diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts index bbc722b1d5..04d475677c 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts @@ -15,7 +15,7 @@ */ import { Config } from '@backstage/config'; -import { ResponseError, ConflictError, InputError } from '@backstage/errors'; +import { ResponseError, ConflictError } from '@backstage/errors'; import fs from 'fs-extra'; import { Readable } from 'stream'; @@ -188,26 +188,24 @@ export const createConfluenceVariables = (url: string) => { let title: string | undefined = undefined; let titleWithSpaces: string | undefined = ''; const params = new URL(url); - if (params.pathname.split('/')[1] === 'display') { - // https://confluence.example.com/display/SPACEKEY/Page+Title - spacekey = params.pathname.split('/')[2]; - title = params.pathname.split('/')[3]; - titleWithSpaces = title?.replace(/\+/g, ' '); - return { spacekey, title, titleWithSpaces }; - } else if (params.pathname.split('/')[2] === 'display') { - // https://confluence.example.com/prefix/display/SPACEKEY/Page+Title - spacekey = params.pathname.split('/')[3]; - title = params.pathname.split('/')[4]; - titleWithSpaces = title?.replace(/\+/g, ' '); - return { spacekey, title, titleWithSpaces }; - } else if (params.pathname.split('/')[2] === 'spaces') { - // https://example.atlassian.net/wiki/spaces/SPACEKEY/pages/1234567/Page+Title - spacekey = params.pathname.split('/')[3]; - title = params.pathname.split('/')[6]; - titleWithSpaces = title?.replace(/\+/g, ' '); - return { spacekey, title, titleWithSpaces }; + const pathParts = params.pathname.split('/').filter(Boolean); + + if (pathParts.includes('display')) { + // /display// + const idx = pathParts.indexOf('display'); + spacekey = pathParts[idx + 1]; + title = pathParts[idx + 2]; + } else if (pathParts.includes('spaces')) { + // /spaces/<SPACEKEY>/pages/<PAGEID>/<TITLE> + const idx = pathParts.indexOf('spaces'); + spacekey = pathParts[idx + 1]; + title = pathParts[pathParts.length - 1]; + } else { + throw new Error( + 'The Url format for Confluence is incorrect. Acceptable format is `<CONFLUENCE_BASE_URL>/display/<SPACEKEY>/<PAGE+TITLE>` or `<CONFLUENCE_BASE_URL>/spaces/<SPACEKEY>/pages/<PAGEID>/<PAGE+TITLE>`', + ); } - throw new InputError( - 'The Url format for Confluence is incorrect. Acceptable format is `<CONFLUENCE_BASE_URL>/display/<SPACEKEY>/<PAGE+TITLE>` or `<CONFLUENCE_BASE_URL>/spaces/<SPACEKEY>/pages/<PAGEID>/<PAGE+TITLE>` for Confluence cloud', - ); + + titleWithSpaces = title?.replace(/\+/g, ' '); + return { spacekey, title, titleWithSpaces }; }; diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index c1a9ce091f..f0f706737b 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,138 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.3.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + +## 0.3.13 + +### Patch Changes + +- cf9ba6f: Fixing the typescript issue with using `z.unknown()` +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-defaults@0.11.1 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## 0.3.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## 0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## 0.3.13-next.0 + +### Patch Changes + +- cf9ba6f: Fixing the typescript issue with using `z.unknown()` +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.3.11 + +### Patch Changes + +- 0b2bbf1: Migrate using new actions format +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + +## 0.3.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.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 + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-defaults@0.10.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.3.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.3.10-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 404bad2b94..3e27ad15a9 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-next.2", + "version": "0.3.14-next.0", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-cookiecutter/report.api.md b/plugins/scaffolder-backend-module-cookiecutter/report.api.md index 4f963ce818..a60a8c0396 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/report.api.md +++ b/plugins/scaffolder-backend-module-cookiecutter/report.api.md @@ -4,11 +4,12 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; -import { JsonObject } from '@backstage/types'; +import { objectOutputType } from 'zod'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { Writable } from 'stream'; +import { ZodTypeAny } from 'zod'; // @public export interface ContainerRunner { @@ -48,13 +49,15 @@ export function createFetchCookiecutterAction(options: { }): TemplateAction< { url: string; - targetPath?: string; - values: JsonObject; - copyWithoutRender?: string[]; - extensions?: string[]; - imageName?: string; + values: objectOutputType<{}, ZodTypeAny, 'passthrough'>; + 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..9a9df95dea 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,55 @@ 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', - 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', - }, - }, + }), + targetPath: z => + z + .string({ + description: + 'Target path within the working directory to download the contents to.', + }) + .optional(), + values: z => + z + .object( + {}, + { + description: 'Values to pass on to cookiecutter for templating', + }, + ) + .passthrough(), + 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 9f7f9b1631..3ff86ac0ea 100644 --- a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,117 @@ # @backstage/plugin-scaffolder-backend-module-gcp +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + +## 0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## 0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.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 + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + ## 0.2.9-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gcp/package.json b/plugins/scaffolder-backend-module-gcp/package.json index 7c2fc0a624..2d07b01102 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-next.2", + "version": "0.2.12-next.0", "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 7c0367d1b0..d73f996de7 100644 --- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,120 @@ # @backstage/plugin-scaffolder-backend-module-gerrit +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + +## 0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## 0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.2.10 + +### Patch Changes + +- e24b29b: Migrating to use new format for actions +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.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 + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + ## 0.2.9-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index aa6e7478ef..258bf7e200 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-next.2", + "version": "0.2.12-next.0", "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 c7e65d176e..70864c66b7 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,119 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + +## 0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## 0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.2.10 + +### Patch Changes + +- ed41017: Migrate to new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.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 + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + ## 0.2.9-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index 1be15b9164..978a40e3f1 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-next.2", + "version": "0.2.12-next.0", "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<any>; + ctx: ActionContext<any, any, any>; }, ) { 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 6170587864..d941db48c5 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,260 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.8.2-next.1 + +### Patch Changes + +- a22cce0: Fixed bug in the `customProperties` type which was preventing it being used to set a list of values against a key (e.g. for multi-select fields) + +## 0.8.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + +## 0.8.1 + +### Patch Changes + +- f36bcf9: Added support for file deletion to `publish:github:pull-request` action. + + Example usage: + + ```diff + - action: publish:github:pull-request + id: clean-up-pr + input: + description: This is the description + + filesToDelete: + + - outdated/changelog.md + + - sample-file.txt + owner: owner + repo: repo + title: Title Goes Here + + ``` + +- 38db3eb: Fix typo in `InputError` +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-node@1.17.2 + +## 0.8.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## 0.8.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## 0.8.1-next.0 + +### Patch Changes + +- f36bcf9: Added support for file deletion to `publish:github:pull-request` action. + + Example usage: + + ```diff + - action: publish:github:pull-request + id: clean-up-pr + input: + description: This is the description + + filesToDelete: + + - outdated/changelog.md + + - sample-file.txt + owner: owner + repo: repo + title: Title Goes Here + + ``` + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.8.0 + +### Minor Changes + +- 5863b04: **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, + }), + ); + }, + }); + }, + }); + ``` + +### Patch Changes + +- 575c76b: Migrate to using new actions +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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.8.0-next.2 + +### Minor Changes + +- 5863b04: **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, + }), + ); + }, + }); + }, + }); + ``` + +### Patch Changes + +- 575c76b: Migrate to using new actions +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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-node@1.17.1-next.1 + +## 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 + +- 6579c2c: Use action context logger in Octokit client +- 72d019d: Removed various typos +- b2b654c: Added optional assignees parameter to `publish:github:pull-request` action +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.7.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.7.1-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index 0217d0b48d..9ff59db2e4 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-next.2", + "version": "0.8.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..85ca128c34 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<string, string> | 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<string, string> | undefined; + secrets?: Record<string, string> | 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<string, string> | undefined; + secrets?: Record<string, string> | 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<string, string | string[]> | 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<string, string> | undefined; + secrets?: Record<string, string> | undefined; + oidcCustomization?: + | { + useDefault: boolean; + includeClaimKeys?: string[] | undefined; + } + | undefined; + requiredCommitSigning?: boolean | undefined; + requiredLinearHistory?: boolean | undefined; + customProperties?: Record<string, string | string[]> | undefined; + subscribe?: boolean | undefined; }, - JsonObject, - 'v1' + { + remoteUrl: string; + repoContentsUrl: string; + commitHash: string; + }, + 'v2' >; // @public @@ -425,28 +442,33 @@ 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; + filesToDelete?: string[] | undefined; + 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<any>; + let action: TemplateAction<any, any, any>; 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..ed6422f090 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<any>; + let action: TemplateAction<any, any, any>; const mockContext = createMockActionContext({ input: { @@ -330,7 +330,7 @@ describe('publish:github', () => { ...mockContext.input, customProperties: { foo: 'bar', - foo2: 'bar2', + foo2: ['bar2', 'bar3'], }, }, }); @@ -505,7 +505,7 @@ describe('publish:github', () => { ...mockContext.input, customProperties: { foo: 'bar', - foo2: 'bar2', + foo2: ['bar2', 'bar3'], }, }, }); @@ -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<any>; + let action: TemplateAction<any, any, any>; 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<any>; + let action: TemplateAction<any, any, any>; 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<any, any>; + let action: TemplateAction<any, any, any>; 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<any, any>; + let action: TemplateAction<any, any, any>; 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 `<num>` for the reference number. `<num>` 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 `<num>` 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 `<num>` 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<any>; + let action: TemplateAction<any, any, any>; 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<any>; + let action: TemplateAction<any, any, any>; 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<any>; + let action: TemplateAction<any, any, any>; 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<any>; + let action: TemplateAction<any, any, any>; 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<CatalogApi> = { - 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<any>; + let action: TemplateAction<any, any, any>; 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<CatalogApi> = { - 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<any>; + let action: TemplateAction<any, any, any>; 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<Entity | undefined> = []; // 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<any>; + let action: TemplateAction<any, any, any>; 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<any>; + let action: TemplateAction<any, any, any>; 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<any>; + let action: TemplateAction<any, any, any>; 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<any>; + let action: TemplateAction<any, any, any>; 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<any>; + let action: TemplateAction<any, any, any>; 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..5a8019579b 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts @@ -24,18 +24,20 @@ import { TemplateAction, } from '@backstage/plugin-scaffolder-node'; import fs from 'fs-extra'; +import path from 'node:path'; import { createPublishGithubPullRequestAction } from './githubPullRequest'; import { createMockDirectory } from '@backstage/backend-test-utils'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { DELETE_FILE } from 'octokit-plugin-create-pull-request'; type GithubPullRequestActionInput = ReturnType< typeof createPublishGithubPullRequestAction -> extends TemplateAction<infer U> +> extends TemplateAction<infer U, any, any> ? U : never; describe('createPublishGithubPullRequestAction', () => { - let instance: TemplateAction<GithubPullRequestActionInput>; + let instance: TemplateAction<GithubPullRequestActionInput, any, any>; let fakeClient: { createPullRequest: jest.Mock; rest: { @@ -50,8 +52,6 @@ describe('createPublishGithubPullRequestAction', () => { const workspacePath = mockDir.resolve('workspace'); beforeEach(() => { - mockDir.clear(); - config = new ConfigReader({}); integrations = ScmIntegrations.fromConfig(config); fakeClient = { @@ -92,12 +92,13 @@ describe('createPublishGithubPullRequestAction', () => { }); afterEach(() => { + mockDir.clear(); jest.resetAllMocks(); }); describe('with targetBranchName', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { fakeClient = { @@ -181,7 +182,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with no sourcePath', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -250,7 +251,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with sourcePath', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -304,9 +305,121 @@ describe('createPublishGithubPullRequestAction', () => { }); }); + describe('with filesToDelete', () => { + let input: GithubPullRequestActionInput; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; + + beforeEach(() => { + input = { + filesToDelete: ['changed-file-to-delete.txt', 'delete-me-too.md'], + repoUrl: 'github.com?owner=myorg&repo=myrepo', + title: 'Create my new app', + branchName: 'new-app', + description: 'This PR is really good', + }; + + mockDir.setContent({ + [workspacePath]: { + 'catpants.md': 'cat + pants', + 'changed-file-to-delete.txt': 'file is changed and deleted', + 'foobar.txt': 'Hello there!', + }, + }); + + ctx = createMockActionContext({ input, workspacePath }); + }); + + it('should delete named files', async () => { + await instance.handler(ctx); + + expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ + owner: 'myorg', + repo: 'myrepo', + title: input.title, + head: input.branchName, + body: input.description, + changes: [ + { + commit: input.title, + files: { + 'catpants.md': { + content: Buffer.from('cat + pants').toString('base64'), + encoding: 'base64', + mode: '100644', + }, + 'foobar.txt': { + content: Buffer.from('Hello there!').toString('base64'), + encoding: 'base64', + mode: '100644', + }, + 'changed-file-to-delete.txt': DELETE_FILE, + 'delete-me-too.md': DELETE_FILE, + }, + }, + ], + }); + }); + + describe('with targetPath', () => { + const targetPath = `target-path-${Date.now()}`; + + beforeEach(() => { + Object.assign(input, { + filesToDelete: [ + path.posix.join('nested', 'catpants.md'), + path.posix.join('nested', 'delete-me.too'), + ], + targetPath, + }); + + mockDir.setContent({ + [workspacePath]: { + 'catpants.md': 'cat + pants', + 'foobar.txt': 'Hello there!', + [path.posix.join('nested', 'catpants.md')]: 'delete me', + [path.posix.join('nested', 'delete-me.too')]: 'delete me too', + }, + }); + }); + + it('should delete named files', async () => { + await instance.handler(ctx); + + expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ + owner: 'myorg', + repo: 'myrepo', + title: input.title, + head: input.branchName, + body: input.description, + changes: [ + { + commit: input.title, + files: { + [path.posix.join(targetPath, 'catpants.md')]: { + content: Buffer.from('cat + pants').toString('base64'), + encoding: 'base64', + mode: '100644', + }, + [path.posix.join(targetPath, 'foobar.txt')]: { + content: Buffer.from('Hello there!').toString('base64'), + encoding: 'base64', + mode: '100644', + }, + [path.posix.join(targetPath, 'nested', 'catpants.md')]: + DELETE_FILE, + [path.posix.join(targetPath, 'nested', 'delete-me.too')]: + DELETE_FILE, + }, + }, + ], + }); + }); + }); + }); + describe('with repoUrl', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -359,7 +472,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with reviewers and teamReviewers', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -406,7 +519,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with no reviewers and teamReviewers', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -431,7 +544,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with assignees', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -475,7 +588,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with broken symlink', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -520,7 +633,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with executable file mode 755', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -579,7 +692,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with executable file mode 775', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -638,7 +751,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with commit message', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -683,7 +796,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with force fork', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -729,7 +842,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author name and email', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -779,7 +892,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author name', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -828,7 +941,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author email', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -877,7 +990,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author from config file', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -946,7 +1059,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author attributes and config file', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -1017,7 +1130,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author fallback and no config', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -1112,7 +1225,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with createWhenEmpty equals true', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; beforeEach(() => { input = { @@ -1175,7 +1288,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with createWhenEmpty equals false', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext<GithubPullRequestActionInput>; + let ctx: ActionContext<GithubPullRequestActionInput, any, any>; 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..7d1e88d0c7 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -27,7 +27,10 @@ import { } from '@backstage/plugin-scaffolder-node'; import { Octokit } from 'octokit'; import { CustomErrorBase, InputError } from '@backstage/errors'; -import { createPullRequest } from 'octokit-plugin-create-pull-request'; +import { + createPullRequest, + DELETE_FILE, +} from 'octokit-plugin-create-pull-request'; import { getOctokitOptions } from '../util'; import { examples } from './githubPullRequest.examples'; import { @@ -128,175 +131,154 @@ 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', + }), + filesToDelete: z => + z + .array(z.string(), { + description: 'List of files that will be deleted', + }) + .optional(), + 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) { const { repoUrl, branchName, + filesToDelete, targetBranchName, title, description, @@ -351,8 +333,8 @@ export const createPublishGithubPullRequestAction = ( file: SerializedFile, ): 'utf-8' | 'base64' => (file.symlink ? 'utf-8' : 'base64'); - const files = Object.fromEntries( - directoryContents.map(file => [ + const files = Object.fromEntries([ + ...directoryContents.map(file => [ targetPath ? path.posix.join(targetPath, file.path) : file.path, { // See the properties of tree items @@ -369,7 +351,13 @@ export const createPublishGithubPullRequestAction = ( content: file.content.toString(determineFileEncoding(file)), }, ]), - ); + // order of arrays is important so filesToDelete will overwrite + // changes from files above + ...(filesToDelete || []).map(filePath => [ + targetPath ? path.posix.join(targetPath, filePath) : filePath, + DELETE_FILE, + ]), + ]); // If this is a dry run, log and return if (ctx.isDryRun) { 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<any>; + let action: TemplateAction<any, any, any>; 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..b90f0c4e5a 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<any>; + let action: TemplateAction<any, any, any>; const mockContext = createMockActionContext({ input: { @@ -279,7 +279,7 @@ describe('github:repo:create', () => { ...mockContext.input, customProperties: { foo: 'bar', - foo2: 'bar2', + foo2: ['bar2', 'bar3'], }, }, }); @@ -455,7 +455,7 @@ describe('github:repo:create', () => { ...mockContext.input, customProperties: { foo: 'bar', - foo2: 'bar2', + foo2: ['bar2', 'bar3'], }, }, }); 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<any>; + let action: TemplateAction<any, any, any>; 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<any>; + let action: TemplateAction<any, any, any>; 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<any>; + let action: TemplateAction<any, any, any>; 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<any>; + let action: TemplateAction<any, any, any>; 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/helpers.ts b/plugins/scaffolder-backend-module-github/src/actions/helpers.ts index db3aa03679..6509d7fd29 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/helpers.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/helpers.ts @@ -75,7 +75,7 @@ export async function createGithubRepoWithCollaboratorsAndTopics( includeClaimKeys?: string[]; } | undefined, - customProperties: { [key: string]: string } | undefined, + customProperties: { [key: string]: string | string[] } | undefined, subscribe: boolean | undefined, logger: LoggerService, ) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts index cac93ec7c6..e1df40f85d 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.union([z.string(), z.array(z.string())]), { + description: + 'Custom properties to be added to the repository (note, this only works for organization repositories). All values must be strings', + }) + .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-github/src/util.ts b/plugins/scaffolder-backend-module-github/src/util.ts index c25ca99f5f..260c93993f 100644 --- a/plugins/scaffolder-backend-module-github/src/util.ts +++ b/plugins/scaffolder-backend-module-github/src/util.ts @@ -89,7 +89,7 @@ export async function getOctokitOptions(options: { if (!owner || !repo) { throw new InputError( - `No owner and/or owner provided, which is required if a token is not provided`, + `No owner and/or repo provided, which is required if a token is not provided`, ); } diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index aa4a43c22c..b461b8eaa8 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,131 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.9.4-next.1 + +### Patch Changes + +- 5bb870b: Show additional information about the cause in error messages from GitLab + +## 0.9.4-next.0 + +### Patch Changes + +- df2bfab: The `description` property in `publish:gitlab:merge-request` has been made optional again to comply with the GitLab API. +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + +## 0.9.3 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## 0.9.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## 0.9.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## 0.9.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.9.2 + +### Patch Changes + +- 063b2d3: Fixed dependency to frontend package from tests +- a296637: Migrate to new action format +- 3d6493a: Support merge request labels in publish:gitlab:merge-request +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## 0.9.2-next.2 + +### Patch Changes + +- a296637: Migrate to new action format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.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 + +- d15355c: If the commit action is not `create` log a more appropriate error message to the end user advising that the files they're trying to modify might not exist +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.9.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + ## 0.9.1-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index d2f699b219..ee44f1e002 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-next.2", + "version": "0.9.4-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", @@ -50,6 +50,7 @@ "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", + "@gitbeaker/requester-utils": "^41.2.0", "@gitbeaker/rest": "^41.2.0", "luxon": "^3.0.0", "winston": "^3.2.1", @@ -59,7 +60,6 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" } } diff --git a/plugins/scaffolder-backend-module-gitlab/report.api.md b/plugins/scaffolder-backend-module-gitlab/report.api.md index cdb6065a3b..1c4ce80995 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 @@ -194,21 +218,27 @@ export const createPublishGitlabMergeRequestAction: (options: { { 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; + description?: string | undefined; + 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<string, string> | 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.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts index 4d8e0fd0c0..449c5980a4 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import yaml from 'yaml'; import { createGitlabGroupEnsureExistsAction } from './gitlabGroupEnsureExists'; import { examples } from './gitlabGroupEnsureExists.examples'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Groups: { @@ -50,15 +50,17 @@ describe('gitlab:group:ensureExists', () => { full_path: 'group1', }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); @@ -91,15 +93,17 @@ describe('gitlab:group:ensureExists', () => { full_path: 'group1/group2', }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); @@ -138,15 +142,17 @@ describe('gitlab:group:ensureExists', () => { full_path: 'group1/group2/group3', }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); @@ -170,15 +176,17 @@ describe('gitlab:group:ensureExists', () => { }); it(`Should ${examples[3].description}`, async () => { - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); @@ -213,15 +221,17 @@ describe('gitlab:group:ensureExists', () => { full_path: 'group1/group2/group3', }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); @@ -264,15 +274,17 @@ describe('gitlab:group:ensureExists', () => { full_path: 'group1/group2/group3/group4', }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts index 3d703eb91f..8e52ee999e 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { createGitlabGroupEnsureExistsAction } from './gitlabGroupEnsureExists'; import { getClient } from '../util'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Groups: { @@ -45,15 +45,17 @@ describe('gitlab:group:ensureExists', () => { jest.clearAllMocks(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); 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.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts index 917ba61d91..858adb4b99 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { createGitlabIssueAction } from './gitlabIssueCreate'; import { examples } from './gitlabIssueCreate.examples'; import yaml from 'yaml'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Issues: { @@ -46,15 +46,17 @@ describe('gitlab:issues:create', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'sample-token', - apiBaseUrl: 'https://gitlab.com/api/v1', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'sample-token', + apiBaseUrl: 'https://gitlab.com/api/v1', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts index d5aa2d1e17..e09bfaf249 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { IssueType } from '../commonGitlabConfig'; import { createGitlabIssueAction } from './gitlabIssueCreate'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Issues: { @@ -45,15 +45,17 @@ describe('gitlab:issues:create', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'myIntegrationsToken', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'myIntegrationsToken', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); 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.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.test.ts index 7e50eac66c..fa53609c19 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.test.ts @@ -15,11 +15,11 @@ */ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { editGitlabIssueAction } from './gitlabIssueEdit'; import { examples } from './gitlabIssueEdit.examples'; import yaml from 'yaml'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Issues: { @@ -46,15 +46,17 @@ describe('gitlab:issue:edit', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'myIntegrationsToken', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'myIntegrationsToken', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.test.ts index 9604fe8fe2..a16236a509 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.test.ts @@ -15,10 +15,10 @@ */ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { IssueType } from '../commonGitlabConfig'; import { editGitlabIssueAction } from './gitlabIssueEdit'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Issues: { @@ -45,15 +45,17 @@ describe('gitlab:issue:edit', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'myIntegrationsToken', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'myIntegrationsToken', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); 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<any>; + let instance: TemplateAction<any, any, 'v2'>; 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<any>; + let instance: TemplateAction<any, any, 'v2'>; 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..66c72b4c46 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,99 @@ 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() + .optional() + .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 +254,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 +463,7 @@ which uses additional API calls in order to detect whether to 'create', 'update' : false, assigneeId, reviewerIds, + labels, }, ); return { @@ -552,7 +517,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.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.test.ts index 53cbfb0b87..41535479fe 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { createTriggerGitlabPipelineAction } from './gitlabPipelineTrigger'; import { examples } from './gitlabPipelineTrigger.examples'; import yaml from 'yaml'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { PipelineTriggerTokens: { @@ -49,15 +49,17 @@ describe('gitlab:pipeline:trigger', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'glpat-abcdef', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'glpat-abcdef', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts index 3803198ec8..89498c52f2 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { createTriggerGitlabPipelineAction } from './gitlabPipelineTrigger'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { PipelineTriggerTokens: { @@ -47,15 +47,17 @@ describe('gitlab:pipeline:trigger', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'glpat-abcdef', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'glpat-abcdef', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); 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<any>; + let instance: TemplateAction<any, any, any>; 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<any>; + let instance: TemplateAction<any, any, any>; 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<any>; + let instance: TemplateAction<any, any, any>; 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<any>; + let instance: TemplateAction<any, any, any>; 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-gitlab/src/actions/helpers.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/helpers.ts index 7f6dead3ca..59f5601747 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/helpers.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/helpers.ts @@ -17,6 +17,7 @@ import { parseRepoUrl } from '@backstage/plugin-scaffolder-node'; import { ErrorLike, InputError, isError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { Gitlab } from '@gitbeaker/rest'; +import { GitbeakerRequestError } from '@gitbeaker/requester-utils'; export function createGitlabApi(options: { integrations: ScmIntegrationRegistry; @@ -57,7 +58,13 @@ function isGitlabError(e: unknown): e is GitlabError { return isError(e) && 'description' in e && typeof e.description === 'string'; } +function isGitbeakerRequestError(e: unknown): e is GitbeakerRequestError { + return isError(e) && e.name === 'GitbeakerRequestError'; +} + export function getErrorMessage(e: unknown): string { + if (isGitbeakerRequestError(e) && e.cause) + return `${e} - ${e.cause.description}`; if (isGitlabError(e)) return `${e} - ${e.description}`; return String(e); } diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md index 4db8585828..4a9578db3a 100644 --- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -1,5 +1,116 @@ # @backstage/plugin-scaffolder-backend-module-notifications +## 0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/plugin-notifications-node@0.2.18-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-notifications-common@0.0.10 + +## 0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-notifications-common@0.0.10 + - @backstage/plugin-notifications-node@0.2.17 + +## 0.1.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## 0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-notifications-common@0.0.10-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + - @backstage/plugin-notifications-node@0.2.17-next.0 + +## 0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-notifications-node@0.2.16 + - @backstage/plugin-notifications-common@0.0.9 + +## 0.1.11 + +### Patch Changes + +- 1fb5f06: Adds ability for user to turn on/off notifications for specific topics within an origin. +- 3f56115: Removed `octokit` dependency as it was not being used +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/plugin-notifications-common@0.0.9 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-notifications-node@0.2.16 + +## 0.1.11-next.2 + +### Patch Changes + +- 3f56115: Removed `octokit` dependency as it was not being used +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.1 + +## 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 + +- b60253d: Change notification send scaffolder action to use native zod schemas +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/plugin-notifications-node@0.2.15 + - @backstage/plugin-notifications-common@0.0.8 + +## 0.1.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/plugin-notifications-common@0.0.8 + - @backstage/plugin-notifications-node@0.2.15-next.2 + ## 0.1.10-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-notifications/knip-report.md b/plugins/scaffolder-backend-module-notifications/knip-report.md index 9ac53047a7..2661c35327 100644 --- a/plugins/scaffolder-backend-module-notifications/knip-report.md +++ b/plugins/scaffolder-backend-module-notifications/knip-report.md @@ -1,8 +1,2 @@ # Knip report -## Unused dependencies (1) - -| Name | Location | Severity | -| :------ | :----------- | :------- | -| octokit | package.json | error | - diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json index ea6c416a9e..77c41d9758 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-next.2", + "version": "0.1.13-next.0", "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 9c9117bfb3..a82fc034f1 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,126 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + +## 0.5.11 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + +## 0.5.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## 0.5.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## 0.5.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.5.10 + +### Patch Changes + +- a579693: Migrate to new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + +## 0.5.10-next.2 + +### Patch Changes + +- a579693: Migrate to new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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.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 + +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.5.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.5.9-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index c4f58e24f0..bd2e21fa46 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-next.2", + "version": "0.5.12-next.0", "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 886803e1f0..f0f4f3361c 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,111 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## 0.2.11 + +### Patch Changes + +- 648b3d7: Add optional input to sentry:project:create to set the new Sentry project's platform +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/backend-plugin-api@1.4.1 + +## 0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.2.10 + +### Patch Changes + +- 6c6fb4a: Migrate to new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.2.10-next.2 + +### Patch Changes + +- 6c6fb4a: Migrate to new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + ## 0.2.9-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index c59a288861..35d746e1ed 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-next.2", + "version": "0.2.12-next.0", "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..98b3c8b599 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,14 @@ export function createSentryCreateProjectAction(options: { organizationSlug: string; teamSlug: string; name: string; - slug?: string; - authToken?: string; + slug?: string | undefined; + platform?: string | undefined; + authToken?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.test.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.test.ts index 3e31aef3bc..6fab15f59f 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.test.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.test.ts @@ -43,6 +43,7 @@ describe('sentry:project:create action', () => { teamSlug: string; name: string; slug?: string; + platform?: string; authToken?: string; }> => createMockActionContext({ @@ -56,12 +57,14 @@ describe('sentry:project:create action', () => { }, }); - it(`should ${examples[0].description}`, async () => { + it(`should ${examples[0].description} ${ + yaml.parse(examples[0].example).steps[2].name + }`, async () => { expect.assertions(3); let input; try { - input = yaml.parse(examples[0].example).steps[1].input; + input = yaml.parse(examples[0].example).steps[2].input; } catch (error) { console.error('Failed to parse YAML:', error); } @@ -97,7 +100,9 @@ describe('sentry:project:create action', () => { }); }); - it(`should ${examples[0].description}`, async () => { + it(`should ${examples[0].description} ${ + yaml.parse(examples[0].example).steps[0].name + }`, async () => { expect.assertions(3); let input; @@ -140,6 +145,51 @@ describe('sentry:project:create action', () => { }); }); + it(`should ${examples[0].description} ${ + yaml.parse(examples[0].example).steps[1].name + }`, async () => { + expect.assertions(3); + + let input; + try { + input = yaml.parse(examples[0].example).steps[1].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const action = createSentryCreateProjectAction(createScaffolderConfig()); + const actionContext = getActionContext(); + actionContext.input = { ...actionContext.input, platform: 'platform-a' }; + + worker.use( + http.post( + `https://sentry.io/api/0/teams/${input.organizationSlug}/${input.teamSlug}/projects/`, + async ({ request }) => { + expect(request.headers.get('Authorization')).toBe( + `Bearer c25711beb516e1e910d2ede554dc1bf725654ef3c75e5a9106de9aec13d5de85`, + ); + expect(request.headers.get('Content-Type')).toBe(`application/json`); + await expect(request.json()).resolves.toEqual({ + name: 'Scaffolded project A', + platform: 'platform-a', + }); + return HttpResponse.json( + { detail: 'project creation mocked result' }, + { status: 201 }, + ); + }, + ), + ); + + await action.handler({ + ...actionContext, + input: { + ...actionContext.input, + ...input, + }, + }); + }); + it(`should ${examples[1].description}`, async () => { expect.assertions(3); diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.ts index 3af88a4663..1744a995a9 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.ts @@ -38,7 +38,20 @@ export const examples: TemplateExample[] = [ { id: 'create-sentry-project', action: 'sentry:project:create', - name: 'Create a Sentry project without providing a project slug.', + name: 'Create a Sentry project with provided platform.', + input: { + organizationSlug: 'my-org', + teamSlug: 'team-a', + name: 'Scaffolded project A', + platform: 'platform-a', + authToken: + 'c25711beb516e1e910d2ede554dc1bf725654ef3c75e5a9106de9aec13d5de85', + }, + }, + { + id: 'create-sentry-project', + action: 'sentry:project:create', + name: 'Create a Sentry project without optional parameters.', input: { organizationSlug: 'my-org', teamSlug: 'team-b', diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.test.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.test.ts index 48c437d2a4..0279c198a9 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.test.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.test.ts @@ -42,6 +42,7 @@ describe('sentry:project:create action', () => { teamSlug: string; name: string; slug?: string; + platform?: string; authToken?: string; }> => createMockActionContext({ @@ -113,6 +114,37 @@ describe('sentry:project:create action', () => { await action.handler(actionContext); }); + it('should request sentry project create with added optional specified platform', async () => { + expect.assertions(3); + + const action = createSentryCreateProjectAction(createScaffolderConfig()); + const actionContext = getActionContext(); + actionContext.input = { ...actionContext.input, platform: 'platform-slug' }; + + worker.use( + http.post( + `https://sentry.io/api/0/teams/${actionContext.input.organizationSlug}/${actionContext.input.teamSlug}/projects/`, + async ({ request }) => { + expect(request.headers.get('Authorization')).toBe( + `Bearer ${actionContext.input.authToken}`, + ); + expect(request.headers.get('Content-Type')).toBe(`application/json`); + await expect(request.json()).resolves.toEqual({ + name: actionContext.input.name, + slug: actionContext.input.slug, + platform: actionContext.input.platform, + }); + return HttpResponse.json( + { detail: 'project creation mocked result' }, + { status: 201 }, + ); + }, + ), + ); + + await action.handler(actionContext); + }); + it('should take Sentry auth token from scaffolder config when input authToken is missing.', async () => { expect.assertions(3); diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts index 6c173deb59..57f8b349e0 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts @@ -31,46 +31,47 @@ 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(), + platform: z => + z + .string({ + description: 'Optional sentry platform for the new project. ', + }) + .optional(), + authToken: z => + z + .string({ + description: + 'authenticate via bearer auth token. Requires scope: project:write', + }) + .optional(), }, }, async handler(ctx) { - const { organizationSlug, teamSlug, name, slug, authToken } = ctx.input; + const { organizationSlug, teamSlug, name, slug, platform, authToken } = + ctx.input; const body: any = { name: name, @@ -80,6 +81,10 @@ export function createSentryCreateProjectAction(options: { config: Config }) { body.slug = slug; } + if (platform) { + body.platform = platform; + } + const token = authToken ? authToken : config.getOptionalString('scaffolder.sentry.token'); diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index a319388ed3..ea384447a2 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,112 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.4.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/plugin-scaffolder-node-test-utils@0.3.2-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/types@1.2.1 + +## 0.4.12 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node-test-utils@0.3.1 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/backend-plugin-api@1.4.1 + +## 0.4.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + - @backstage/plugin-scaffolder-node-test-utils@0.3.1-next.2 + +## 0.4.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.3.1-next.1 + +## 0.4.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node-test-utils@0.3.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/types@1.2.1 + +## 0.4.11 + +### Patch Changes + +- b392a34: Migrate to new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node-test-utils@0.3.0 + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/types@1.2.1 + +## 0.4.11-next.2 + +### Patch Changes + +- b392a34: Migrate to new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node-test-utils@0.3.0-next.2 + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/types@1.2.1 + +## 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 + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/plugin-scaffolder-node-test-utils@0.2.2 + - @backstage/types@1.2.1 + +## 0.4.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-node-test-utils@0.2.2-next.3 + ## 0.4.10-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 577e6735a0..e0aee18d22 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-next.2", + "version": "0.4.13-next.0", "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<string, any> | 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/.eslintrc.js b/plugins/scaffolder-backend/.eslintrc.js index 953af54f90..3c5416068f 100644 --- a/plugins/scaffolder-backend/.eslintrc.js +++ b/plugins/scaffolder-backend/.eslintrc.js @@ -5,13 +5,13 @@ module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { name: 'path', importNames: ['resolve'], message: - 'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-common` instead as it prevents security issues', + 'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-plugin-api` instead as it prevents security issues', }, ], restrictedSrcSyntax: [ { message: - 'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-common` instead as it prevents security issues', + 'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-plugin-api` instead as it prevents security issues', selector: 'MemberExpression[object.name="path"][property.name="resolve"]', }, ], diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index bfcea59479..81f1baf6b2 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,581 @@ # @backstage/plugin-scaffolder-backend +## 2.2.0-next.1 + +### Minor Changes + +- 2032660: Fixed fs:readdir action example +- 11dc90f: Implement max length for scaffolder auditor audit logging with default of 256 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.4-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.8.2-next.1 + +## 2.2.0-next.0 + +### Minor Changes + +- c08cbc4: Move Scaffolder API to OpenAPI +- 812485c: Add step info to scaffolder action context to access the step id and name. + +### Patch Changes + +- caee2eb: Fixed WinstonLogger throwing when redactions were null or undefined +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/backend-openapi-utils@0.6.0-next.0 + - @backstage/plugin-scaffolder-common@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.4-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.11-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.12-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.13-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.12-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.12-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.12-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.12-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.8.2-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.1 + - @backstage/plugin-permission-common@0.9.1 + +## 2.1.0 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- 424610a: Scaffolder audit Log now includes taskId and createdBy +- dbde180: An internal refactor which adds additional types to experimental checkpoints +- fc70b43: Replaced deprecated uses of `@backstage/backend-common` with the equivalents in `@backstage/backend-defaults` and `@backstage/backend-plugin-api`. +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-defaults@0.11.1 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/integration@1.17.1 + - @backstage/plugin-scaffolder-backend-module-github@0.8.1 + - @backstage/plugin-scaffolder-common@1.6.0 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-bitbucket-cloud-common@0.3.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.10 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-events-node@0.4.13 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.11 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.12 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.11 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.11 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.3 + +## 2.1.0-next.2 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.6.0-next.1 + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.10-next.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.11-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.12-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.2 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.11-next.2 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.11-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.8.1-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.3-next.2 + +## 2.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.11-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.12-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.11-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.11-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.8.1-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.3-next.1 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-scaffolder-common@1.5.12-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.10-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 2.0.1-next.0 + +### Patch Changes + +- dbde180: An internal refactor which adds additional types to experimental checkpoints +- fc70b43: Replaced deprecated uses of `@backstage/backend-common` with the equivalents in `@backstage/backend-defaults` and `@backstage/backend-plugin-api`. +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.8.1-next.0 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.12-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.11-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.11-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.11-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.11-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.3-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.11-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 2.0.0 + +### 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. + +- 5863b04: **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, + }), + ); + }, + }); + }, + }); + ``` + +### 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 + +- 89a941d: Migrating to latest action format +- 023629e: Enable usage of secrets within 'each' step of software templates. For example, you can now structure your `each` step like this: + + ``` + each: + [ + { name: "Service1", token: "${{ secrets.token1 }}" }, + { name: "Service2", token: "${{ secrets.token2 }}" }, + ] + ``` + +- e92e481: Add tests for Scaffolder +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.10 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10 + - @backstage/plugin-scaffolder-backend-module-github@0.8.0 + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.10 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.10 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.11 + - @backstage/backend-plugin-api@1.4.0 + - @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 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 2.0.0-next.2 + +### Major Changes + +- 5863b04: **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, + }), + ); + }, + }); + }, + }); + ``` + +### Patch Changes + +- 89a941d: Migrating to latest action format +- 023629e: Enable usage of secrets within 'each' step of software templates. For example, you can now structure your `each` step like this: + + ``` + each: + [ + { name: "Service1", token: "${{ secrets.token1 }}" }, + { name: "Service2", token: "${{ secrets.token2 }}" }, + ] + ``` + +- e92e481: Add tests for Scaffolder +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.2-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.8.0-next.2 + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @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-auth-node@0.6.4-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9-next.1 + - @backstage/plugin-catalog-node@1.17.1-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-backend-module-azure@0.2.10-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.11-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.2 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.10-next.2 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.10-next.2 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 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 + +- 587cb05: Added `workspace:template` and `workspace:template:file` actions to complement respective `fetch:*` actions + +### Patch Changes + +- eb39388: Fixed bug in fs:delete that prevented wildcard patterns from matching paths starting with "." +- 36ae651: Fixing a bug where the name for `templatingExtensions` was incorrectly set to `templateExtensions` +- 72d019d: Removed various typos +- ec42f8e: Generating new tokens on each Scaffolder Task Retry +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.0 + - @backstage/plugin-scaffolder-backend-module-github@0.7.1 + - @backstage/backend-defaults@0.10.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.10 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.9 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.9 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.9 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8 + - @backstage/plugin-events-node@0.4.11 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 1.33.0-next.3 + +### Patch Changes + +- ec42f8e: Generating new tokens on each Scaffolder Task Retry +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.1-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.2 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-events-node@0.4.11-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.9-next.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.10-next.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.3 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.9-next.3 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.9-next.3 + - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.3 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + ## 1.33.0-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend/config.d.ts b/plugins/scaffolder-backend/config.d.ts index ae91c905b3..b4fb532901 100644 --- a/plugins/scaffolder-backend/config.d.ts +++ b/plugins/scaffolder-backend/config.d.ts @@ -93,5 +93,20 @@ export interface Config { * Default value is 24 hours. */ taskTimeout?: HumanDuration | string; + + /** + * Sets the maximum length for task parameters recorded by the auditor. + * + * If set to -1, the limit is disabled and parameters are not truncated. + * Defaults to 256 character length. + * + * @example + * scaffolder: + * auditor: + * taskParameterMaxLength: 512 + */ + auditor?: { + taskParameterMaxLength?: number; + }; }; } diff --git a/plugins/scaffolder-backend/knip-report.md b/plugins/scaffolder-backend/knip-report.md index 51ea7441f5..b6fcf59bb3 100644 --- a/plugins/scaffolder-backend/knip-report.md +++ b/plugins/scaffolder-backend/knip-report.md @@ -1,13 +1,23 @@ # Knip report -## Unused dependencies (4) +## Unused dependencies (14) -| Name | Location | Severity | -| :--------------------------------------- | :----------- | :------- | -| @backstage/plugin-bitbucket-cloud-common | package.json | error | -| concat-stream | package.json | error | -| p-limit | package.json | error | -| tar | package.json | error | +| Name | Location | Severity | +| :--------------------------------------------------------------- | :----------- | :------- | +| @backstage/plugin-catalog-backend-module-scaffolder-entity-model | package.json | error | +| @backstage/plugin-scaffolder-backend-module-bitbucket-server | package.json | error | +| @backstage/plugin-scaffolder-backend-module-bitbucket-cloud | package.json | error | +| @backstage/plugin-scaffolder-backend-module-bitbucket | package.json | error | +| @backstage/plugin-scaffolder-backend-module-gerrit | package.json | error | +| @backstage/plugin-scaffolder-backend-module-github | package.json | error | +| @backstage/plugin-scaffolder-backend-module-gitlab | package.json | error | +| @backstage/plugin-scaffolder-backend-module-azure | package.json | error | +| @backstage/plugin-scaffolder-backend-module-gitea | package.json | error | +| @backstage/plugin-bitbucket-cloud-common | package.json | error | +| @backstage/plugin-auth-node | package.json | error | +| concat-stream | package.json | error | +| p-limit | package.json | error | +| tar | package.json | error | ## Unused devDependencies (1) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 8a36adfd7a..4caaedc4bd 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-next.2", + "version": "2.2.0-next.1", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin", @@ -54,6 +54,7 @@ "build": "backstage-cli package build", "build:assets": "node scripts/build-nunjucks.js", "clean": "backstage-cli package clean", + "generate": "backstage-repo-tools package schema openapi generate --server --client-package plugins/scaffolder-common", "lint": "backstage-cli package lint", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", @@ -61,10 +62,9 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "^0.25.0", "@backstage/backend-defaults": "workspace:^", + "@backstage/backend-openapi-utils": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", @@ -88,11 +88,9 @@ "@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", - "express-promise-router": "^4.1.0", "fs-extra": "^11.2.0", "globby": "^11.0.0", "isbinaryfile": "^5.0.0", @@ -122,6 +120,8 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", + "@backstage/repo-tools": "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..966d71acaa 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'; @@ -11,6 +10,8 @@ import { PermissionCondition } from '@backstage/plugin-permission-common'; import { PermissionCriteria } from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; import { ResourcePermission } from '@backstage/plugin-permission-common'; +import { SerializedTask } from '@backstage/plugin-scaffolder-node'; +import { TaskFilter } from '@backstage/plugin-scaffolder-node'; import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common'; @@ -20,16 +21,18 @@ export const createScaffolderActionConditionalDecision: ( conditions: PermissionCriteria<PermissionCondition<'scaffolder-action'>>, ) => ConditionalPolicyDecision; +// @alpha (undocumented) +export const createScaffolderTaskConditionalDecision: ( + permission: ResourcePermission<'scaffolder-task'>, + conditions: PermissionCriteria<PermissionCondition<'scaffolder-task'>>, +) => ConditionalPolicyDecision; + // @alpha export const createScaffolderTemplateConditionalDecision: ( permission: ResourcePermission<'scaffolder-template'>, conditions: PermissionCriteria<PermissionCondition<'scaffolder-template'>>, ) => ConditionalPolicyDecision; -// @alpha (undocumented) -const _feature: BackendFeature; -export default _feature; - // @alpha export const scaffolderActionConditions: Conditions<{ hasActionId: PermissionRule< @@ -81,6 +84,18 @@ export const scaffolderActionConditions: Conditions<{ >; }>; +// @alpha +export const scaffolderTaskConditions: Conditions<{ + isTaskOwner: PermissionRule< + SerializedTask, + TaskFilter, + 'scaffolder-task', + { + createdBy: string[]; + } + >; +}>; + // @alpha export const scaffolderTemplateConditions: Conditions<{ hasTag: PermissionRule< diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index 0438922de3..918718e495 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -3,84 +3,46 @@ > 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 { PermissionCriteria } from '@backstage/plugin-permission-common'; 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 { TaskFilters } 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 { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node/alpha'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; -// @public @deprecated (undocumented) -export type ActionContext<TInput extends JsonObject> = ActionContext_2<TInput>; - // @public (undocumented) export type ActionPermissionRuleInput< TParams extends PermissionRuleParams = PermissionRuleParams, @@ -91,60 +53,63 @@ export type ActionPermissionRuleInput< TParams >; -// @public -export const createBuiltinActions: ( - options: CreateBuiltInActionsOptions, -) => TemplateAction_2[]; - -// @public -export interface CreateBuiltInActionsOptions { - additionalTemplateFilters?: Record<string, TemplateFilter_2>; - // (undocumented) - additionalTemplateGlobals?: Record<string, TemplateGlobal_2>; - 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<string, any>; filePath?: string | undefined; }, - any, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public -export function createDebugLogAction(): TemplateAction_2<any, any, 'v1'>; +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 +128,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<string, TemplateFilter_2>; - additionalTemplateGlobals?: Record<string, TemplateGlobal_2>; -}): TemplateAction_2< + additionalTemplateFilters?: Record<string, TemplateFilter>; + additionalTemplateGlobals?: Record<string, TemplateGlobal>; +}): 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<string, any> | 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<string, TemplateFilter_2>; - additionalTemplateGlobals?: Record<string, TemplateGlobal_2>; -}): TemplateAction_2< + additionalTemplateFilters?: Record<string, TemplateFilter>; + additionalTemplateGlobals?: Record<string, TemplateGlobal>; +}): TemplateAction< { url: string; targetPath: string; - values: any; - cookiecutterCompat?: boolean; - replace?: boolean; - trimBlocks?: boolean; - lstripBlocks?: boolean; - token?: string; + values?: Record<string, any> | 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 +234,51 @@ 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<express.Router>; - -// @public @deprecated (undocumented) -export const createTemplateAction: typeof createTemplateAction_2; - // @public export function createWaitAction(options?: { maxWaitTime?: Duration | HumanDuration; -}): TemplateAction_2<HumanDuration, JsonObject, 'v1'>; +}): 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<string, TemplateFilter_2>; + config?: Config; + additionalTemplateFilters?: Record<string, TemplateFilter>; concurrentTasksLimit?: number; - additionalTemplateGlobals?: Record<string, TemplateGlobal_2>; + additionalTemplateGlobals?: Record<string, TemplateGlobal>; permissions?: PermissionEvaluator; gracefulShutdown?: boolean; }; @@ -409,14 +286,14 @@ export type CreateWorkerOptions = { // @public export interface CurrentClaimedTask { createdBy?: string; - secrets?: TaskSecrets_2; + secrets?: TaskSecrets; spec: TaskSpec; state?: JsonObject; taskId: string; workspace?: Promise<Buffer>; } -// @public +// @public @deprecated export class DatabaseTaskStore implements TaskStore { // (undocumented) cancelTask( @@ -427,13 +304,13 @@ export class DatabaseTaskStore implements TaskStore { >, ): Promise<void>; // (undocumented) - claimTask(): Promise<SerializedTask_2 | undefined>; + claimTask(): Promise<SerializedTask | undefined>; // (undocumented) cleanWorkspace({ taskId }: { taskId: string }): Promise<void>; // (undocumented) completeTask(options: { taskId: string; - status: TaskStatus_2; + status: TaskStatus; eventBody: JsonObject; }): Promise<void>; // (undocumented) @@ -451,7 +328,7 @@ export class DatabaseTaskStore implements TaskStore { >, ): Promise<void>; // (undocumented) - getTask(taskId: string): Promise<SerializedTask_2>; + getTask(taskId: string): Promise<SerializedTask>; // (undocumented) getTaskState({ taskId }: { taskId: string }): Promise< | { @@ -464,10 +341,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; @@ -477,13 +354,14 @@ export class DatabaseTaskStore implements TaskStore { order: 'asc' | 'desc'; field: string; }[]; + permissionFilters?: PermissionCriteria<TaskFilters>; }): 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,7 +380,7 @@ export class DatabaseTaskStore implements TaskStore { targetPath: string; }): Promise<void>; // (undocumented) - retryTask?(options: { taskId: string }): Promise<void>; + retryTask?(options: { secrets?: TaskSecrets; taskId: string }): Promise<void>; // (undocumented) saveTaskState(options: { taskId: string; state?: JsonObject }): Promise<void>; // (undocumented) @@ -511,121 +389,30 @@ export class DatabaseTaskStore implements TaskStore { shutdownTask(options: TaskStoreShutDownTaskOptions): Promise<void>; } -// @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<any, any, any>[]; - // (undocumented) - additionalTemplateFilters?: - | Record<string, TemplateFilter_2> - | CreatedTemplateFilter<any, any>[]; - // (undocumented) - additionalTemplateGlobals?: - | Record<string, TemplateGlobal_2> - | CreatedTemplateGlobal[]; - // (undocumented) - additionalWorkspaceProviders?: Record<string, WorkspaceProvider>; - // (undocumented) - auditor?: AuditorService; - // (undocumented) - auth?: AuthService; - // (undocumented) - autocompleteHandlers?: Record<string, AutocompleteHandler>; - // (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<void>; // (undocumented) - complete(result: TaskCompletionState_2, metadata?: JsonObject): Promise<void>; + complete(result: TaskCompletionState, metadata?: JsonObject): Promise<void>; // (undocumented) static create( task: CurrentClaimedTask, storage: TaskStore, abortSignal: AbortSignal, - logger: Logger, + logger: LoggerService, auth?: AuthService, config?: Config, additionalWorkspaceProviders?: Record<string, WorkspaceProvider>, @@ -653,34 +440,18 @@ export class TaskManager implements TaskContext_2 { targetPath: string; }): Promise<void>; // (undocumented) - get secrets(): TaskSecrets_2 | undefined; + get secrets(): TaskSecrets | undefined; // (undocumented) serializeWorkspace?(options: { path: string }): Promise<void>; // (undocumented) get spec(): TaskSpecV1beta3; // (undocumented) - updateCheckpoint?( - options: - | { - key: string; - status: 'success'; - value: JsonValue; - } - | { - key: string; - status: 'failed'; - reason: string; - }, - ): Promise<void>; + get taskId(): string; + // (undocumented) + updateCheckpoint?(options: UpdateTaskCheckpointOptions): Promise<void>; } -// @public @deprecated (undocumented) -export type TaskSecrets = TaskSecrets_2; - // @public @deprecated -export type TaskStatus = TaskStatus_2; - -// @public export interface TaskStore { // (undocumented) cancelTask?(options: TaskStoreEmitOptions): Promise<void>; @@ -721,6 +492,7 @@ export interface TaskStore { limit?: number; offset?: number; }; + permissionFilters?: PermissionCriteria<TaskFilters>; order?: { order: 'asc' | 'desc'; field: string; @@ -769,7 +541,7 @@ export interface TaskStore { targetPath: string; }): Promise<void>; // (undocumented) - retryTask?(options: { taskId: string }): Promise<void>; + retryTask?(options: { secrets?: TaskSecrets; taskId: string }): Promise<void>; // (undocumented) saveTaskState?(options: { taskId: string; @@ -787,42 +559,42 @@ export interface TaskStore { shutdownTask?(options: TaskStoreShutDownTaskOptions): Promise<void>; } -// @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<TBody = JsonObject> = { 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<TaskWorker>; @@ -831,33 +603,23 @@ export class TaskWorker { // (undocumented) recoverTasks(): Promise<void>; // (undocumented) - runOneTask(task: TaskContext_2): Promise<void>; + runOneTask(task: TaskContext): Promise<void>; // (undocumented) start(): void; // (undocumented) stop(): Promise<void>; } -// @public @deprecated (undocumented) -export type TemplateAction<TInput extends JsonObject> = - TemplateAction_2<TInput>; - -// @public +// @public @deprecated export class TemplateActionRegistry { // (undocumented) - get(actionId: string): TemplateAction_2<any, any, any>; + get(actionId: string): TemplateAction<any, any, any>; // (undocumented) - list(): TemplateAction_2<any, any, any>[]; + list(): TemplateAction<any, any, any>[]; // (undocumented) - register(action: TemplateAction_2<any, any, any>): void; + register(action: TemplateAction<any, any, any>): 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/scripts/Cookiecutter.dockerfile b/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile index fb6f3a1f50..d3b56cdacb 100644 --- a/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile +++ b/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.21 +FROM alpine:3.22 RUN apk add --update \ git \ diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.test.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.test.ts new file mode 100644 index 0000000000..f3b3456e79 --- /dev/null +++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.test.ts @@ -0,0 +1,1267 @@ +/* + * 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 { IncomingMessage } from 'http'; +import request from 'supertest'; +import waitForExpect from 'wait-for-expect'; + +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { + mockCredentials, + mockServices, + startTestBackend, +} from '@backstage/backend-test-utils'; +import { stringifyEntityRef } from '@backstage/catalog-model'; +import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; +import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { scaffolderAutocompleteExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; + +import { scaffolderPlugin } from './ScaffolderPlugin'; + +describe('scaffolderPlugin', () => { + const mockTemplateEntity = { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { + description: 'Create a new CRA website project', + name: 'create-react-app-template', + tags: ['experimental', 'react', 'cra'], + title: 'Create React App Template', + annotations: { + 'backstage.io/managed-by-location': 'url:https://dev.azure.com', + }, + }, + spec: { + owner: 'web@example.com', + type: 'website', + steps: [ + { + id: 'step-one', + name: 'First log', + action: 'debug:log', + input: { + message: 'hello', + }, + }, + { + id: 'step-two', + name: 'Second log', + action: 'debug:log', + input: { + message: 'world', + }, + 'backstage:permissions': { + tags: ['steps-tag'], + }, + }, + ], + parameters: [ + { + type: 'object', + required: ['requiredParameter1'], + properties: { + requiredParameter1: { + type: 'string', + description: 'Required parameter 1', + }, + }, + }, + { + type: 'object', + required: ['requiredParameter2'], + 'backstage:permissions': { + tags: ['parameters-tag'], + }, + properties: { + requiredParameter2: { + type: 'string', + description: 'Required parameter 2', + }, + }, + }, + ], + }, + } satisfies TemplateEntityV1beta3; + + it('supports fetching template parameters schema', async () => { + const { server } = await startTestBackend({ + features: [ + scaffolderPlugin, + catalogServiceMock.factory({ + entities: [mockTemplateEntity], + }), + ], + }); + + const { body, status } = await request(server).get( + '/api/scaffolder/v2/templates/default/template/create-react-app-template/parameter-schema', + ); + + expect(status).toBe(200); + + expect(body).toMatchObject({ + description: 'Create a new CRA website project', + steps: [ + { + schema: { + properties: { + requiredParameter1: { + description: 'Required parameter 1', + type: 'string', + }, + }, + required: ['requiredParameter1'], + type: 'object', + }, + title: 'Please enter the following information', + }, + { + schema: { + 'backstage:permissions': { tags: ['parameters-tag'] }, + properties: { + requiredParameter2: { + description: 'Required parameter 2', + type: 'string', + }, + }, + required: ['requiredParameter2'], + type: 'object', + }, + title: 'Please enter the following information', + }, + ], + title: 'Create React App Template', + }); + }); + + it('supports listing actions', async () => { + const { server } = await startTestBackend({ + features: [scaffolderPlugin], + }); + + const { body, status } = await request(server).get( + '/api/scaffolder/v2/actions', + ); + + expect(status).toBe(200); + expect(body).toBeInstanceOf(Array); + + const actionSchema = { + id: expect.any(String), + description: expect.any(String), + examples: expect.any(Array), + schema: expect.any(Object), + }; + + // General cursory check + body.forEach((action: any) => expect(action).toMatchObject(actionSchema)); + + expect(body).toContainEqual({ + id: 'fetch:plain', + description: + 'Downloads content and places it in the workspace, or optionally in a subdirectory specified by the `targetPath` input option.', + examples: [ + { + description: 'Downloads content and places it in the workspace.', + example: + 'steps:\n - action: fetch:plain\n id: fetch-plain\n name: Fetch plain\n input:\n url: https://github.com/backstage/community/tree/main/backstage-community-sessions/assets\n', + }, + { + description: + 'Optionally, if you would prefer the data to be downloaded to a subdirectory in the workspace you may specify the ‘targetPath’ input option.', + example: + 'steps:\n - action: fetch:plain\n id: fetch-plain\n name: Fetch plain\n input:\n url: https://github.com/backstage/community/tree/main/backstage-community-sessions/assets\n targetPath: fetched-data\n', + }, + ], + schema: { + input: { + type: 'object', + properties: { + url: { + type: 'string', + description: + 'Relative path or absolute URL pointing to the directory tree to fetch', + }, + targetPath: { + type: 'string', + description: + 'Target path within the working directory to download the contents to.', + }, + token: { + type: 'string', + description: + 'An optional token to use for authentication when reading the resources.', + }, + }, + required: ['url'], + additionalProperties: false, + $schema: 'http://json-schema.org/draft-07/schema#', + }, + }, + }); + }); + + it('supports listing tasks', async () => { + const { server } = await startTestBackend({ + features: [scaffolderPlugin], + }); + + const { body, status } = await request(server).get( + '/api/scaffolder/v2/tasks', + ); + + expect(status).toBe(200); + expect(body).toMatchObject({ tasks: [], totalTasks: 0 }); + }); + + it('rejects creating tasks if template schema definition mismatches', async () => { + const { server } = await startTestBackend({ + features: [ + scaffolderPlugin, + catalogServiceMock.factory({ + entities: [mockTemplateEntity], + }), + ], + }); + + // Confirm no tasks are present + let response = await request(server).get('/api/scaffolder/v2/tasks'); + + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ tasks: [], totalTasks: 0 }); + + // Create a task + response = await request(server) + .post('/api/scaffolder/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + storePath: 'https://github.com/backstage/backstage', + }, + }); + + expect(response.status).toBe(400); + }); + + it('supports creating tasks', async () => { + const { server } = await startTestBackend({ + features: [ + scaffolderPlugin, + catalogServiceMock.factory({ + entities: [mockTemplateEntity], + }), + ], + }); + + // Confirm no tasks are present + let response = await request(server).get('/api/scaffolder/v2/tasks'); + + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ tasks: [], totalTasks: 0 }); + + // Create a task + response = await request(server) + .post('/api/scaffolder/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + }); + + expect(response.status).toBe(201); + expect(response.body).toMatchObject({ id: expect.any(String) }); + + const { id: taskId } = response.body; + + // Wait for task to complete + await waitForExpect(async () => { + response = await request(server).get( + `/api/scaffolder/v2/tasks/${taskId}`, + ); + + expect(response.body).toMatchObject({ + status: 'completed', + }); + }); + + response = await request(server).get('/api/scaffolder/v2/tasks'); + + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ + tasks: expect.any(Array), + totalTasks: 1, + }); + + const { tasks } = response.body; + expect(tasks.length).toBe(1); + expect(tasks).toContainEqual({ + createdAt: expect.any(String), + createdBy: 'user:default/mock', + id: taskId, + lastHeartbeatAt: expect.any(String), + spec: { + apiVersion: 'scaffolder.backstage.io/v1beta3', + output: {}, + parameters: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + steps: [ + { + action: 'debug:log', + id: 'step-one', + input: { message: 'hello' }, + name: 'First log', + }, + { + action: 'debug:log', + 'backstage:permissions': { tags: ['steps-tag'] }, + id: 'step-two', + input: { message: 'world' }, + name: 'Second log', + }, + ], + templateInfo: { + baseUrl: 'https://dev.azure.com', + entity: { + metadata: { + annotations: { + 'backstage.io/managed-by-location': 'url:https://dev.azure.com', + }, + description: 'Create a new CRA website project', + name: 'create-react-app-template', + tags: ['experimental', 'react', 'cra'], + title: 'Create React App Template', + }, + }, + entityRef: 'template:default/create-react-app-template', + }, + user: { ref: 'user:default/mock' }, + }, + status: 'completed', + }); + }); + + it('emits auditlog containing user identifier when backstage auth is passed', async () => { + const mockLogger = mockServices.logger.mock(); + mockLogger.child.mockReturnValue(mockLogger); + const loggerSpy = jest.spyOn(mockLogger, 'info'); + + const { server } = await startTestBackend({ + features: [ + scaffolderPlugin, + catalogServiceMock.factory({ + entities: [mockTemplateEntity], + }), + mockLogger.factory, + ], + }); + + // Confirm no tasks are present + let response = await request(server).get('/api/scaffolder/v2/tasks'); + + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ tasks: [], totalTasks: 0 }); + + // Create a task + response = await request(server) + .post('/api/scaffolder/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + }); + + expect(response.status).toBe(201); + expect(response.body).toMatchObject({ id: expect.any(String) }); + + const { id: taskId } = response.body; + + // Wait for task to complete + await waitForExpect(async () => { + response = await request(server).get( + `/api/scaffolder/v2/tasks/${taskId}`, + ); + + expect(response.body).toMatchObject({ + status: 'completed', + }); + }); + + expect(loggerSpy).toHaveBeenCalledTimes(10); + expect(loggerSpy).toHaveBeenNthCalledWith( + 3, + 'Scaffolding task for template:default/create-react-app-template created by user:default/mock', + ); + }); + + it('supports fetching a task by ID', async () => { + const { server } = await startTestBackend({ + features: [ + scaffolderPlugin, + catalogServiceMock.factory({ + entities: [mockTemplateEntity], + }), + ], + }); + + // Confirm no tasks are present + let response = await request(server).get('/api/scaffolder/v2/tasks'); + + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ tasks: [], totalTasks: 0 }); + + // Create a task to lookup + response = await request(server) + .post('/api/scaffolder/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + }); + + expect(response.status).toBe(201); + expect(response.body).toMatchObject({ id: expect.any(String) }); + + const { id: taskId } = response.body; + + // Wait for task to complete + await waitForExpect(async () => { + response = await request(server).get( + `/api/scaffolder/v2/tasks/${taskId}`, + ); + + expect(response.body).toMatchObject({ + status: 'completed', + }); + }); + + // Get the task by ID + response = await request(server).get(`/api/scaffolder/v2/tasks/${taskId}`); + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ + createdAt: expect.any(String), + createdBy: 'user:default/mock', + id: taskId, + lastHeartbeatAt: expect.any(String), + spec: { + apiVersion: 'scaffolder.backstage.io/v1beta3', + output: {}, + parameters: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + steps: [ + { + action: 'debug:log', + id: 'step-one', + input: { message: 'hello' }, + name: 'First log', + }, + { + action: 'debug:log', + 'backstage:permissions': { tags: ['steps-tag'] }, + id: 'step-two', + input: { message: 'world' }, + name: 'Second log', + }, + ], + templateInfo: { + baseUrl: 'https://dev.azure.com', + entity: { + metadata: { + annotations: { + 'backstage.io/managed-by-location': 'url:https://dev.azure.com', + }, + description: 'Create a new CRA website project', + name: 'create-react-app-template', + tags: ['experimental', 'react', 'cra'], + title: 'Create React App Template', + }, + }, + entityRef: 'template:default/create-react-app-template', + }, + user: { + ref: 'user:default/mock', + }, + }, + status: 'completed', + }); + expect(response.body.secrets).toBeUndefined(); + }); + + it('supports listing tasks using a filter', async () => { + const { server } = await startTestBackend({ + features: [ + scaffolderPlugin, + catalogServiceMock.factory({ + entities: [mockTemplateEntity], + }), + ], + }); + + // Confirm no tasks are present + let response = await request(server).get('/api/scaffolder/v2/tasks'); + + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ tasks: [], totalTasks: 0 }); + + // Create a task to lookup + response = await request(server) + .post('/api/scaffolder/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + }); + + expect(response.status).toBe(201); + expect(response.body).toMatchObject({ id: expect.any(String) }); + + const { id: taskId } = response.body; + + // Wait for task to complete + await waitForExpect(async () => { + response = await request(server).get( + `/api/scaffolder/v2/tasks/${taskId}`, + ); + + expect(response.body).toMatchObject({ + status: 'completed', + }); + }); + + // Confirm non-matching `createdBy` filter does not return any results + let { body, status } = await request(server).get( + '/api/scaffolder/v2/tasks?createdBy=user:default/foo&status=completed&limit=1&offset=0&order=desc:created_at', + ); + + expect(status).toBe(200); + expect(body).toMatchObject({ tasks: [], totalTasks: 0 }); + + // Confirm matching `createdBy` filter returns the expected result + ({ body, status } = await request(server).get( + '/api/scaffolder/v2/tasks?createdBy=user:default/mock&status=completed&limit=1&offset=0&order=desc:created_at', + )); + + expect(status).toBe(200); + expect(body).toMatchObject({ + tasks: [ + { + createdAt: expect.any(String), + createdBy: 'user:default/mock', + id: taskId, + lastHeartbeatAt: expect.any(String), + spec: { + apiVersion: 'scaffolder.backstage.io/v1beta3', + output: {}, + parameters: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + steps: [ + { + action: 'debug:log', + id: 'step-one', + input: { message: 'hello' }, + name: 'First log', + }, + { + action: 'debug:log', + 'backstage:permissions': { tags: ['steps-tag'] }, + id: 'step-two', + input: { message: 'world' }, + name: 'Second log', + }, + ], + templateInfo: { + baseUrl: 'https://dev.azure.com', + entity: { + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://dev.azure.com', + }, + description: 'Create a new CRA website project', + name: 'create-react-app-template', + tags: ['experimental', 'react', 'cra'], + title: 'Create React App Template', + }, + }, + entityRef: 'template:default/create-react-app-template', + }, + user: { ref: 'user:default/mock' }, + }, + status: 'completed', + }, + ], + totalTasks: 1, + }); + }); + + it('supports canceling a task by ID', async () => { + const { server } = await startTestBackend({ + features: [ + scaffolderPlugin, + catalogServiceMock.factory({ + entities: [mockTemplateEntity], + }), + ], + }); + + // Confirm no tasks are present + let response = await request(server).get('/api/scaffolder/v2/tasks'); + + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ tasks: [], totalTasks: 0 }); + + // Create a task to cancel + response = await request(server) + .post('/api/scaffolder/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + }); + + expect(response.status).toBe(201); + expect(response.body).toMatchObject({ id: expect.any(String) }); + + const { id: taskId } = response.body; + + // Wait for task to complete + await waitForExpect(async () => { + response = await request(server).get( + `/api/scaffolder/v2/tasks/${taskId}`, + ); + + expect(response.body).toMatchObject({ + status: 'completed', + }); + }); + + // Cancel the task by ID + response = await request(server).post( + `/api/scaffolder/v2/tasks/${taskId}/cancel`, + ); + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ + status: 'cancelled', + }); + }); + + it('supports retrying a task by ID', async () => { + const { server } = await startTestBackend({ + features: [ + scaffolderPlugin, + catalogServiceMock.factory({ + entities: [mockTemplateEntity], + }), + ], + }); + + // Confirm no tasks are present + let response = await request(server).get('/api/scaffolder/v2/tasks'); + + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ tasks: [], totalTasks: 0 }); + + // Create a task to retry + response = await request(server) + .post('/api/scaffolder/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + }); + + expect(response.status).toBe(201); + expect(response.body).toMatchObject({ id: expect.any(String) }); + + const { id: taskId } = response.body; + + // Wait for task to complete + await waitForExpect(async () => { + response = await request(server).get( + `/api/scaffolder/v2/tasks/${taskId}`, + ); + + expect(response.body).toMatchObject({ + status: 'completed', + }); + }); + + // Retry the task by ID + response = await request(server) + .post(`/api/scaffolder/v2/tasks/${taskId}/retry`) + .send({}); + + expect(response.status).toBe(201); + expect(response.body).toMatchObject({ id: taskId }); + + // Wait for task to complete + await waitForExpect(async () => { + response = await request(server).get( + `/api/scaffolder/v2/tasks/${taskId}`, + ); + + expect(response.body).toMatchObject({ + status: 'completed', + }); + }); + }); + + it('supports fetching event stream for a task', async () => { + const { server } = await startTestBackend({ + features: [ + scaffolderPlugin, + catalogServiceMock.factory({ + entities: [mockTemplateEntity], + }), + ], + }); + + // Confirm no tasks are present + let response = await request(server).get('/api/scaffolder/v2/tasks'); + + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ tasks: [], totalTasks: 0 }); + + // Create a task to fetch event stream for + response = await request(server) + .post('/api/scaffolder/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + }); + + expect(response.status).toBe(201); + expect(response.body).toMatchObject({ id: expect.any(String) }); + + const { id: taskId } = response.body; + + let statusCode: IncomingMessage['statusCode'] = undefined; + let headers: IncomingMessage['headers'] = {}; + const responseDataFn = jest.fn(); + + // Get event stream for the task + const req = request(server) + .get(`/api/scaffolder/v2/tasks/${taskId}/eventstream`) + .set('accept', 'text/event-stream') + .parse((res, _) => { + ({ statusCode, headers } = res as unknown as IncomingMessage); + + res.on('data', chunk => { + responseDataFn(chunk.toString()); + + // the server expects the client to abort the request + if (chunk.includes('completion')) { + req.abort(); + } + }); + }); + + // wait for the request to finish + await req.catch(() => { + // ignore 'aborted' error + }); + + expect(statusCode).toBe(200); + expect(headers['content-type']).toBe('text/event-stream'); + expect(responseDataFn).toHaveBeenCalledTimes(10); + expect(responseDataFn).toHaveBeenLastCalledWith( + expect.stringContaining(taskId), + ); + expect(responseDataFn).toHaveBeenLastCalledWith( + expect.stringContaining('event: completion'), + ); + }); + + it('supports fetching event stream for a task with after query', async () => { + const { server } = await startTestBackend({ + features: [ + scaffolderPlugin, + catalogServiceMock.factory({ + entities: [mockTemplateEntity], + }), + ], + }); + + // Confirm no tasks are present + let response = await request(server).get('/api/scaffolder/v2/tasks'); + + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ tasks: [], totalTasks: 0 }); + + // Create a task to fetch event stream for + response = await request(server) + .post('/api/scaffolder/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + }); + + expect(response.status).toBe(201); + expect(response.body).toMatchObject({ id: expect.any(String) }); + + const { id: taskId } = response.body; + + let statusCode: IncomingMessage['statusCode'] = undefined; + let headers: IncomingMessage['headers'] = {}; + const responseDataFn = jest.fn(); + + // Get event stream for the task + const req = request(server) + .get(`/api/scaffolder/v2/tasks/${taskId}/eventstream`) + .query({ after: 8 }) // out of 10 events + .set('accept', 'text/event-stream') + .parse((res, _) => { + ({ statusCode, headers } = res as unknown as IncomingMessage); + + res.on('data', chunk => { + responseDataFn(chunk.toString()); + + // the server expects the client to abort the request + if (chunk.includes('completion')) { + req.abort(); + } + }); + }); + + // wait for the request to finish + await req.catch(() => { + // ignore 'aborted' error + }); + + expect(statusCode).toBe(200); + expect(headers['content-type']).toBe('text/event-stream'); + expect(responseDataFn).toHaveBeenCalledTimes(2); + expect(responseDataFn).toHaveBeenNthCalledWith( + 1, + expect.stringContaining(taskId), + ); + expect(responseDataFn).toHaveBeenNthCalledWith( + 1, + expect.stringContaining('event: log'), + ); + expect(responseDataFn).toHaveBeenLastCalledWith( + expect.stringContaining(taskId), + ); + expect(responseDataFn).toHaveBeenLastCalledWith( + expect.stringContaining('event: completion'), + ); + }); + + it('supports fetching events for a task', async () => { + const { server } = await startTestBackend({ + features: [ + scaffolderPlugin, + catalogServiceMock.factory({ + entities: [mockTemplateEntity], + }), + ], + }); + + // Confirm no tasks are present + let response = await request(server).get('/api/scaffolder/v2/tasks'); + + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ tasks: [], totalTasks: 0 }); + + // Create a task to fetch events for + response = await request(server) + .post('/api/scaffolder/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + }); + + expect(response.status).toBe(201); + expect(response.body).toMatchObject({ id: expect.any(String) }); + + const { id: taskId } = response.body; + + // Wait for task to complete + await waitForExpect(async () => { + response = await request(server).get( + `/api/scaffolder/v2/tasks/${taskId}`, + ); + + expect(response.body).toMatchObject({ + status: 'completed', + }); + }); + + // Get events for the task + response = await request(server).get( + `/api/scaffolder/v2/tasks/${taskId}/events`, + ); + + expect(response.status).toEqual(200); + expect(response.body).toHaveLength(10); + expect(response.body).toContainEqual({ + body: { message: 'Starting up task with 2 steps' }, + id: 1, + isTaskRecoverable: false, + taskId: taskId, + type: 'log', + createdAt: expect.any(String), + }); + expect(response.body).toContainEqual({ + body: { message: 'Run completed with status: completed', output: {} }, + id: 10, + isTaskRecoverable: false, + taskId: taskId, + type: 'completion', + createdAt: expect.any(String), + }); + }); + + it('supports fetching events for a task with after query', async () => { + const { server } = await startTestBackend({ + features: [ + scaffolderPlugin, + catalogServiceMock.factory({ + entities: [mockTemplateEntity], + }), + ], + }); + + // Confirm no tasks are present + let response = await request(server).get('/api/scaffolder/v2/tasks'); + + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ tasks: [], totalTasks: 0 }); + + // Create a task to fetch events for + response = await request(server) + .post('/api/scaffolder/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + }); + + expect(response.status).toBe(201); + expect(response.body).toMatchObject({ id: expect.any(String) }); + + const { id: taskId } = response.body; + + // Wait for task to complete + await waitForExpect(async () => { + response = await request(server).get( + `/api/scaffolder/v2/tasks/${taskId}`, + ); + + expect(response.body).toMatchObject({ + status: 'completed', + }); + }); + + // Get events for the task + response = await request(server) + .get(`/api/scaffolder/v2/tasks/${taskId}/events`) + .query({ after: 8 }); // out of 10 events + + expect(response.status).toEqual(200); + expect(response.body).toHaveLength(2); + expect(response.body).toContainEqual({ + body: { + message: 'Finished step Second log', + status: 'completed', + stepId: 'step-two', + }, + createdAt: expect.any(String), + id: 9, + isTaskRecoverable: false, + taskId: taskId, + type: 'log', + }); + expect(response.body).toContainEqual({ + body: { message: 'Run completed with status: completed', output: {} }, + createdAt: expect.any(String), + id: 10, + isTaskRecoverable: false, + taskId: taskId, + type: 'completion', + }); + }); + + it('supports performing a dry-run of a template', async () => { + const { server } = await startTestBackend({ + features: [ + scaffolderPlugin, + catalogServiceMock.factory({ + entities: [mockTemplateEntity], + }), + ], + }); + + const { body, status } = await request(server) + .post('/api/scaffolder/v2/dry-run') + .auth(mockCredentials.user.token(), { type: 'bearer' }) + .send({ + template: mockTemplateEntity, + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + directoryContents: [], + }); + + expect(status).toBe(200); + expect(body).toMatchObject({ + log: [ + { body: { message: 'Starting up task with 3 steps' } }, + { + body: { + stepId: 'step-one', + status: 'processing', + message: 'Beginning step First log', + }, + }, + { + body: { + stepId: 'step-one', + message: + '\u001b[32minfo\u001b[39m: Running debug:log in dry-run mode with inputs (secrets redacted): {\n "message": "hello"\n}', + }, + }, + { + body: { + stepId: 'step-one', + message: '\u001b[32minfo\u001b[39m: {\n "message": "hello"\n}', + }, + }, + { + body: { + stepId: 'step-one', + message: '\u001b[32minfo\u001b[39m: hello', + }, + }, + { + body: { + stepId: 'step-one', + status: 'completed', + message: 'Finished step First log', + }, + }, + { + body: { + stepId: 'step-two', + status: 'processing', + message: 'Beginning step Second log', + }, + }, + { + body: { + stepId: 'step-two', + message: + '\u001b[32minfo\u001b[39m: Running debug:log in dry-run mode with inputs (secrets redacted): {\n "message": "world"\n}', + }, + }, + { + body: { + stepId: 'step-two', + message: '\u001b[32minfo\u001b[39m: {\n "message": "world"\n}', + }, + }, + { + body: { + stepId: 'step-two', + message: '\u001b[32minfo\u001b[39m: world', + }, + }, + { + body: { + stepId: 'step-two', + status: 'completed', + message: 'Finished step Second log', + }, + }, + ], + directoryContents: [], + output: {}, + steps: [ + { + id: 'step-one', + name: 'First log', + action: 'debug:log', + input: { message: 'hello' }, + }, + { + id: 'step-two', + name: 'Second log', + action: 'debug:log', + input: { message: 'world' }, + 'backstage:permissions': { tags: ['steps-tag'] }, + }, + ], + }); + }); + + it('supports performing an autocomplete for a given provider and resource', async () => { + const handleAutocompleteRequest = jest.fn().mockResolvedValue({ + results: [{ title: 'blob' }], + }); + + const mockContext = { mock: 'context' }; + const mockToken = 'mocktoken'; + + const { server } = await startTestBackend({ + features: [ + scaffolderPlugin, + createBackendModule({ + pluginId: 'scaffolder', + moduleId: 'custom-extensions', + register(env) { + env.registerInit({ + deps: { + scaffolder: scaffolderAutocompleteExtensionPoint, + }, + async init({ scaffolder }) { + scaffolder.addAutocompleteProvider({ + id: 'test-provider', + handler: handleAutocompleteRequest, + }); + }, + }); + }, + }), + ], + }); + + const { body, status } = await request(server) + .post('/api/scaffolder/v2/autocomplete/test-provider/my-resource') + .send({ + token: mockToken, + context: mockContext, + }); + + expect(status).toBe(200); + expect(body).toMatchObject({ results: [{ title: 'blob' }] }); + + expect(handleAutocompleteRequest).toHaveBeenCalledWith({ + context: mockContext, + resource: 'my-resource', + token: mockToken, + }); + }); + + it('supports listing templating extensions', async () => { + const { server } = await startTestBackend({ + features: [scaffolderPlugin], + }); + + const { body, status } = await request(server).get( + '/api/scaffolder/v2/templating-extensions', + ); + + expect(status).toBe(200); + expect(body).toMatchObject({ + filters: expect.objectContaining({ + parseRepoUrl: expect.objectContaining({ + description: expect.any(String), + examples: expect.any(Array), + schema: expect.objectContaining({ + input: expect.any(Object), + output: expect.any(Object), + }), + }), + parseEntityRef: expect.objectContaining({ + description: expect.any(String), + examples: expect.any(Array), + schema: expect.objectContaining({ + input: expect.any(Object), + output: expect.any(Object), + }), + }), + pick: expect.objectContaining({ + description: expect.any(String), + examples: expect.any(Array), + schema: expect.objectContaining({ + input: expect.any(Object), + output: expect.any(Object), + }), + }), + projectSlug: expect.objectContaining({ + description: expect.any(String), + examples: expect.any(Array), + schema: expect.objectContaining({ + input: expect.any(Object), + output: expect.any(Object), + }), + }), + }), + globals: { functions: {}, values: {} }, + }); + }); +}); 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<TInput extends JsonObject> = - ActionContextNode<TInput>; - -/** - * @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<TInput extends JsonObject> = - TemplateActionNode<TInput>; - -/** - * 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<string, TemplateAction>(); 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<string, TemplateFilter>; - additionalTemplateGlobals?: Record<string, TemplateGlobal>; -} - -/** - * 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<Partial<Writable>> as jest.Mocked<Writable>; - - 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<Partial<Writable>> as jest.Mocked<Writable>; - - 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<HumanDuration>({ + 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<FetchTemplateInput>; - - 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<FetchTemplateInput>; 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<string, TemplateFilter>; additionalTemplateGlobals?: Record<string, TemplateGlobal>; }) { - 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<I extends TemplateActionInput>( 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<I extends TemplateActionInput>( (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<string, TemplateFilter>; additionalTemplateGlobals?: Record<string, TemplateGlobal>; }) { - 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..62d3bde0df 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,22 @@ * 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'; +import { examples } from './read.examples'; -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<typeof contentSchema>; +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<ReturnType<typeof contentSchema>>; /** * Creates new action that enables reading directories in the workspace. @@ -37,15 +40,16 @@ export const createFilesystemReadDirAction = () => { id: 'fs:readdir', description: 'Reads files and directories from the workspace', supportsDryRun: true, + examples, 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..d384443252 100644 --- a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts @@ -17,11 +17,16 @@ import { AuditorService, BackstageCredentials, + LoggerService, } from '@backstage/backend-plugin-api'; import type { UserEntity } from '@backstage/catalog-model'; import { ScmIntegrations } from '@backstage/integration'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; -import { TaskSpec, TemplateInfo } from '@backstage/plugin-scaffolder-common'; +import { + ScaffolderTaskStatus, + TaskSpec, + TemplateInfo, +} from '@backstage/plugin-scaffolder-common'; import { createTemplateAction, deserializeDirectoryContents, @@ -36,7 +41,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'; @@ -54,14 +58,20 @@ interface DryRunInput { } interface DryRunResult { - log: Array<{ body: JsonObject }>; + log: Array<{ + body: { + message: string; + stepId?: string; + status?: ScaffolderTaskStatus; + }; + }>; directoryContents: SerializedFile[]; output: JsonObject; } /** @internal */ export type TemplateTesterCreateOptions = { - logger: Logger; + logger: LoggerService; auditor?: AuditorService; integrations: ScmIntegrations; actionRegistry: TemplateActionRegistry; @@ -106,7 +116,13 @@ export function createDryRunner(options: TemplateTesterCreateOptions) { const contentsPath = path.dirname(basePath); const dryRunId = uuid(); - const log = new Array<{ body: JsonObject }>(); + const log = new Array<{ + body: { + message: string; + stepId?: string; + status?: ScaffolderTaskStatus; + }; + }>(); try { await deserializeDirectoryContents(contentsPath, input.directoryContents); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts index eb0418b5ed..03a85ad971 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts @@ -14,14 +14,19 @@ * limitations under the License. */ -import { DatabaseManager } from '@backstage/backend-common'; +import { DatabaseManager } from '@backstage/backend-defaults/database'; import { ConfigReader } from '@backstage/config'; import { DatabaseTaskStore, RawDbTaskEventRow } from './DatabaseTaskStore'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { ConflictError } from '@backstage/errors'; -import { createMockDirectory } from '@backstage/backend-test-utils'; +import { + mockServices, + createMockDirectory, +} from '@backstage/backend-test-utils'; import fs from 'fs-extra'; import { EventsService } from '@backstage/plugin-events-node'; +import { PermissionCriteria } from '@backstage/plugin-permission-common'; +import { TaskFilters } from '@backstage/plugin-scaffolder-node'; const createStore = async (events?: EventsService) => { const manager = DatabaseManager.fromConfig( @@ -33,7 +38,10 @@ const createStore = async (events?: EventsService) => { }, }, }), - ).forPlugin('scaffolder'); + ).forPlugin('scaffolder', { + logger: mockServices.logger.mock(), + lifecycle: mockServices.lifecycle.mock(), + }); const store = await DatabaseTaskStore.create({ database: manager, events, @@ -231,6 +239,56 @@ describe('DatabaseTaskStore', () => { expect(tasks[0].id).toBeDefined(); }); + it('should filter tasks based on permissionFilters', async () => { + const { store } = await createStore(); + + await store.createTask({ + spec: {} as TaskSpec, + createdBy: 'user:default/one', + }); + + await store.createTask({ + spec: {} as TaskSpec, + createdBy: 'user:default/two', + }); + + await store.createTask({ + spec: {} as TaskSpec, + createdBy: 'user:default/three', + }); + + await store.createTask({ + spec: {} as TaskSpec, + createdBy: 'user:default/one', + }); + + await store.createTask({ + spec: {} as TaskSpec, + createdBy: 'user:default/four', + }); + + const permissionFilters: PermissionCriteria<TaskFilters> = { + not: { + key: 'created_by', + values: ['user:default/three', 'user:default/four'], + }, + }; + + const { tasks, totalTasks } = await store.list({ + permissionFilters: permissionFilters, + }); + + expect(totalTasks).toBe(3); + + const createdByList = tasks.map(task => task.createdBy); + expect(createdByList).toEqual( + expect.arrayContaining(['user:default/one', 'user:default/two']), + ); + expect(createdByList).not.toEqual( + expect.arrayContaining(['user:default/three', 'user:default/four']), + ); + }); + it('should sent an event to start cancelling the task', async () => { const { store } = await createStore(eventsService); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index 4dbe938679..fdcaa8613e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -35,6 +35,7 @@ import { SerializedTask, SerializedTaskEvent, TaskEventType, + TaskFilter, TaskSecrets, TaskStatus, } from '@backstage/plugin-scaffolder-node'; @@ -48,6 +49,14 @@ import { } from '@backstage/plugin-scaffolder-node/alpha'; import { flattenParams } from '../../service/helpers'; import { EventsService } from '@backstage/plugin-events-node'; +import { PermissionCriteria } from '@backstage/plugin-permission-common'; +import { + isAndCriteria, + isNotCriteria, + isOrCriteria, +} from '@backstage/plugin-permission-node'; +import { TaskFilters } from '@backstage/plugin-scaffolder-node'; +import { compact } from 'lodash'; const migrationsDir = resolvePackagePath( '@backstage/plugin-scaffolder-backend', @@ -76,7 +85,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 +95,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 +118,7 @@ const parseSqlDateToIsoString = <T>(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 { @@ -197,6 +204,48 @@ export class DatabaseTaskStore implements TaskStore { } } + private isTaskFilter(filter: any): filter is TaskFilter { + return filter.hasOwnProperty('key'); + } + + private parseFilter( + filter: PermissionCriteria<TaskFilters>, + query: Knex.QueryBuilder, + db: Knex, + negate: boolean = false, + ): Knex.QueryBuilder { + if (isNotCriteria(filter)) { + return this.parseFilter(filter.not, query, db, !negate); + } + + if (this.isTaskFilter(filter)) { + const values: string[] = compact(filter.values) ?? []; + if (negate) { + query.whereNotIn(filter.key, values); + } else { + query.whereIn(filter.key, values); + } + + return query; + } + + return query[negate ? 'andWhereNot' : 'andWhere'](subQuery => { + if (isOrCriteria(filter)) { + for (const subFilter of filter.anyOf ?? []) { + subQuery.orWhere(subQueryInner => + this.parseFilter(subFilter, subQueryInner, db, false), + ); + } + } else if (isAndCriteria(filter)) { + for (const subFilter of filter.allOf ?? []) { + subQuery.andWhere(subQueryInner => + this.parseFilter(subFilter, subQueryInner, db, false), + ); + } + } + }); + } + async list(options: { createdBy?: string; status?: TaskStatus; @@ -209,16 +258,31 @@ export class DatabaseTaskStore implements TaskStore { offset?: number; }; order?: { order: 'asc' | 'desc'; field: string }[]; + permissionFilters?: PermissionCriteria<TaskFilters>; }): Promise<{ tasks: SerializedTask[]; totalTasks?: number }> { - const { createdBy, status, pagination, order, filters } = options ?? {}; + const { createdBy, status, pagination, order, filters, permissionFilters } = + options ?? {}; const queryBuilder = this.db<RawDbTaskRow & { count: number }>('tasks'); - if (createdBy || filters?.createdBy) { - const arr: string[] = flattenParams<string>( - createdBy, - filters?.createdBy, - ); - queryBuilder.whereIn('created_by', [...new Set(arr)]); + const createdByValues = flattenParams<string>( + createdBy, + filters?.createdBy, + ); + + const combinedPermissionFilters: + | PermissionCriteria<TaskFilters> + | undefined = + createdByValues.length > 0 + ? { + allOf: [ + { key: 'created_by', values: createdByValues }, + ...(permissionFilters ? [permissionFilters] : []), + ], + } + : permissionFilters; + + if (combinedPermissionFilters) { + this.parseFilter(combinedPermissionFilters, queryBuilder, this.db); } if (status || filters?.status) { @@ -273,24 +337,29 @@ export class DatabaseTaskStore implements TaskStore { throw new NotFoundError(`No task with id '${taskId}' found`); } try { - const spec = JSON.parse(result.spec); - const secrets = result.secrets ? JSON.parse(result.secrets) : undefined; - const state = this.getState(result); - return { - id: result.id, - spec, - status: result.status, - lastHeartbeatAt: parseSqlDateToIsoString(result.last_heartbeat_at), - createdAt: parseSqlDateToIsoString(result.created_at), - createdBy: result.created_by ?? undefined, - secrets, - state, - }; + return this.parseTaskRow(result); } catch (error) { throw new Error(`Failed to parse spec of task '${taskId}', ${error}`); } } + private parseTaskRow(result: RawDbTaskRow): SerializedTask { + const spec = JSON.parse(result.spec); + const secrets = result.secrets ? JSON.parse(result.secrets) : undefined; + const state = this.getState(result); + + return { + id: result.id, + spec, + status: result.status, + lastHeartbeatAt: parseSqlDateToIsoString(result.last_heartbeat_at), + createdAt: parseSqlDateToIsoString(result.created_at), + createdBy: result.created_by ?? undefined, + secrets, + state, + }; + } + async createTask( options: TaskStoreCreateTaskOptions, ): Promise<TaskStoreCreateTaskResult> { @@ -538,7 +607,7 @@ export class DatabaseTaskStore implements TaskStore { const events = rawEvents.map(event => { try { - const body = JSON.parse(event.body) as JsonObject; + const body = JSON.parse(event.body) as SerializedTaskEvent['body']; return { id: Number(event.id), isTaskRecoverable, @@ -654,12 +723,18 @@ export class DatabaseTaskStore implements TaskStore { }); } - async retryTask?(options: { taskId: string }): Promise<void> { + async retryTask?(options: { + secrets?: TaskSecrets; + taskId: string; + }): Promise<void> { + const { secrets, taskId } = options; + await this.db.transaction(async tx => { const result = await tx<RawDbTaskRow>('tasks') - .where('id', options.taskId) + .where('id', taskId) .update( { + ...(secrets && { secrets: JSON.stringify(secrets) }), status: 'open', last_heartbeat_at: this.db.fn.now(), }, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 2d6ed2d57c..8019bd4134 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<PermissionEvaluator> = { @@ -71,11 +67,21 @@ describe('NunjucksWorkflowRunner', () => { }); const createMockTaskWithSpec = ( - spec: TaskSpec, + { + apiVersion = 'scaffolder.backstage.io/v1beta3', + output = {}, + parameters = {}, + ...spec + }: Partial<TaskSpec>, 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', @@ -428,12 +361,29 @@ describe('NunjucksWorkflowRunner', () => { expect.objectContaining({ backstageToken: token }), ); }); + + it('should pass step info through', async () => { + const task = createMockTaskWithSpec({ + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-validated-action', + input: { foo: 1 }, + }, + ], + }); + + await runner.execute(task); + + expect(fakeActionHandler.mock.calls[0][0].step.id).toEqual('test'); + expect(fakeActionHandler.mock.calls[0][0].step.name).toEqual('name'); + }); }); 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 +396,6 @@ describe('NunjucksWorkflowRunner', () => { output: { result: '${{ steps.conditional.output.mock }}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -456,7 +405,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 +417,6 @@ describe('NunjucksWorkflowRunner', () => { output: { result: '${{ steps.conditional.output.mock }}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -479,7 +426,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 +438,6 @@ describe('NunjucksWorkflowRunner', () => { output: { result: '${{ steps.conditional.output.mock }}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -502,7 +447,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 +458,6 @@ describe('NunjucksWorkflowRunner', () => { output: { result: '${{ steps.conditional.output.mock }}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -522,7 +465,6 @@ describe('NunjucksWorkflowRunner', () => { }); it('skips when false', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'conditional', @@ -534,7 +476,6 @@ describe('NunjucksWorkflowRunner', () => { output: { result: '${{ steps.conditional.output.mock }}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -546,7 +487,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 +497,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { input: 'BACKSTAGE', }, @@ -573,7 +512,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 +522,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { input: 'BACKSTAGE', }, @@ -597,7 +534,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 +545,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { number: 0, string: '1', @@ -625,7 +560,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 +570,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { complex: { bar: 'BACKSTAGE' }, }, @@ -651,7 +584,6 @@ describe('NunjucksWorkflowRunner', () => { it('supports really complex structures', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -662,7 +594,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { complex: { bar: 'BACKSTAGE', @@ -680,7 +611,6 @@ describe('NunjucksWorkflowRunner', () => { it('supports numbers as first class too', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -691,7 +621,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { complex: { bar: 'BACKSTAGE', @@ -710,8 +639,6 @@ describe('NunjucksWorkflowRunner', () => { it('should deal with checkpoints', async () => { const task = { ...createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, steps: [ { id: 'test', @@ -762,7 +689,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 +700,6 @@ describe('NunjucksWorkflowRunner', () => { output: { foo: '${{steps.test.output.mock | upper}}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -784,7 +709,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 +721,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, - parameters: {}, }); await runner.execute(task); @@ -836,9 +758,6 @@ describe('NunjucksWorkflowRunner', () => { const task = createMockTaskWithSpec( { - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -882,9 +801,6 @@ describe('NunjucksWorkflowRunner', () => { const task = createMockTaskWithSpec( { - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -909,7 +825,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 +834,6 @@ describe('NunjucksWorkflowRunner', () => { input: { color: '${{each.value}}' }, }, ], - output: {}, parameters: { colors, }, @@ -936,9 +850,44 @@ describe('NunjucksWorkflowRunner', () => { }); }); + it('should run a step repeatedly - flat values with secrets', async () => { + const secrets = { + s1: 'secret-value1', + s2: 'secret-value2', + s3: 'secret-value3', + }; + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + each: [ + '${{ secrets.s1 }}', + '${{ secrets.s2 }}', + '${{ secrets.s3 }}', + ], + action: 'jest-mock-action', + input: { secret: '${{each.value}}' }, + }, + ], + }, + secrets, + ); + await runner.execute(task); + + Object.values(secrets).forEach((secret, idx) => { + expectTaskLog( + `info: Running step each: {"key":"${idx}","value":"***"}`, + ); + expect(fakeActionHandler).toHaveBeenCalledWith( + expect.objectContaining({ input: { secret } }), + ); + }); + }); + it('should run a step repeatedly - object list', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -951,7 +900,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { settings: [{ color: 'blue' }], }, @@ -968,13 +916,52 @@ describe('NunjucksWorkflowRunner', () => { ); }); + it('should run a step repeatedly - object list with secrets', async () => { + const secrets = { + s1: 'secret-value1', + s2: 'secret-value2', + }; + const names = ['Service1', 'Service2']; + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + each: [ + { name: names[0], token: '${{ secrets.s1 }}' }, + { name: names[1], token: '${{ secrets.s2 }}' }, + ], + action: 'jest-mock-action', + input: { + name: '${{each.value.name}}', + token: '${{each.value.token}}', + }, + }, + ], + }, + secrets, + ); + await runner.execute(task); + + Object.values(secrets).forEach((secret, idx) => { + expectTaskLog( + `info: Running step each: {"key":"${idx}","value":"[object Object]"}`, + ); + expect(fakeActionHandler).toHaveBeenCalledWith( + expect.objectContaining({ + input: { name: names[idx], token: secret }, + }), + ); + }); + }); + it('should run a step repeatedly - object', async () => { const settings = { color: 'blue', transparent: 'yes', }; const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -984,7 +971,6 @@ describe('NunjucksWorkflowRunner', () => { input: { key: '${{each.key}}', value: '${{each.value}}' }, }, ], - output: {}, parameters: { settings, }, @@ -1006,7 +992,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 +1001,6 @@ describe('NunjucksWorkflowRunner', () => { input: { foo: '${{each.value}}' }, }, ], - output: {}, parameters: { numbers, }, @@ -1037,7 +1021,6 @@ describe('NunjucksWorkflowRunner', () => { it('should validate each action iteration', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1047,7 +1030,6 @@ describe('NunjucksWorkflowRunner', () => { input: { foo: '${{each.value.foo}}' }, }, ], - output: {}, parameters: { data: [ { @@ -1065,7 +1047,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 +1056,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 +1068,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 +1076,6 @@ describe('NunjucksWorkflowRunner', () => { input: {}, }, ], - output: {}, - parameters: {}, }, { foo: 'bar' }, ); @@ -1114,7 +1090,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 +1100,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, - parameters: {}, }, { foo: 'bar' }, ); @@ -1141,7 +1114,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 +1127,6 @@ describe('NunjucksWorkflowRunner', () => { output: { b: '${{ secrets.foo }}', }, - parameters: {}, }, { foo: 'bar' }, ); @@ -1169,7 +1140,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 +1169,6 @@ describe('NunjucksWorkflowRunner', () => { describe('filters', () => { it('provides the parseRepoUrl filter', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1228,7 +1197,6 @@ describe('NunjucksWorkflowRunner', () => { describe('parseEntityRef', () => { it('parses entity ref', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1256,7 +1224,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 +1251,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 +1278,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 +1307,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 +1335,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 +1361,6 @@ describe('NunjucksWorkflowRunner', () => { it('provides the pick filter', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1422,7 +1384,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 +1416,6 @@ describe('NunjucksWorkflowRunner', () => { it('sets isDryRun flag correctly', async () => { const task = createMockTaskWithSpec( { - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -1481,13 +1439,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', @@ -1510,6 +1465,35 @@ describe('NunjucksWorkflowRunner', () => { fakeActionHandler.mock.calls[0][0].templateInfo.entity.metadata.name, ).toEqual('test-template'); }); + + it('should have step info in action context during dry run', async () => { + const task = createMockTaskWithSpec( + { + templateInfo: { + entityRef: 'dryRun-Entity', + entity: { metadata: { name: 'test-template' } }, + }, + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-validated-action', + input: { foo: 1 }, + }, + ], + }, + { + backstageToken: token, + }, + true, + ); + + await runner.execute(task); + + expect(fakeActionHandler.mock.calls[0][0].isDryRun).toEqual(true); + expect(fakeActionHandler.mock.calls[0][0].step.id).toEqual('test'); + expect(fakeActionHandler.mock.calls[0][0].step.name).toEqual('name'); + }); }); describe('permissions', () => { @@ -1519,9 +1503,6 @@ describe('NunjucksWorkflowRunner', () => { ]); const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -1560,9 +1541,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..d069a6bc1c 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,17 @@ 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'; +import { + CheckpointState, + CheckpointContext, +} from '@backstage/plugin-scaffolder-node/alpha'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; actionRegistry: TemplateActionRegistry; integrations: ScmIntegrations; - logger: winston.Logger; + logger: LoggerService; auditor?: AuditorService; additionalTemplateFilters?: Record<string, TemplateFilter>; additionalTemplateGlobals?: Record<string, TemplateGlobal>; @@ -92,16 +95,6 @@ type TemplateContext = { }; }; -type CheckpointState = - | { - status: 'failed'; - reason: string; - } - | { - status: 'success'; - value: JsonValue; - }; - const isValidTaskSpec = (taskSpec: TaskSpec): taskSpec is TaskSpecV1beta3 => { return taskSpec.apiVersion === 'scaffolder.backstage.io/v1beta3'; }; @@ -113,7 +106,7 @@ const createStepLogger = ({ }: { task: TaskContext; step: TaskStep; - rootLogger: winston.Logger; + rootLogger: LoggerService; }) => { const taskLogger = WinstonLogger.create({ level: process.env.LOG_LEVEL || 'info', @@ -126,22 +119,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 +246,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { } const action: TemplateAction<JsonObject> = this.options.actionRegistry.get(step.action); - const { taskLogger, streamLogger } = createStepLogger({ + const { taskLogger } = createStepLogger({ task, step, rootLogger: this.options.logger, @@ -315,7 +293,12 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { } const resolvedEach = - step.each && this.render(step.each, context, renderTemplate); + step.each && + this.render( + step.each, + { ...context, secrets: task.secrets ?? {} }, + renderTemplate, + ); if (step.each && !resolvedEach) { throw new InputError( @@ -393,14 +376,11 @@ 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<T extends JsonValue | void>(opts: { - key?: string; - fn: () => Promise<T> | T; - }) { + async checkpoint<T extends JsonValue | void>( + opts: CheckpointContext<T>, + ) { const { key: checkpointKey, fn } = opts; const key = `v1.task.checkpoint.${step.id}.${checkpointKey}`; @@ -409,9 +389,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { if (prevTaskState) { const prevState = ( - prevTaskState.state?.checkpoints as { - [key: string]: CheckpointState; - } + prevTaskState.state?.checkpoints as CheckpointState )?.[key]; if (prevState && prevState.status === 'success') { @@ -460,6 +438,10 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { isDryRun: task.isDryRun, signal: task.cancelSignal, getInitiatorCredentials: () => task.getInitiatorCredentials(), + step: { + id: step.id, + name: step.name, + }, }); } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index dc89100b95..2b13130908 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { DatabaseManager } from '@backstage/backend-common'; +import { DatabaseManager } from '@backstage/backend-defaults/database'; import { ConfigReader } from '@backstage/config'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { @@ -36,7 +36,10 @@ async function createStore(): Promise<DatabaseTaskStore> { }, }, }), - ).forPlugin('scaffolder'); + ).forPlugin('scaffolder', { + logger: mockServices.logger.mock(), + lifecycle: mockServices.lifecycle.mock(), + }); return await DatabaseTaskStore.create({ database: manager, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 0fd18a9ee5..5b4d568144 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'; @@ -28,38 +29,28 @@ import { TaskBrokerDispatchOptions, TaskCompletionState, TaskContext, + TaskFilters, TaskSecrets, TaskStatus, } from '@backstage/plugin-scaffolder-node'; -import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; import { - JsonObject, - JsonValue, - Observable, - createDeferred, -} from '@backstage/types'; -import { Logger } from 'winston'; + CheckpointState, + WorkspaceProvider, + UpdateTaskCheckpointOptions, +} from '@backstage/plugin-scaffolder-node/alpha'; +import { JsonObject, Observable, createDeferred } from '@backstage/types'; import ObservableImpl from 'zen-observable'; import { DefaultWorkspaceService, WorkspaceService } from './WorkspaceService'; import { readDuration } from './helper'; import { InternalTaskSecrets, TaskStore } from './types'; +import { PermissionCriteria } from '@backstage/plugin-permission-common'; type TaskState = { - checkpoints: { - [key: string]: - | { - status: 'failed'; - reason: string; - } - | { - status: 'success'; - value: JsonValue; - }; - }; + checkpoints: CheckpointState; }; /** * 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 +62,7 @@ export class TaskManager implements TaskContext { task: CurrentClaimedTask, storage: TaskStore, abortSignal: AbortSignal, - logger: Logger, + logger: LoggerService, auth?: AuthService, config?: Config, additionalWorkspaceProviders?: Record<string, WorkspaceProvider>, @@ -100,11 +91,15 @@ 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, ) {} + get taskId() { + return this.task.taskId; + } + get spec() { return this.task.spec; } @@ -152,20 +147,9 @@ export class TaskManager implements TaskContext { return this.storage.getTaskState?.({ taskId: this.task.taskId }); } - async updateCheckpoint?( - options: - | { - key: string; - status: 'success'; - value: JsonValue; - } - | { - key: string; - status: 'failed'; - reason: string; - }, - ): Promise<void> { + async updateCheckpoint?(options: UpdateTaskCheckpointOptions): Promise<void> { const { key, ...value } = options; + if (this.task.state) { (this.task.state as TaskState).checkpoints[key] = value; } else { @@ -269,7 +253,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< @@ -291,6 +275,7 @@ export class StorageTaskBroker implements TaskBroker { offset?: number; }; order?: { order: 'asc' | 'desc'; field: string }[]; + permissionFilters?: PermissionCriteria<TaskFilters>; }): Promise<{ tasks: SerializedTask[]; totalTasks?: number }> { if (!this.storage.list) { throw new Error( @@ -503,8 +488,11 @@ export class StorageTaskBroker implements TaskBroker { }); } - async retry?(taskId: string): Promise<void> { - await this.storage.retryTask?.({ taskId }); + async retry?(options: { + secrets?: TaskSecrets; + taskId: string; + }): Promise<void> { + await this.storage.retryTask?.(options); this.signalDispatch(); } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index dd9233a5ee..7fe0618af2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -15,11 +15,15 @@ */ import os from 'os'; -import { DatabaseManager } from '@backstage/backend-common'; +import { DatabaseManager } from '@backstage/backend-defaults/database'; import { ConfigReader } from '@backstage/config'; import { DatabaseTaskStore } from './DatabaseTaskStore'; import { StorageTaskBroker } from './StorageTaskBroker'; -import { TaskWorker, TaskWorkerOptions } from './TaskWorker'; +import { + createParameterTruncator, + TaskWorker, + TaskWorkerOptions, +} from './TaskWorker'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateActionRegistry } from '../actions'; import { NunjucksWorkflowRunner } from './NunjucksWorkflowRunner'; @@ -49,7 +53,10 @@ async function createStore(): Promise<DatabaseTaskStore> { }, }, }), - ).forPlugin('scaffolder'); + ).forPlugin('scaffolder', { + logger: mockServices.logger.mock(), + lifecycle: mockServices.lifecycle.mock(), + }); return await DatabaseTaskStore.create({ database: manager, }); @@ -137,6 +144,66 @@ describe('TaskWorker', () => { const event = events.find(e => e.type === 'completion'); expect(event?.body.output).toEqual({ testOutput: 'testmockoutput' }); }); + + it('should log an audit event with task parameters when running a task', async () => { + (workflowRunner.execute as jest.Mock).mockResolvedValue({ + output: {}, + }); + + const auditor = mockServices.auditor.mock(); + const auditEvent = { + success: jest.fn(), + fail: jest.fn(), + }; + auditor.createEvent.mockResolvedValue(auditEvent); + + const broker = new StorageTaskBroker(storage, logger); + const taskWorker = await TaskWorker.create({ + logger, + workingDirectory, + integrations, + taskBroker: broker, + actionRegistry, + auditor, + config: mockServices.rootConfig({ + data: { + scaffolder: { + auditor: { + taskParameterMaxLength: 5, + }, + }, + }, + }), + }); + + await taskWorker.runOneTask({ + spec: { + apiVersion: 'scaffolder.backstage.io/v1beta3', + parameters: { + test: 'thisisaverylongstring', + }, + steps: [], + output: {}, + }, + complete: jest.fn(), + createdBy: 'test-creator', + taskId: 'test-id', + } as unknown as TaskContext); + + expect(auditor.createEvent).toHaveBeenCalledWith({ + eventId: 'task', + severityLevel: 'medium', + meta: { + actionType: 'execution', + createdBy: 'test-creator', + taskId: 'test-id', + taskParameters: { + test: 'thisi...<truncated>', + }, + }, + }); + expect(auditEvent.success).toHaveBeenCalled(); + }); }); describe('Concurrent TaskWorker', () => { @@ -340,3 +407,111 @@ describe('TaskWorker internals', () => { expect(inflightTasks.length).toBe(2); }); }); + +describe('createParameterTruncator', () => { + it('successfully does nothing', async () => { + const testParams = {}; + + const result = createParameterTruncator()(testParams); + + expect(result).toEqual({}); + }); + + it('truncates long strings in nested objects and arrays', async () => { + const params = { + test: 'short', + test2: 'thisisaverylongstring', + nested: { + test3: 'anotherlongstringhere', + test4: ['ok', 'toolongstring', { prop: 'thisisaverylongstring' }], + }, + }; + + const result = createParameterTruncator( + mockServices.rootConfig({ + data: { + scaffolder: { + auditor: { + taskParameterMaxLength: 5, + }, + }, + }, + }), + )(params); + + expect(result).toEqual({ + test: 'short', + test2: 'thisi...<truncated>', + nested: { + test3: 'anoth...<truncated>', + test4: ['ok', 'toolo...<truncated>', { prop: 'thisi...<truncated>' }], + }, + }); + }); + + it('should not truncate if max length is -1', async () => { + const params = { + test: 'short', + test2: 'thisisaverylongstring', + nested: { + test3: 'anotherlongstringhere', + test4: ['ok', 'toolongstring', { prop: 'thisisaverylongstring' }], + }, + }; + + const result = createParameterTruncator( + mockServices.rootConfig({ + data: { + scaffolder: { + auditor: { + taskParameterMaxLength: -1, + }, + }, + }, + }), + )(params); + + expect(result).toEqual({ + test: 'short', + test2: 'thisisaverylongstring', + nested: { + test3: 'anotherlongstringhere', + test4: ['ok', 'toolongstring', { prop: 'thisisaverylongstring' }], + }, + }); + }); + + it('should throw on invalid max length', async () => { + expect(() => + createParameterTruncator( + mockServices.rootConfig({ + data: { + scaffolder: { + auditor: { + taskParameterMaxLength: -2, + }, + }, + }, + }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid configuration for 'scaffolder.auditor.taskParameterMaxLength', got -2. Must be a positive integer or -1 to disable truncation."`, + ); + + expect(() => + createParameterTruncator( + mockServices.rootConfig({ + data: { + scaffolder: { + auditor: { + taskParameterMaxLength: 1.5, + }, + }, + }, + }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid configuration for 'scaffolder.auditor.taskParameterMaxLength', got 1.5. Must be a positive integer or -1 to disable truncation."`, + ); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 4441a2f0ca..9abe5f3ba6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { AuditorService } from '@backstage/backend-plugin-api'; -import { assertError, stringifyError } from '@backstage/errors'; +import { AuditorService, LoggerService } from '@backstage/backend-plugin-api'; +import { assertError, InputError, stringifyError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { @@ -25,15 +25,18 @@ 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'; import { setTimeout } from 'timers/promises'; +import { JsonObject } from '@backstage/types'; +import { Config } from '@backstage/config'; + +const DEFAULT_TASK_PARAMETER_MAX_LENGTH = 256; /** * 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 +46,15 @@ export type TaskWorkerOptions = { }; concurrentTasksLimit: number; permissions?: PermissionEvaluator; - logger?: Logger; + logger?: LoggerService; auditor?: AuditorService; + config?: Config; 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,8 +62,9 @@ export type CreateWorkerOptions = { actionRegistry: TemplateActionRegistry; integrations: ScmIntegrations; workingDirectory: string; - logger: Logger; + logger: LoggerService; auditor?: AuditorService; + config?: Config; additionalTemplateFilters?: Record<string, TemplateFilter>; /** * The number of tasks that can be executed at the same time by the worker @@ -81,22 +86,28 @@ 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 parameterAuditTransform: ParameterAuditTransform; private stopWorkers: boolean; - private constructor(private readonly options: TaskWorkerOptions) { + private constructor( + private readonly options: TaskWorkerOptions & { + parameterAuditTransform: ParameterAuditTransform; + }, + ) { this.stopWorkers = false; this.logger = options.logger; this.auditor = options.auditor; this.taskQueue = new PQueue({ concurrency: options.concurrentTasksLimit, }); + this.parameterAuditTransform = options.parameterAuditTransform; } static async create(options: CreateWorkerOptions): Promise<TaskWorker> { @@ -104,6 +115,7 @@ export class TaskWorker { taskBroker, logger, auditor, + config, actionRegistry, integrations, workingDirectory, @@ -131,7 +143,9 @@ export class TaskWorker { concurrentTasksLimit, permissions, auditor, + config, gracefulShutdown, + parameterAuditTransform: createParameterTruncator(config), }); } @@ -189,8 +203,9 @@ export class TaskWorker { severityLevel: 'medium', meta: { actionType: 'execution', + createdBy: task.createdBy, taskId: task.taskId, - taskParameters: task.spec.parameters, + taskParameters: this.parameterAuditTransform(task.spec.parameters), templateRef: task.spec.templateInfo?.entityRef, }, }); @@ -219,3 +234,53 @@ export class TaskWorker { } } } + +type ParameterAuditTransform = (parameters: JsonObject) => JsonObject; + +/** + * Truncates task parameters for audit logging using the configured max length. + * @internal + */ +export function createParameterTruncator( + config?: Config, +): ParameterAuditTransform { + const maxLength = + config?.getOptionalNumber('scaffolder.auditor.taskParameterMaxLength') ?? + DEFAULT_TASK_PARAMETER_MAX_LENGTH; + + if (!Number.isSafeInteger(maxLength) || maxLength < -1) { + throw new InputError( + `Invalid configuration for 'scaffolder.auditor.taskParameterMaxLength', got ${maxLength}. Must be a positive integer or -1 to disable truncation.`, + ); + } + + if (maxLength === -1) { + return (parameters: JsonObject) => parameters; + } + + return (parameters: JsonObject) => { + function truncate(value: unknown): unknown { + if (typeof value === 'string') { + if (value.length > maxLength) { + return value.slice(0, maxLength).concat('...<truncated>'); + } + return value; + } + if (Array.isArray(value)) { + return value.map(truncate); + } + if (value && typeof value === 'object') { + const result: Record<string, unknown> = {}; + for (const k in value as object) { + if (Object.hasOwn(value, k)) { + result[k] = truncate((value as any)[k]); + } + } + return result; + } + return value; + } + + return truncate(parameters) as JsonObject; + }; +} 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/logger.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.test.ts new file mode 100644 index 0000000000..ae83c1d719 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.test.ts @@ -0,0 +1,75 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { WinstonLogger } from './logger'; +import { MESSAGE } from 'triple-beam'; + +describe('WinstonLogger', () => { + describe('redacter', () => { + describe('add method', () => { + it('should handle null and undefined values in newRedactions without crashing', () => { + const { add, format } = WinstonLogger.redacter(); + + expect(() => { + add([null as any, undefined as any, 'valid-secret']); + }).not.toThrow(); + + const testObj = { + level: 'info', + message: 'This contains valid-secret and should be redacted', + [MESSAGE]: 'This contains valid-secret and should be redacted', + }; + const result = format.transform(testObj); + expect((result as any)?.[MESSAGE]).toBe( + 'This contains *** and should be redacted', + ); + }); + + it('should skip empty and single character redactions', () => { + const { add, format } = WinstonLogger.redacter(); + + add(['', 'x', 'valid-secret-123']); + + // MESSAGE symbol is where Winston stores the formatted message for redaction + const testObj = { + level: 'info', + message: 'This contains valid-secret-123 and should be redacted', + [MESSAGE]: 'This contains valid-secret-123 and should be redacted', + }; + + const result = format.transform(testObj); + expect((result as any)?.[MESSAGE]).toBe( + 'This contains *** and should be redacted', + ); + }); + + it('should trim whitespace from redactions', () => { + const { add, format } = WinstonLogger.redacter(); + + add([' secret-with-spaces \n', ' another-secret\t']); + + const testObj = { + level: 'info', + message: 'This contains secret-with-spaces and another-secret', + [MESSAGE]: 'This contains secret-with-spaces and another-secret', + }; + + const result = format.transform(testObj); + expect((result as any)?.[MESSAGE]).toBe('This contains *** and ***'); + }); + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts index 38263b73ca..a8e844c827 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts @@ -132,6 +132,10 @@ export class WinstonLogger implements RootLoggerService { add(newRedactions) { let added = 0; for (const redactionToTrim of newRedactions) { + // Skip null or undefined values + if (redactionToTrim === null || redactionToTrim === undefined) { + continue; + } // Trimming the string ensures that we don't accdentally get extra // newlines or other whitespace interfering with the redaction; this // can happen for example when using string literals in yaml diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/taskRecoveryHelper.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/taskRecoveryHelper.ts index f3b8b321fe..fa75bdb2df 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/taskRecoveryHelper.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/taskRecoveryHelper.ts @@ -28,7 +28,7 @@ export const trimEventsTillLastRecovery = ( if (recoveredEventInd >= 0) { const ind = events.length - recoveredEventInd - 1; const { recoverStrategy } = events[ind].body as { - recoverStrategy: TaskRecoverStrategy; + recoverStrategy?: TaskRecoverStrategy; }; if (recoverStrategy === 'startOver') { return { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 874793922f..62ffb171ff 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -17,96 +17,21 @@ 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, + TaskFilters, } 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; +import { PermissionCriteria } from '@backstage/plugin-permission-common'; /** * 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<TBody = JsonObject> = { taskId: string; @@ -117,6 +42,7 @@ export type TaskStoreEmitOptions<TBody = JsonObject> = { * 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 +54,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 +63,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 +74,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 +83,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 +93,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<void>; @@ -171,7 +102,7 @@ export interface TaskStore { options: TaskStoreCreateTaskOptions, ): Promise<TaskStoreCreateTaskResult>; - retryTask?(options: { taskId: string }): Promise<void>; + retryTask?(options: { secrets?: TaskSecrets; taskId: string }): Promise<void>; recoverTasks?( options: TaskStoreRecoverTaskOptions, @@ -202,6 +133,7 @@ export interface TaskStore { limit?: number; offset?: number; }; + permissionFilters?: PermissionCriteria<TaskFilters>; order?: { order: 'asc' | 'desc'; field: string }[]; }): Promise<{ tasks: SerializedTask[]; totalTasks?: number }>; diff --git a/plugins/scaffolder-backend/src/schema/openapi.yaml b/plugins/scaffolder-backend/src/schema/openapi.yaml new file mode 100644 index 0000000000..b89f54603c --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi.yaml @@ -0,0 +1,887 @@ +openapi: 3.0.3 +info: + title: scaffolder + version: '1' + description: The Backstage backend plugin that helps you create new things + license: + name: Apache-2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + contact: {} +servers: + - url: / +components: + examples: {} + headers: {} + parameters: + createdBy: + name: createdBy + in: query + description: Created by + required: false + allowReserved: true + schema: + type: array + items: + type: string + eventsAfter: + name: after + in: query + description: Offset event ID to stream events after. + required: false + allowReserved: true + schema: + type: integer + kind: + name: kind + in: path + required: true + allowReserved: true + schema: + type: string + limit: + name: limit + in: query + description: Number of records to return in the response. + required: false + allowReserved: true + schema: + type: integer + minimum: 0 + namespace: + name: namespace + in: path + required: true + allowReserved: true + schema: + type: string + name: + name: name + in: path + required: true + allowReserved: true + schema: + type: string + offset: + name: offset + in: query + description: Number of records to skip in the query page. + required: false + allowReserved: true + schema: + type: integer + minimum: 0 + order: + name: order + in: query + description: Order + required: false + allowReserved: true + schema: + type: array + items: + type: string + status: + name: status + in: query + description: Status + required: false + allowReserved: true + schema: + type: array + items: + type: string + taskId: + name: taskId + in: path + required: true + allowReserved: true + schema: + type: string + requestBodies: {} + responses: + ErrorResponse: + description: An error response from the backend. + content: + application/json; charset=utf-8: + schema: + $ref: '#/components/schemas/Error' + schemas: + Action: + type: object + properties: + id: + type: string + description: + type: string + "'examples'": + type: array + items: + $ref: '#/components/schemas/ActionExample' + schema: + type: object + properties: + input: + $ref: '#/components/schemas/JsonObject' + output: + $ref: '#/components/schemas/JsonObject' + required: + - id + description: The response shape for a single action in the `listActions` call to the `scaffolder-backend` + ActionExample: + type: object + properties: + description: + type: string + example: + type: string + required: + - description + - example + description: A single action example + DryRunResult: + type: object + properties: + log: + type: array + items: + type: object + properties: + body: + allOf: + - $ref: '#/components/schemas/JsonObject' + - type: object + properties: + message: + type: string + status: + $ref: '#/components/schemas/TaskStatus' + stepId: + type: string + required: + - message + required: + - body + directoryContents: + type: array + items: + $ref: '#/components/schemas/SerializedFile' + output: + $ref: '#/components/schemas/JsonObject' + required: + - log + - directoryContents + - output + Error: + type: object + properties: + error: + type: object + properties: + name: + type: string + message: + type: string + stack: + type: string + code: + type: string + required: + - name + - message + request: + type: object + properties: + method: + type: string + url: + type: string + required: + - method + - url + response: + type: object + properties: + statusCode: + type: number + required: + - statusCode + required: + - error + - response + additionalProperties: {} + JsonArray: + type: array + items: + $ref: '#/components/schemas/JsonValue' + description: A type representing all allowed JSON array values. + JsonObject: + type: object + properties: {} + description: A type representing all allowed JSON object values. + additionalProperties: {} + JsonPrimitive: + oneOf: + - type: boolean + - type: number + - type: string + - type: object + nullable: true + description: A type representing all allowed JSON primitive values. + JsonValue: + oneOf: + - $ref: '#/components/schemas/JsonObject' + - $ref: '#/components/schemas/JsonArray' + - $ref: '#/components/schemas/JsonPrimitive' + description: A type representing all allowed JSON values. + ListActionsResponse: + type: array + items: + $ref: '#/components/schemas/Action' + description: The response shape for the `listActions` call to the `scaffolder-backend` + ListTasksResponse: + type: object + properties: + tasks: + type: array + items: + $ref: '#/components/schemas/SerializedTask' + totalTasks: + type: number + required: + - tasks + description: The response shape for the `listTasks` call to the `scaffolder-backend` + ListTemplatingExtensionsResponse: + type: object + properties: + filters: + type: object + additionalProperties: + $ref: '#/components/schemas/TemplateFilter' + globals: + type: object + properties: + functions: + type: object + additionalProperties: + $ref: '#/components/schemas/TemplateGlobalFunction' + values: + type: object + additionalProperties: + $ref: '#/components/schemas/TemplateGlobalValue' + required: + - functions + - values + required: + - filters + - globals + description: The response shape for the `listTemplatingExtensions` call to the `scaffolder-backend` + ScaffolderScaffoldOptions: + type: object + properties: + templateRef: + type: string + values: + type: object + additionalProperties: {} + secrets: + type: object + additionalProperties: + type: string + required: + - templateRef + - values + description: The input options to the `scaffold` method of the `ScaffolderClient`. + ScaffolderUsageExample: + type: object + properties: + description: + type: string + example: + type: string + notes: + type: string + required: + - example + description: A single scaffolder usage example + SerializedFile: + type: object + properties: + path: + type: string + content: + type: string + executable: + type: boolean + symlink: + type: boolean + required: + - path + - content + SerializedTaskEvent: + type: object + properties: + id: + type: integer + isTaskRecoverable: + type: boolean + taskId: + type: string + body: + allOf: + - $ref: '#/components/schemas/JsonObject' + - type: object + properties: + message: + type: string + status: + $ref: '#/components/schemas/TaskStatus' + stepId: + type: string + required: + - message + type: + $ref: '#/components/schemas/TaskEventType' + createdAt: + type: string + required: + - id + - taskId + - body + - type + - createdAt + description: SerializedTaskEvent + SerializedTask: + type: object + properties: + id: + type: string + spec: + type: object + status: + $ref: '#/components/schemas/TaskStatus' + createdAt: + type: string + lastHeartbeatAt: + type: string + createdBy: + type: string + secrets: + $ref: '#/components/schemas/TaskSecrets' + state: + $ref: '#/components/schemas/JsonObject' + required: + - id + - spec + - status + - createdAt + description: SerializedTask + TaskEventType: + type: string + description: TaskEventType + enum: + - cancelled + - completion + - log + - recovered + TaskSecrets: + allOf: + - type: object + additionalProperties: + type: string + - type: object + properties: + backstageToken: + type: string + description: TaskSecrets + TaskStatus: + type: string + enum: + - cancelled + - completed + - failed + - open + - processing + - skipped + description: The status of each step of the Task + TemplateGlobalFunction: + type: object + properties: + description: + type: string + schema: + type: object + properties: + arguments: + type: array + items: + $ref: '#/components/schemas/JsonObject' + output: + $ref: '#/components/schemas/JsonObject' + "'examples'": + type: array + items: + $ref: '#/components/schemas/ScaffolderUsageExample' + description: The response shape for a single global function in the `listTemplatingExtensions` call to the `scaffolder-backend` + TemplateGlobalValue: + type: object + properties: + description: + type: string + value: + type: object + nullable: true + required: + - value + description: The response shape for a single global value in the `listTemplatingExtensions` call to the `scaffolder-backend` + TemplateFilter: + type: object + properties: + description: + type: string + schema: + type: object + properties: + input: + $ref: '#/components/schemas/JsonObject' + arguments: + type: array + items: + $ref: '#/components/schemas/JsonObject' + output: + $ref: '#/components/schemas/JsonObject' + "'examples'": + type: array + items: + $ref: '#/components/schemas/ScaffolderUsageExample' + description: The response shape for a single filter in the `listTemplatingExtensions` call to the `scaffolder-backend` + TemplateParameterSchema: + type: object + properties: + title: + type: string + description: + type: string + steps: + type: array + items: + type: object + properties: + title: + type: string + description: + type: string + schema: + type: object + required: + - title + - schema + required: + - title + - steps + description: |- + The shape of each entry of parameters which gets rendered + as a separate step in the wizard input + additionalProperties: {} + ValidationError: + type: object + properties: + path: + type: array + items: + oneOf: + - type: string + - type: integer + property: + type: string + message: + type: string + instance: + type: object + name: + type: string + argument: + oneOf: + - type: boolean + - type: number + - type: object + - type: string + stack: + type: string + required: + - path + - property + - message + - schema + - instance + - name + - argument + - stack + additionalProperties: {} + securitySchemes: + JWT: + type: http + scheme: bearer + bearerFormat: JWT +paths: + /v2/templates/{namespace}/{kind}/{name}/parameter-schema: + get: + operationId: GetTemplateParameterSchema + description: Get template parameter schema. + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/TemplateParameterSchema' + '400': + $ref: '#/components/responses/ErrorResponse' + default: + $ref: '#/components/responses/ErrorResponse' + security: + - {} + - JWT: [] + parameters: + - $ref: '#/components/parameters/namespace' + - $ref: '#/components/parameters/kind' + - $ref: '#/components/parameters/name' + + /v2/actions: + get: + operationId: ListActions + description: Returns a list of all installed actions. + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/ListActionsResponse' + security: + - {} + - JWT: [] + parameters: [] + + /v2/tasks: + get: + operationId: ListTasks + description: Returns a list of tasks, filtering by ownership and/or status if given. + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/ListTasksResponse' + security: + - {} + - JWT: [] + parameters: + - $ref: '#/components/parameters/createdBy' + - $ref: '#/components/parameters/limit' + - $ref: '#/components/parameters/offset' + - $ref: '#/components/parameters/order' + - $ref: '#/components/parameters/status' + post: + operationId: Scaffold + description: |- + Executes the scaffolding of a component, given a template and its + parameter values. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ScaffolderScaffoldOptions' + responses: + '201': + description: Created + content: + application/json: + schema: + type: object + properties: + id: + type: string + required: + - id + '400': + description: Validation errors. + content: + application/json: + schema: + type: object + properties: + errors: + type: array + items: + $ref: '#/components/schemas/ValidationError' + required: + - errors + security: + - {} + - JWT: [] + parameters: [] + + /v2/tasks/{taskId}: + get: + operationId: GetTask + description: Get a task by ID. + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/SerializedTask' + '403': + $ref: '#/components/responses/ErrorResponse' + security: + - {} + - JWT: [] + parameters: + - $ref: '#/components/parameters/taskId' + + /v2/tasks/{taskId}/cancel: + post: + operationId: CancelTask + description: Sends a signal to a task broker to cancel the running task by taskId. + responses: + '200': + description: Ok + content: + application/json: + schema: + type: object + properties: + status: + $ref: '#/components/schemas/TaskStatus' + security: + - {} + - JWT: [] + parameters: + - $ref: '#/components/parameters/taskId' + + /v2/tasks/{taskId}/retry: + post: + operationId: Retry + description: Starts the task again from the point where it failed. + requestBody: + # this should probably be marked as required, but for legacy reasons we're not. + required: false + content: + application/json: + schema: + type: object + properties: + secrets: + type: object + additionalProperties: + type: string + responses: + '201': + description: Ok + content: + application/json: + schema: + type: object + properties: + id: + type: string + required: + - id + security: + - {} + - JWT: [] + parameters: + - $ref: '#/components/parameters/taskId' + + /v2/tasks/{taskId}/events: + get: + operationId: StreamLogsPolling + description: Get events for a task by ID. + responses: + '200': + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/SerializedTaskEvent' + '403': + $ref: '#/components/responses/ErrorResponse' + parameters: + - $ref: '#/components/parameters/eventsAfter' + - $ref: '#/components/parameters/taskId' + + /v2/dry-run: + post: + operationId: DryRun + description: Perform a dry-run of a template + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + template: + type: object + values: + $ref: '#/components/schemas/JsonObject' + secrets: + type: object + additionalProperties: + type: string + directoryContents: + type: array + items: + type: object + properties: + path: + type: string + base64Content: + type: string + required: + - template + - values + - directoryContents + responses: + '200': + description: Ok + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/DryRunResult' + - type: object + properties: + steps: + type: array + items: + type: object + properties: + id: + type: string + name: + type: string + action: + type: string + required: + - id + - name + - action + additionalProperties: {} + directoryContents: + type: array + items: + type: object + properties: + path: + type: string + executable: + type: boolean + base64Content: + type: string + required: + - path + - base64Content + required: + - steps + '400': + description: Validation errors. + content: + application/json: + schema: + type: object + properties: + errors: + type: array + items: + $ref: '#/components/schemas/ValidationError' + required: + - errors + parameters: [] + + /v2/autocomplete/{provider}/{resource}: + post: + operationId: Autocomplete + description: Perform an autocomplete for the given provider and resource. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + context: + type: object + additionalProperties: + type: string + token: + type: string + required: + - context + - token + responses: + '200': + description: Ok + content: + application/json: + schema: + type: object + properties: + results: + type: array + items: + type: object + properties: + title: + type: string + id: + type: string + required: + - id + required: + - results + '400': + description: Unsupported provider. + content: + application/json: + schema: + type: object + properties: + message: + type: string + name: + type: string + parameters: + - in: path + name: provider + required: true + allowReserved: true + schema: + type: string + - in: path + name: resource + required: true + allowReserved: true + schema: + type: string + + /v2/templating-extensions: + get: + operationId: ListTemplatingExtensions + description: Returns a structure describing the available templating extensions. + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/ListTemplatingExtensionsResponse' + security: + - {} + - JWT: [] + parameters: [] diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/apis/Api.server.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/apis/Api.server.ts new file mode 100644 index 0000000000..c5bd9f0ac2 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/apis/Api.server.ts @@ -0,0 +1,163 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { Action } from '../models/Action.model'; +import { Autocomplete200Response } from '../models/Autocomplete200Response.model'; +import { Autocomplete400Response } from '../models/Autocomplete400Response.model'; +import { AutocompleteRequest } from '../models/AutocompleteRequest.model'; +import { CancelTask200Response } from '../models/CancelTask200Response.model'; +import { DryRun200Response } from '../models/DryRun200Response.model'; +import { DryRunRequest } from '../models/DryRunRequest.model'; +import { ListTasksResponse } from '../models/ListTasksResponse.model'; +import { ListTemplatingExtensionsResponse } from '../models/ListTemplatingExtensionsResponse.model'; +import { RetryRequest } from '../models/RetryRequest.model'; +import { Scaffold201Response } from '../models/Scaffold201Response.model'; +import { Scaffold400Response } from '../models/Scaffold400Response.model'; +import { ScaffolderScaffoldOptions } from '../models/ScaffolderScaffoldOptions.model'; +import { SerializedTask } from '../models/SerializedTask.model'; +import { SerializedTaskEvent } from '../models/SerializedTaskEvent.model'; +import { TemplateParameterSchema } from '../models/TemplateParameterSchema.model'; + +/** + * @public + */ +export type Autocomplete = { + path: { + provider: string; + resource: string; + }; + body: AutocompleteRequest; + response: Autocomplete200Response | Autocomplete400Response; +}; +/** + * @public + */ +export type CancelTask = { + path: { + taskId: string; + }; + response: CancelTask200Response; +}; +/** + * @public + */ +export type DryRun = { + body: DryRunRequest; + response: DryRun200Response | Scaffold400Response; +}; +/** + * @public + */ +export type GetTask = { + path: { + taskId: string; + }; + response: SerializedTask | Error; +}; +/** + * @public + */ +export type GetTemplateParameterSchema = { + path: { + namespace: string; + kind: string; + name: string; + }; + response: TemplateParameterSchema | Error | Error; +}; +/** + * @public + */ +export type ListActions = { + response: Array<Action>; +}; +/** + * @public + */ +export type ListTasks = { + query: { + createdBy?: Array<string>; + limit?: number; + offset?: number; + order?: Array<string>; + status?: Array<string>; + }; + response: ListTasksResponse; +}; +/** + * @public + */ +export type ListTemplatingExtensions = { + response: ListTemplatingExtensionsResponse; +}; +/** + * @public + */ +export type Retry = { + path: { + taskId: string; + }; + body: RetryRequest; + response: Scaffold201Response; +}; +/** + * @public + */ +export type Scaffold = { + body: ScaffolderScaffoldOptions; + response: Scaffold201Response | Scaffold400Response; +}; +/** + * @public + */ +export type StreamLogsPolling = { + path: { + taskId: string; + }; + query: { + after?: number; + }; + response: Array<SerializedTaskEvent> | Error; +}; + +export type EndpointMap = { + '#post|/v2/autocomplete/{provider}/{resource}': Autocomplete; + + '#post|/v2/tasks/{taskId}/cancel': CancelTask; + + '#post|/v2/dry-run': DryRun; + + '#get|/v2/tasks/{taskId}': GetTask; + + '#get|/v2/templates/{namespace}/{kind}/{name}/parameter-schema': GetTemplateParameterSchema; + + '#get|/v2/actions': ListActions; + + '#get|/v2/tasks': ListTasks; + + '#get|/v2/templating-extensions': ListTemplatingExtensions; + + '#post|/v2/tasks/{taskId}/retry': Retry; + + '#post|/v2/tasks': Scaffold; + + '#get|/v2/tasks/{taskId}/events': StreamLogsPolling; +}; diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/apis/index.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/apis/index.ts new file mode 100644 index 0000000000..8d81cbaf39 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/apis/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +export * from './Api.server'; diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/index.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/index.ts new file mode 100644 index 0000000000..dec4b8804e --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './apis'; +export * from './router'; diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Action.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Action.model.ts new file mode 100644 index 0000000000..f9c028f3e3 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Action.model.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { ActionExample } from '../models/ActionExample.model'; +import { ActionSchema } from '../models/ActionSchema.model'; + +/** + * The response shape for a single action in the `listActions` call to the `scaffolder-backend` + * @public + */ +export interface Action { + id: string; + description?: string; + examples?: Array<ActionExample>; + schema?: ActionSchema; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ActionExample.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ActionExample.model.ts new file mode 100644 index 0000000000..50550e2883 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ActionExample.model.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * A single action example + * @public + */ +export interface ActionExample { + description: string; + example: string; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ActionSchema.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ActionSchema.model.ts new file mode 100644 index 0000000000..35d2014181 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ActionSchema.model.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface ActionSchema { + /** + * A type representing all allowed JSON object values. + */ + input?: { [key: string]: any }; + /** + * A type representing all allowed JSON object values. + */ + output?: { [key: string]: any }; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete200Response.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete200Response.model.ts new file mode 100644 index 0000000000..e3eec0aa20 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete200Response.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { Autocomplete200ResponseResultsInner } from '../models/Autocomplete200ResponseResultsInner.model'; + +/** + * @public + */ +export interface Autocomplete200Response { + results: Array<Autocomplete200ResponseResultsInner>; +} diff --git a/packages/frontend-plugin-api/src/components/createComponentRef.tsx b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete200ResponseResultsInner.model.ts similarity index 61% rename from packages/frontend-plugin-api/src/components/createComponentRef.tsx rename to plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete200ResponseResultsInner.model.ts index 88181dd079..00ccb41b0f 100644 --- a/packages/frontend-plugin-api/src/components/createComponentRef.tsx +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete200ResponseResultsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,21 +14,14 @@ * limitations under the License. */ -/** @public */ -export type ComponentRef<T extends {} = {}> = { - id: string; - T: T; -}; +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** -/** @public */ -export function createComponentRef<T extends {} = {}>(options: { +/** + * @public + */ +export interface Autocomplete200ResponseResultsInner { + title?: string; id: string; -}): ComponentRef<T> { - const { id } = options; - return { - id, - toString() { - return `ComponentRef{id=${id}}`; - }, - } as ComponentRef<T>; } diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete400Response.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete400Response.model.ts new file mode 100644 index 0000000000..1456babb96 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Autocomplete400Response.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface Autocomplete400Response { + message?: string; + name?: string; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/AutocompleteRequest.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/AutocompleteRequest.model.ts new file mode 100644 index 0000000000..769a929918 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/AutocompleteRequest.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface AutocompleteRequest { + context: { [key: string]: string }; + token: string; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/CancelTask200Response.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/CancelTask200Response.model.ts new file mode 100644 index 0000000000..f29c509d41 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/CancelTask200Response.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { TaskStatus } from '../models/TaskStatus.model'; + +/** + * @public + */ +export interface CancelTask200Response { + status?: TaskStatus; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200Response.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200Response.model.ts new file mode 100644 index 0000000000..62e3689d5d --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200Response.model.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { DryRun200ResponseAllOfDirectoryContentsInner } from '../models/DryRun200ResponseAllOfDirectoryContentsInner.model'; +import { DryRun200ResponseAllOfStepsInner } from '../models/DryRun200ResponseAllOfStepsInner.model'; +import { DryRunResultLogInner } from '../models/DryRunResultLogInner.model'; + +/** + * @public + */ +export interface DryRun200Response { + log: Array<DryRunResultLogInner>; + directoryContents: Array<DryRun200ResponseAllOfDirectoryContentsInner>; + /** + * A type representing all allowed JSON object values. + */ + output: { [key: string]: any }; + steps: Array<DryRun200ResponseAllOfStepsInner>; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOf.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOf.model.ts new file mode 100644 index 0000000000..5b6c2ff9c7 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOf.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { DryRun200ResponseAllOfDirectoryContentsInner } from '../models/DryRun200ResponseAllOfDirectoryContentsInner.model'; +import { DryRun200ResponseAllOfStepsInner } from '../models/DryRun200ResponseAllOfStepsInner.model'; + +/** + * @public + */ +export interface DryRun200ResponseAllOf { + steps: Array<DryRun200ResponseAllOfStepsInner>; + directoryContents?: Array<DryRun200ResponseAllOfDirectoryContentsInner>; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOfDirectoryContentsInner.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOfDirectoryContentsInner.model.ts new file mode 100644 index 0000000000..2646ca49f9 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOfDirectoryContentsInner.model.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface DryRun200ResponseAllOfDirectoryContentsInner { + path: string; + executable?: boolean; + base64Content: string; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOfStepsInner.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOfStepsInner.model.ts new file mode 100644 index 0000000000..2a462edefa --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRun200ResponseAllOfStepsInner.model.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface DryRun200ResponseAllOfStepsInner { + [key: string]: any; + + id: string; + name: string; + action: string; +} diff --git a/packages/canon/src/components/Button/Button.props.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunRequest.model.ts similarity index 50% rename from packages/canon/src/components/Button/Button.props.ts rename to plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunRequest.model.ts index e363360afa..212b36c26b 100644 --- a/packages/canon/src/components/Button/Button.props.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunRequest.model.ts @@ -14,28 +14,20 @@ * limitations under the License. */ -import type { PropDef, GetPropDefTypes } from '../../props/prop-def'; +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { DryRunRequestDirectoryContentsInner } from '../models/DryRunRequestDirectoryContentsInner.model'; -/** @public */ -export const buttonPropDefs = { - variant: { - type: 'enum', - values: ['primary', 'secondary'], - className: 'canon-Button--variant', - default: 'primary', - responsive: true, - }, - size: { - type: 'enum', - values: ['small', 'medium'], - className: 'canon-Button--size', - default: 'medium', - responsive: true, - }, -} satisfies { - variant: PropDef<'primary' | 'secondary'>; - size: PropDef<'small' | 'medium'>; -}; - -/** @public */ -export type ButtonOwnProps = GetPropDefTypes<typeof buttonPropDefs>; +/** + * @public + */ +export interface DryRunRequest { + template: any; + /** + * A type representing all allowed JSON object values. + */ + values: { [key: string]: any }; + secrets?: { [key: string]: string }; + directoryContents: Array<DryRunRequestDirectoryContentsInner>; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunRequestDirectoryContentsInner.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunRequestDirectoryContentsInner.model.ts new file mode 100644 index 0000000000..ddcae4fd9d --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunRequestDirectoryContentsInner.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface DryRunRequestDirectoryContentsInner { + path?: string; + base64Content?: string; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResult.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResult.model.ts new file mode 100644 index 0000000000..6e2ad23ef4 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResult.model.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { DryRunResultLogInner } from '../models/DryRunResultLogInner.model'; +import { SerializedFile } from '../models/SerializedFile.model'; + +/** + * @public + */ +export interface DryRunResult { + log: Array<DryRunResultLogInner>; + directoryContents: Array<SerializedFile>; + /** + * A type representing all allowed JSON object values. + */ + output: { [key: string]: any }; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInner.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInner.model.ts new file mode 100644 index 0000000000..4c6b786d82 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInner.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { DryRunResultLogInnerBody } from '../models/DryRunResultLogInnerBody.model'; + +/** + * @public + */ +export interface DryRunResultLogInner { + body: DryRunResultLogInnerBody; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInnerBody.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInnerBody.model.ts new file mode 100644 index 0000000000..e3b19be045 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInnerBody.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { TaskStatus } from '../models/TaskStatus.model'; + +/** + * @public + */ +export interface DryRunResultLogInnerBody { + message: string; + status?: TaskStatus; + stepId?: string; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInnerBodyAllOf.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInnerBodyAllOf.model.ts new file mode 100644 index 0000000000..e67240ee54 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/DryRunResultLogInnerBodyAllOf.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { TaskStatus } from '../models/TaskStatus.model'; + +/** + * @public + */ +export interface DryRunResultLogInnerBodyAllOf { + message: string; + status?: TaskStatus; + stepId?: string; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorError.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorError.model.ts new file mode 100644 index 0000000000..e0265e95d7 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorError.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface ErrorError { + name: string; + message: string; + stack?: string; + code?: string; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts new file mode 100644 index 0000000000..3eb5e15740 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorRequest.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface ErrorRequest { + method: string; + url: string; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts new file mode 100644 index 0000000000..edbcc32df7 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface ErrorResponse { + statusCode: number; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/JsonPrimitive.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/JsonPrimitive.model.ts new file mode 100644 index 0000000000..6aa6394c14 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/JsonPrimitive.model.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * A type representing all allowed JSON primitive values. + * @public + */ +export type JsonPrimitive = any | boolean | number | string; diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/JsonValue.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/JsonValue.model.ts new file mode 100644 index 0000000000..9fe2ac9de3 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/JsonValue.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { JsonPrimitive } from '../models/JsonPrimitive.model'; + +/** + * A type representing all allowed JSON values. + * @public + */ +export type JsonValue = + | Array<JsonValue> + | JsonPrimitive + | { [key: string]: any }; diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTasksResponse.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTasksResponse.model.ts new file mode 100644 index 0000000000..9347c766a9 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTasksResponse.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { SerializedTask } from '../models/SerializedTask.model'; + +/** + * The response shape for the `listTasks` call to the `scaffolder-backend` + * @public + */ +export interface ListTasksResponse { + tasks: Array<SerializedTask>; + totalTasks?: number; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTemplatingExtensionsResponse.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTemplatingExtensionsResponse.model.ts new file mode 100644 index 0000000000..74b7cd3780 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTemplatingExtensionsResponse.model.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { ListTemplatingExtensionsResponseGlobals } from '../models/ListTemplatingExtensionsResponseGlobals.model'; +import { TemplateFilter } from '../models/TemplateFilter.model'; + +/** + * The response shape for the `listTemplatingExtensions` call to the `scaffolder-backend` + * @public + */ +export interface ListTemplatingExtensionsResponse { + filters: { [key: string]: TemplateFilter }; + globals: ListTemplatingExtensionsResponseGlobals; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTemplatingExtensionsResponseGlobals.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTemplatingExtensionsResponseGlobals.model.ts new file mode 100644 index 0000000000..1f022a0675 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ListTemplatingExtensionsResponseGlobals.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { TemplateGlobalFunction } from '../models/TemplateGlobalFunction.model'; +import { TemplateGlobalValue } from '../models/TemplateGlobalValue.model'; + +/** + * @public + */ +export interface ListTemplatingExtensionsResponseGlobals { + functions: { [key: string]: TemplateGlobalFunction }; + values: { [key: string]: TemplateGlobalValue }; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ModelError.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ModelError.model.ts new file mode 100644 index 0000000000..958fde7d0b --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ModelError.model.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { ErrorError } from '../models/ErrorError.model'; +import { ErrorRequest } from '../models/ErrorRequest.model'; +import { ErrorResponse } from '../models/ErrorResponse.model'; + +/** + * @public + */ +export interface ModelError { + [key: string]: any; + + error: ErrorError; + request?: ErrorRequest; + response: ErrorResponse; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/RetryRequest.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/RetryRequest.model.ts new file mode 100644 index 0000000000..858fa67566 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/RetryRequest.model.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface RetryRequest { + secrets?: { [key: string]: string }; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Scaffold201Response.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Scaffold201Response.model.ts new file mode 100644 index 0000000000..1271642e81 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Scaffold201Response.model.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface Scaffold201Response { + id: string; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/Scaffold400Response.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Scaffold400Response.model.ts new file mode 100644 index 0000000000..e3cb7eaa45 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/Scaffold400Response.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { ValidationError } from '../models/ValidationError.model'; + +/** + * @public + */ +export interface Scaffold400Response { + errors: Array<ValidationError>; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ScaffolderScaffoldOptions.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ScaffolderScaffoldOptions.model.ts new file mode 100644 index 0000000000..93f4ae71c0 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ScaffolderScaffoldOptions.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * The input options to the `scaffold` method of the `ScaffolderClient`. + * @public + */ +export interface ScaffolderScaffoldOptions { + templateRef: string; + values: { [key: string]: any }; + secrets?: { [key: string]: string }; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ScaffolderUsageExample.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ScaffolderUsageExample.model.ts new file mode 100644 index 0000000000..3e0f4ef866 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ScaffolderUsageExample.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * A single scaffolder usage example + * @public + */ +export interface ScaffolderUsageExample { + description?: string; + example: string; + notes?: string; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedFile.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedFile.model.ts new file mode 100644 index 0000000000..65a3c714a7 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedFile.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface SerializedFile { + path: string; + content: string; + executable?: boolean; + symlink?: boolean; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedTask.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedTask.model.ts new file mode 100644 index 0000000000..67cbf14279 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedTask.model.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { TaskSecrets } from '../models/TaskSecrets.model'; +import { TaskStatus } from '../models/TaskStatus.model'; + +/** + * SerializedTask + * @public + */ +export interface SerializedTask { + id: string; + spec: any; + status: TaskStatus; + createdAt: string; + lastHeartbeatAt?: string; + createdBy?: string; + secrets?: TaskSecrets; + /** + * A type representing all allowed JSON object values. + */ + state?: { [key: string]: any }; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedTaskEvent.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedTaskEvent.model.ts new file mode 100644 index 0000000000..6e4ddaa296 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/SerializedTaskEvent.model.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { DryRunResultLogInnerBody } from '../models/DryRunResultLogInnerBody.model'; +import { TaskEventType } from '../models/TaskEventType.model'; + +/** + * SerializedTaskEvent + * @public + */ +export interface SerializedTaskEvent { + id: number; + isTaskRecoverable?: boolean; + taskId: string; + body: DryRunResultLogInnerBody; + type: TaskEventType; + createdAt: string; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskEventType.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskEventType.model.ts new file mode 100644 index 0000000000..d32d77a448 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskEventType.model.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export type TaskEventType = 'cancelled' | 'completion' | 'log' | 'recovered'; diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskSecrets.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskSecrets.model.ts new file mode 100644 index 0000000000..c78c39c1ec --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskSecrets.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * TaskSecrets + * @public + */ +export interface TaskSecrets { + backstageToken?: string; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskSecretsAllOf.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskSecretsAllOf.model.ts new file mode 100644 index 0000000000..0b33852cc9 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskSecretsAllOf.model.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface TaskSecretsAllOf { + backstageToken?: string; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskStatus.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskStatus.model.ts new file mode 100644 index 0000000000..1f209a3656 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TaskStatus.model.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export type TaskStatus = + | 'cancelled' + | 'completed' + | 'failed' + | 'open' + | 'processing' + | 'skipped'; diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateFilter.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateFilter.model.ts new file mode 100644 index 0000000000..7c631978e6 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateFilter.model.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { ScaffolderUsageExample } from '../models/ScaffolderUsageExample.model'; +import { TemplateFilterSchema } from '../models/TemplateFilterSchema.model'; + +/** + * The response shape for a single filter in the `listTemplatingExtensions` call to the `scaffolder-backend` + * @public + */ +export interface TemplateFilter { + description?: string; + schema?: TemplateFilterSchema; + examples?: Array<ScaffolderUsageExample>; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateFilterSchema.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateFilterSchema.model.ts new file mode 100644 index 0000000000..5a95a58ead --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateFilterSchema.model.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface TemplateFilterSchema { + /** + * A type representing all allowed JSON object values. + */ + input?: { [key: string]: any }; + arguments?: Array<{ [key: string]: any }>; + /** + * A type representing all allowed JSON object values. + */ + output?: { [key: string]: any }; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalFunction.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalFunction.model.ts new file mode 100644 index 0000000000..ee19ce723b --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalFunction.model.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { ScaffolderUsageExample } from '../models/ScaffolderUsageExample.model'; +import { TemplateGlobalFunctionSchema } from '../models/TemplateGlobalFunctionSchema.model'; + +/** + * The response shape for a single global function in the `listTemplatingExtensions` call to the `scaffolder-backend` + * @public + */ +export interface TemplateGlobalFunction { + description?: string; + schema?: TemplateGlobalFunctionSchema; + examples?: Array<ScaffolderUsageExample>; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalFunctionSchema.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalFunctionSchema.model.ts new file mode 100644 index 0000000000..b3a39c3b24 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalFunctionSchema.model.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface TemplateGlobalFunctionSchema { + arguments?: Array<{ [key: string]: any }>; + /** + * A type representing all allowed JSON object values. + */ + output?: { [key: string]: any }; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalValue.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalValue.model.ts new file mode 100644 index 0000000000..95d177a05a --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateGlobalValue.model.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * The response shape for a single global value in the `listTemplatingExtensions` call to the `scaffolder-backend` + * @public + */ +export interface TemplateGlobalValue { + description?: string; + value: any | null; +} diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateParameterSchema.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateParameterSchema.model.ts new file mode 100644 index 0000000000..8638ee8114 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateParameterSchema.model.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { TemplateParameterSchemaStepsInner } from '../models/TemplateParameterSchemaStepsInner.model'; + +/** + * The shape of each entry of parameters which gets rendered as a separate step in the wizard input + * @public + */ +export interface TemplateParameterSchema { + [key: string]: any; + + title: string; + description?: string; + steps: Array<TemplateParameterSchemaStepsInner>; +} diff --git a/packages/canon/src/components/Table/TableCellLink/types.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateParameterSchemaStepsInner.model.ts similarity index 64% rename from packages/canon/src/components/Table/TableCellLink/types.ts rename to plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateParameterSchemaStepsInner.model.ts index 51df3724cf..2f7892fd41 100644 --- a/packages/canon/src/components/Table/TableCellLink/types.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/TemplateParameterSchemaStepsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,15 @@ * limitations under the License. */ -import type { useRender } from '@base-ui-components/react/use-render'; +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** -/** @public */ -export interface TableCellLinkProps - extends React.HTMLAttributes<HTMLDivElement> { +/** + * @public + */ +export interface TemplateParameterSchemaStepsInner { title: string; description?: string; - href: string; - render?: useRender.ComponentProps<'a'>['render']; + schema: any; } diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationError.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationError.model.ts new file mode 100644 index 0000000000..7e762f616c --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationError.model.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { ValidationErrorArgument } from '../models/ValidationErrorArgument.model'; +import { ValidationErrorPathInner } from '../models/ValidationErrorPathInner.model'; + +/** + * @public + */ +export interface ValidationError { + [key: string]: any; + + path: Array<ValidationErrorPathInner>; + property: string; + message: string; + instance: any; + name: string; + argument: ValidationErrorArgument; + stack: string; +} diff --git a/canon-docs/src/components/Chip/Chip.tsx b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationErrorArgument.model.ts similarity index 62% rename from canon-docs/src/components/Chip/Chip.tsx rename to plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationErrorArgument.model.ts index fd9a9f84c3..85c9576fd0 100644 --- a/canon-docs/src/components/Chip/Chip.tsx +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationErrorArgument.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,11 @@ * limitations under the License. */ -import { ReactNode } from 'react'; -import styles from './styles.module.css'; +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** -export const Chip = ({ - children, - head = false, -}: { - children: ReactNode; - head?: boolean; -}) => { - return ( - <span className={`${styles.chip} ${head ? styles.head : ''}`}> - {children} - </span> - ); -}; +/** + * @public + */ +export type ValidationErrorArgument = any | boolean | number | string; diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationErrorPathInner.model.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationErrorPathInner.model.ts new file mode 100644 index 0000000000..4535c735db --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/ValidationErrorPathInner.model.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export type ValidationErrorPathInner = number | string; diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/models/index.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/models/index.ts new file mode 100644 index 0000000000..1bc447fa3d --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/models/index.ts @@ -0,0 +1,65 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from '../models/Action.model'; +export * from '../models/ActionExample.model'; +export * from '../models/ActionSchema.model'; +export * from '../models/Autocomplete200Response.model'; +export * from '../models/Autocomplete200ResponseResultsInner.model'; +export * from '../models/Autocomplete400Response.model'; +export * from '../models/AutocompleteRequest.model'; +export * from '../models/CancelTask200Response.model'; +export * from '../models/DryRun200Response.model'; +export * from '../models/DryRun200ResponseAllOf.model'; +export * from '../models/DryRun200ResponseAllOfDirectoryContentsInner.model'; +export * from '../models/DryRun200ResponseAllOfStepsInner.model'; +export * from '../models/DryRunRequest.model'; +export * from '../models/DryRunRequestDirectoryContentsInner.model'; +export * from '../models/DryRunResult.model'; +export * from '../models/DryRunResultLogInner.model'; +export * from '../models/DryRunResultLogInnerBody.model'; +export * from '../models/DryRunResultLogInnerBodyAllOf.model'; +export * from '../models/ErrorError.model'; +export * from '../models/ErrorRequest.model'; +export * from '../models/ErrorResponse.model'; +export * from '../models/JsonPrimitive.model'; +export * from '../models/JsonValue.model'; +export * from '../models/ListTasksResponse.model'; +export * from '../models/ListTemplatingExtensionsResponse.model'; +export * from '../models/ListTemplatingExtensionsResponseGlobals.model'; +export * from '../models/ModelError.model'; +export * from '../models/RetryRequest.model'; +export * from '../models/Scaffold201Response.model'; +export * from '../models/Scaffold400Response.model'; +export * from '../models/ScaffolderScaffoldOptions.model'; +export * from '../models/ScaffolderUsageExample.model'; +export * from '../models/SerializedFile.model'; +export * from '../models/SerializedTask.model'; +export * from '../models/SerializedTaskEvent.model'; +export * from '../models/TaskEventType.model'; +export * from '../models/TaskSecrets.model'; +export * from '../models/TaskSecretsAllOf.model'; +export * from '../models/TaskStatus.model'; +export * from '../models/TemplateFilter.model'; +export * from '../models/TemplateFilterSchema.model'; +export * from '../models/TemplateGlobalFunction.model'; +export * from '../models/TemplateGlobalFunctionSchema.model'; +export * from '../models/TemplateGlobalValue.model'; +export * from '../models/TemplateParameterSchema.model'; +export * from '../models/TemplateParameterSchemaStepsInner.model'; +export * from '../models/ValidationError.model'; +export * from '../models/ValidationErrorArgument.model'; +export * from '../models/ValidationErrorPathInner.model'; diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/router.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/router.ts new file mode 100644 index 0000000000..669dd63878 --- /dev/null +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/router.ts @@ -0,0 +1,1328 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage/backend-openapi-utils'; +import { EndpointMap } from './apis'; + +export const spec = { + openapi: '3.0.3', + info: { + title: 'scaffolder', + version: '1', + description: + 'The Backstage backend plugin that helps you create new things', + license: { + name: 'Apache-2.0', + url: 'http://www.apache.org/licenses/LICENSE-2.0.html', + }, + contact: {}, + }, + servers: [ + { + url: '/', + }, + ], + components: { + examples: {}, + headers: {}, + parameters: { + createdBy: { + name: 'createdBy', + in: 'query', + description: 'Created by', + required: false, + allowReserved: true, + schema: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + eventsAfter: { + name: 'after', + in: 'query', + description: 'Offset event ID to stream events after.', + required: false, + allowReserved: true, + schema: { + type: 'integer', + }, + }, + kind: { + name: 'kind', + in: 'path', + required: true, + allowReserved: true, + schema: { + type: 'string', + }, + }, + limit: { + name: 'limit', + in: 'query', + description: 'Number of records to return in the response.', + required: false, + allowReserved: true, + schema: { + type: 'integer', + minimum: 0, + }, + }, + namespace: { + name: 'namespace', + in: 'path', + required: true, + allowReserved: true, + schema: { + type: 'string', + }, + }, + name: { + name: 'name', + in: 'path', + required: true, + allowReserved: true, + schema: { + type: 'string', + }, + }, + offset: { + name: 'offset', + in: 'query', + description: 'Number of records to skip in the query page.', + required: false, + allowReserved: true, + schema: { + type: 'integer', + minimum: 0, + }, + }, + order: { + name: 'order', + in: 'query', + description: 'Order', + required: false, + allowReserved: true, + schema: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + status: { + name: 'status', + in: 'query', + description: 'Status', + required: false, + allowReserved: true, + schema: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + taskId: { + name: 'taskId', + in: 'path', + required: true, + allowReserved: true, + schema: { + type: 'string', + }, + }, + }, + requestBodies: {}, + responses: { + ErrorResponse: { + description: 'An error response from the backend.', + content: { + 'application/json; charset=utf-8': { + schema: { + $ref: '#/components/schemas/Error', + }, + }, + }, + }, + }, + schemas: { + Action: { + type: 'object', + properties: { + id: { + type: 'string', + }, + description: { + type: 'string', + }, + "'examples'": { + type: 'array', + items: { + $ref: '#/components/schemas/ActionExample', + }, + }, + schema: { + type: 'object', + properties: { + input: { + $ref: '#/components/schemas/JsonObject', + }, + output: { + $ref: '#/components/schemas/JsonObject', + }, + }, + }, + }, + required: ['id'], + description: + 'The response shape for a single action in the `listActions` call to the `scaffolder-backend`', + }, + ActionExample: { + type: 'object', + properties: { + description: { + type: 'string', + }, + example: { + type: 'string', + }, + }, + required: ['description', 'example'], + description: 'A single action example', + }, + DryRunResult: { + type: 'object', + properties: { + log: { + type: 'array', + items: { + type: 'object', + properties: { + body: { + allOf: [ + { + $ref: '#/components/schemas/JsonObject', + }, + { + type: 'object', + properties: { + message: { + type: 'string', + }, + status: { + $ref: '#/components/schemas/TaskStatus', + }, + stepId: { + type: 'string', + }, + }, + required: ['message'], + }, + ], + }, + }, + required: ['body'], + }, + }, + directoryContents: { + type: 'array', + items: { + $ref: '#/components/schemas/SerializedFile', + }, + }, + output: { + $ref: '#/components/schemas/JsonObject', + }, + }, + required: ['log', 'directoryContents', 'output'], + }, + Error: { + type: 'object', + properties: { + error: { + type: 'object', + properties: { + name: { + type: 'string', + }, + message: { + type: 'string', + }, + stack: { + type: 'string', + }, + code: { + type: 'string', + }, + }, + required: ['name', 'message'], + }, + request: { + type: 'object', + properties: { + method: { + type: 'string', + }, + url: { + type: 'string', + }, + }, + required: ['method', 'url'], + }, + response: { + type: 'object', + properties: { + statusCode: { + type: 'number', + }, + }, + required: ['statusCode'], + }, + }, + required: ['error', 'response'], + additionalProperties: {}, + }, + JsonArray: { + type: 'array', + items: { + $ref: '#/components/schemas/JsonValue', + }, + description: 'A type representing all allowed JSON array values.', + }, + JsonObject: { + type: 'object', + properties: {}, + description: 'A type representing all allowed JSON object values.', + additionalProperties: {}, + }, + JsonPrimitive: { + oneOf: [ + { + type: 'boolean', + }, + { + type: 'number', + }, + { + type: 'string', + }, + { + type: 'object', + nullable: true, + }, + ], + description: 'A type representing all allowed JSON primitive values.', + }, + JsonValue: { + oneOf: [ + { + $ref: '#/components/schemas/JsonObject', + }, + { + $ref: '#/components/schemas/JsonArray', + }, + { + $ref: '#/components/schemas/JsonPrimitive', + }, + ], + description: 'A type representing all allowed JSON values.', + }, + ListActionsResponse: { + type: 'array', + items: { + $ref: '#/components/schemas/Action', + }, + description: + 'The response shape for the `listActions` call to the `scaffolder-backend`', + }, + ListTasksResponse: { + type: 'object', + properties: { + tasks: { + type: 'array', + items: { + $ref: '#/components/schemas/SerializedTask', + }, + }, + totalTasks: { + type: 'number', + }, + }, + required: ['tasks'], + description: + 'The response shape for the `listTasks` call to the `scaffolder-backend`', + }, + ListTemplatingExtensionsResponse: { + type: 'object', + properties: { + filters: { + type: 'object', + additionalProperties: { + $ref: '#/components/schemas/TemplateFilter', + }, + }, + globals: { + type: 'object', + properties: { + functions: { + type: 'object', + additionalProperties: { + $ref: '#/components/schemas/TemplateGlobalFunction', + }, + }, + values: { + type: 'object', + additionalProperties: { + $ref: '#/components/schemas/TemplateGlobalValue', + }, + }, + }, + required: ['functions', 'values'], + }, + }, + required: ['filters', 'globals'], + description: + 'The response shape for the `listTemplatingExtensions` call to the `scaffolder-backend`', + }, + ScaffolderScaffoldOptions: { + type: 'object', + properties: { + templateRef: { + type: 'string', + }, + values: { + type: 'object', + additionalProperties: {}, + }, + secrets: { + type: 'object', + additionalProperties: { + type: 'string', + }, + }, + }, + required: ['templateRef', 'values'], + description: + 'The input options to the `scaffold` method of the `ScaffolderClient`.', + }, + ScaffolderUsageExample: { + type: 'object', + properties: { + description: { + type: 'string', + }, + example: { + type: 'string', + }, + notes: { + type: 'string', + }, + }, + required: ['example'], + description: 'A single scaffolder usage example', + }, + SerializedFile: { + type: 'object', + properties: { + path: { + type: 'string', + }, + content: { + type: 'string', + }, + executable: { + type: 'boolean', + }, + symlink: { + type: 'boolean', + }, + }, + required: ['path', 'content'], + }, + SerializedTaskEvent: { + type: 'object', + properties: { + id: { + type: 'integer', + }, + isTaskRecoverable: { + type: 'boolean', + }, + taskId: { + type: 'string', + }, + body: { + allOf: [ + { + $ref: '#/components/schemas/JsonObject', + }, + { + type: 'object', + properties: { + message: { + type: 'string', + }, + status: { + $ref: '#/components/schemas/TaskStatus', + }, + stepId: { + type: 'string', + }, + }, + required: ['message'], + }, + ], + }, + type: { + $ref: '#/components/schemas/TaskEventType', + }, + createdAt: { + type: 'string', + }, + }, + required: ['id', 'taskId', 'body', 'type', 'createdAt'], + description: 'SerializedTaskEvent', + }, + SerializedTask: { + type: 'object', + properties: { + id: { + type: 'string', + }, + spec: { + type: 'object', + }, + status: { + $ref: '#/components/schemas/TaskStatus', + }, + createdAt: { + type: 'string', + }, + lastHeartbeatAt: { + type: 'string', + }, + createdBy: { + type: 'string', + }, + secrets: { + $ref: '#/components/schemas/TaskSecrets', + }, + state: { + $ref: '#/components/schemas/JsonObject', + }, + }, + required: ['id', 'spec', 'status', 'createdAt'], + description: 'SerializedTask', + }, + TaskEventType: { + type: 'string', + description: 'TaskEventType', + enum: ['cancelled', 'completion', 'log', 'recovered'], + }, + TaskSecrets: { + allOf: [ + { + type: 'object', + additionalProperties: { + type: 'string', + }, + }, + { + type: 'object', + properties: { + backstageToken: { + type: 'string', + }, + }, + }, + ], + description: 'TaskSecrets', + }, + TaskStatus: { + type: 'string', + enum: [ + 'cancelled', + 'completed', + 'failed', + 'open', + 'processing', + 'skipped', + ], + description: 'The status of each step of the Task', + }, + TemplateGlobalFunction: { + type: 'object', + properties: { + description: { + type: 'string', + }, + schema: { + type: 'object', + properties: { + arguments: { + type: 'array', + items: { + $ref: '#/components/schemas/JsonObject', + }, + }, + output: { + $ref: '#/components/schemas/JsonObject', + }, + }, + }, + "'examples'": { + type: 'array', + items: { + $ref: '#/components/schemas/ScaffolderUsageExample', + }, + }, + }, + description: + 'The response shape for a single global function in the `listTemplatingExtensions` call to the `scaffolder-backend`', + }, + TemplateGlobalValue: { + type: 'object', + properties: { + description: { + type: 'string', + }, + value: { + type: 'object', + nullable: true, + }, + }, + required: ['value'], + description: + 'The response shape for a single global value in the `listTemplatingExtensions` call to the `scaffolder-backend`', + }, + TemplateFilter: { + type: 'object', + properties: { + description: { + type: 'string', + }, + schema: { + type: 'object', + properties: { + input: { + $ref: '#/components/schemas/JsonObject', + }, + arguments: { + type: 'array', + items: { + $ref: '#/components/schemas/JsonObject', + }, + }, + output: { + $ref: '#/components/schemas/JsonObject', + }, + }, + }, + "'examples'": { + type: 'array', + items: { + $ref: '#/components/schemas/ScaffolderUsageExample', + }, + }, + }, + description: + 'The response shape for a single filter in the `listTemplatingExtensions` call to the `scaffolder-backend`', + }, + TemplateParameterSchema: { + type: 'object', + properties: { + title: { + type: 'string', + }, + description: { + type: 'string', + }, + steps: { + type: 'array', + items: { + type: 'object', + properties: { + title: { + type: 'string', + }, + description: { + type: 'string', + }, + schema: { + type: 'object', + }, + }, + required: ['title', 'schema'], + }, + }, + }, + required: ['title', 'steps'], + description: + 'The shape of each entry of parameters which gets rendered\nas a separate step in the wizard input', + additionalProperties: {}, + }, + ValidationError: { + type: 'object', + properties: { + path: { + type: 'array', + items: { + oneOf: [ + { + type: 'string', + }, + { + type: 'integer', + }, + ], + }, + }, + property: { + type: 'string', + }, + message: { + type: 'string', + }, + instance: { + type: 'object', + }, + name: { + type: 'string', + }, + argument: { + oneOf: [ + { + type: 'boolean', + }, + { + type: 'number', + }, + { + type: 'object', + }, + { + type: 'string', + }, + ], + }, + stack: { + type: 'string', + }, + }, + required: [ + 'path', + 'property', + 'message', + 'schema', + 'instance', + 'name', + 'argument', + 'stack', + ], + additionalProperties: {}, + }, + }, + securitySchemes: { + JWT: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + }, + }, + }, + paths: { + '/v2/templates/{namespace}/{kind}/{name}/parameter-schema': { + get: { + operationId: 'GetTemplateParameterSchema', + description: 'Get template parameter schema.', + responses: { + '200': { + description: 'Ok', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/TemplateParameterSchema', + }, + }, + }, + }, + '400': { + $ref: '#/components/responses/ErrorResponse', + }, + default: { + $ref: '#/components/responses/ErrorResponse', + }, + }, + security: [ + {}, + { + JWT: [], + }, + ], + parameters: [ + { + $ref: '#/components/parameters/namespace', + }, + { + $ref: '#/components/parameters/kind', + }, + { + $ref: '#/components/parameters/name', + }, + ], + }, + }, + '/v2/actions': { + get: { + operationId: 'ListActions', + description: 'Returns a list of all installed actions.', + responses: { + '200': { + description: 'Ok', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/ListActionsResponse', + }, + }, + }, + }, + }, + security: [ + {}, + { + JWT: [], + }, + ], + parameters: [], + }, + }, + '/v2/tasks': { + get: { + operationId: 'ListTasks', + description: + 'Returns a list of tasks, filtering by ownership and/or status if given.', + responses: { + '200': { + description: 'Ok', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/ListTasksResponse', + }, + }, + }, + }, + }, + security: [ + {}, + { + JWT: [], + }, + ], + parameters: [ + { + $ref: '#/components/parameters/createdBy', + }, + { + $ref: '#/components/parameters/limit', + }, + { + $ref: '#/components/parameters/offset', + }, + { + $ref: '#/components/parameters/order', + }, + { + $ref: '#/components/parameters/status', + }, + ], + }, + post: { + operationId: 'Scaffold', + description: + 'Executes the scaffolding of a component, given a template and its\nparameter values.', + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/ScaffolderScaffoldOptions', + }, + }, + }, + }, + responses: { + '201': { + description: 'Created', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + }, + required: ['id'], + }, + }, + }, + }, + '400': { + description: 'Validation errors.', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + errors: { + type: 'array', + items: { + $ref: '#/components/schemas/ValidationError', + }, + }, + }, + required: ['errors'], + }, + }, + }, + }, + }, + security: [ + {}, + { + JWT: [], + }, + ], + parameters: [], + }, + }, + '/v2/tasks/{taskId}': { + get: { + operationId: 'GetTask', + description: 'Get a task by ID.', + responses: { + '200': { + description: 'Ok', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/SerializedTask', + }, + }, + }, + }, + '403': { + $ref: '#/components/responses/ErrorResponse', + }, + }, + security: [ + {}, + { + JWT: [], + }, + ], + parameters: [ + { + $ref: '#/components/parameters/taskId', + }, + ], + }, + }, + '/v2/tasks/{taskId}/cancel': { + post: { + operationId: 'CancelTask', + description: + 'Sends a signal to a task broker to cancel the running task by taskId.', + responses: { + '200': { + description: 'Ok', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { + $ref: '#/components/schemas/TaskStatus', + }, + }, + }, + }, + }, + }, + }, + security: [ + {}, + { + JWT: [], + }, + ], + parameters: [ + { + $ref: '#/components/parameters/taskId', + }, + ], + }, + }, + '/v2/tasks/{taskId}/retry': { + post: { + operationId: 'Retry', + description: 'Starts the task again from the point where it failed.', + requestBody: { + required: false, + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + secrets: { + type: 'object', + additionalProperties: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + responses: { + '201': { + description: 'Ok', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + id: { + type: 'string', + }, + }, + required: ['id'], + }, + }, + }, + }, + }, + security: [ + {}, + { + JWT: [], + }, + ], + parameters: [ + { + $ref: '#/components/parameters/taskId', + }, + ], + }, + }, + '/v2/tasks/{taskId}/events': { + get: { + operationId: 'StreamLogsPolling', + description: 'Get events for a task by ID.', + responses: { + '200': { + description: 'Ok', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + $ref: '#/components/schemas/SerializedTaskEvent', + }, + }, + }, + }, + }, + '403': { + $ref: '#/components/responses/ErrorResponse', + }, + }, + parameters: [ + { + $ref: '#/components/parameters/eventsAfter', + }, + { + $ref: '#/components/parameters/taskId', + }, + ], + }, + }, + '/v2/dry-run': { + post: { + operationId: 'DryRun', + description: 'Perform a dry-run of a template', + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + template: { + type: 'object', + }, + values: { + $ref: '#/components/schemas/JsonObject', + }, + secrets: { + type: 'object', + additionalProperties: { + type: 'string', + }, + }, + directoryContents: { + type: 'array', + items: { + type: 'object', + properties: { + path: { + type: 'string', + }, + base64Content: { + type: 'string', + }, + }, + }, + }, + }, + required: ['template', 'values', 'directoryContents'], + }, + }, + }, + }, + responses: { + '200': { + description: 'Ok', + content: { + 'application/json': { + schema: { + allOf: [ + { + $ref: '#/components/schemas/DryRunResult', + }, + { + type: 'object', + properties: { + steps: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'string', + }, + name: { + type: 'string', + }, + action: { + type: 'string', + }, + }, + required: ['id', 'name', 'action'], + additionalProperties: {}, + }, + }, + directoryContents: { + type: 'array', + items: { + type: 'object', + properties: { + path: { + type: 'string', + }, + executable: { + type: 'boolean', + }, + base64Content: { + type: 'string', + }, + }, + required: ['path', 'base64Content'], + }, + }, + }, + required: ['steps'], + }, + ], + }, + }, + }, + }, + '400': { + description: 'Validation errors.', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + errors: { + type: 'array', + items: { + $ref: '#/components/schemas/ValidationError', + }, + }, + }, + required: ['errors'], + }, + }, + }, + }, + }, + parameters: [], + }, + }, + '/v2/autocomplete/{provider}/{resource}': { + post: { + operationId: 'Autocomplete', + description: + 'Perform an autocomplete for the given provider and resource.', + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + context: { + type: 'object', + additionalProperties: { + type: 'string', + }, + }, + token: { + type: 'string', + }, + }, + required: ['context', 'token'], + }, + }, + }, + }, + responses: { + '200': { + description: 'Ok', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + results: { + type: 'array', + items: { + type: 'object', + properties: { + title: { + type: 'string', + }, + id: { + type: 'string', + }, + }, + required: ['id'], + }, + }, + }, + required: ['results'], + }, + }, + }, + }, + '400': { + description: 'Unsupported provider.', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { + type: 'string', + }, + name: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + parameters: [ + { + in: 'path', + name: 'provider', + required: true, + allowReserved: true, + schema: { + type: 'string', + }, + }, + { + in: 'path', + name: 'resource', + required: true, + allowReserved: true, + schema: { + type: 'string', + }, + }, + ], + }, + }, + '/v2/templating-extensions': { + get: { + operationId: 'ListTemplatingExtensions', + description: + 'Returns a structure describing the available templating extensions.', + responses: { + '200': { + description: 'Ok', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/ListTemplatingExtensionsResponse', + }, + }, + }, + }, + }, + security: [ + {}, + { + JWT: [], + }, + ], + parameters: [], + }, + }, + }, +} as const; +export const createOpenApiRouter = async ( + options?: Parameters< + typeof createValidatedOpenApiRouterFromGeneratedEndpointMap + >['1'], +) => + createValidatedOpenApiRouterFromGeneratedEndpointMap<EndpointMap>( + spec, + options, + ); diff --git a/canon-docs/src/components/Chip/index.ts b/plugins/scaffolder-backend/src/schema/openapi/index.ts similarity index 88% rename from canon-docs/src/components/Chip/index.ts rename to plugins/scaffolder-backend/src/schema/openapi/index.ts index ff9e4794c5..196aad553a 100644 --- a/canon-docs/src/components/Chip/index.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { Chip } from './Chip'; + +export * from './generated'; diff --git a/plugins/scaffolder-backend/src/service/conditionExports.ts b/plugins/scaffolder-backend/src/service/alpha.ts similarity index 81% rename from plugins/scaffolder-backend/src/service/conditionExports.ts rename to plugins/scaffolder-backend/src/service/alpha.ts index 7d22c9d262..ac14f0b1ba 100644 --- a/plugins/scaffolder-backend/src/service/conditionExports.ts +++ b/plugins/scaffolder-backend/src/service/alpha.ts @@ -17,9 +17,14 @@ import { RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, RESOURCE_TYPE_SCAFFOLDER_ACTION, + RESOURCE_TYPE_SCAFFOLDER_TASK, } from '@backstage/plugin-scaffolder-common/alpha'; import { createConditionExports } from '@backstage/plugin-permission-node'; -import { scaffolderTemplateRules, scaffolderActionRules } from './rules'; +import { + scaffolderTemplateRules, + scaffolderActionRules, + scaffolderTaskRules, +} from './rules'; const templateConditionExports = createConditionExports({ pluginId: 'scaffolder', @@ -33,6 +38,12 @@ const actionsConditionExports = createConditionExports({ rules: scaffolderActionRules, }); +const taskConditionExports = createConditionExports({ + pluginId: 'scaffolder', + resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, + rules: scaffolderTaskRules, +}); + /** * `createScaffolderTemplateConditionalDecision` can be used when authoring policies to * create conditional decisions. It requires a permission of type @@ -90,3 +101,17 @@ export const createScaffolderActionConditionalDecision = * @alpha */ export const scaffolderActionConditions = actionsConditionExports.conditions; + +/** + * @alpha + */ +export const createScaffolderTaskConditionalDecision = + taskConditionExports.createConditionalDecision; + +/** + * These conditions are used when creating conditional decisions for scaffolder + * tasks that are returned by authorization policies. + * + * @alpha + */ +export const scaffolderTaskConditions = taskConditionExports.conditions; 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<string> { 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<TemplateEntityV1beta3> { - 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..b73cce7a74 --- /dev/null +++ b/plugins/scaffolder-backend/src/service/permissions.ts @@ -0,0 +1,92 @@ +/* + * 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_TASK, + RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, +} from '@backstage/plugin-scaffolder-common/alpha'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; +import { SerializedTask } from '@backstage/plugin-scaffolder-node'; + +/** + * + * @public + */ +export type ScaffolderPermissionRuleInput = + | TemplatePermissionRuleInput + | ActionPermissionRuleInput + | TaskPermissionRuleInput; + +/** + * @public + */ +export type TemplatePermissionRuleInput< + TParams extends PermissionRuleParams = PermissionRuleParams, +> = PermissionRule< + TemplateEntityStepV1beta3 | TemplateParametersV1beta3, + {}, + typeof RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, + TParams +>; +export function isTemplatePermissionRuleInput( + permissionRule: ScaffolderPermissionRuleInput, +): 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: ScaffolderPermissionRuleInput, +): permissionRule is ActionPermissionRuleInput { + return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_ACTION; +} + +/** + * @public + */ +export type TaskPermissionRuleInput< + TParams extends PermissionRuleParams = PermissionRuleParams, +> = PermissionRule< + SerializedTask, + { + key: string; + values?: string[]; + }, + typeof RESOURCE_TYPE_SCAFFOLDER_TASK, + TParams +>; +export function isTaskPermissionRuleInput( + permissionRule: ScaffolderPermissionRuleInput, +): permissionRule is TaskPermissionRuleInput { + return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_TASK; +} diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index d26cc955b3..467b590bed 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -14,9 +14,8 @@ * limitations under the License. */ -import { DatabaseManager } from '@backstage/backend-common'; +import { DatabaseManager } from '@backstage/backend-defaults/database'; import { ConfigReader } from '@backstage/config'; -import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import express from 'express'; import request from 'supertest'; import ObservableImpl from 'zen-observable'; @@ -32,17 +31,18 @@ import { stringifyEntityRef, UserEntity, } from '@backstage/catalog-model'; -import { createRouter, DatabaseTaskStore } from '../index'; import { + createTemplateAction, TaskBroker, TemplateFilter, TemplateGlobal, + SerializedTaskEvent, } from '@backstage/plugin-scaffolder-node'; +import { TaskSpec } from '@backstage/plugin-scaffolder-common'; +import { JsonValue } from '@backstage/types'; import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker'; -import { - AuthorizeResult, - PermissionEvaluator, -} from '@backstage/plugin-permission-common'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { wrapServer } from '@backstage/backend-openapi-utils/testUtils'; import { mockCredentials, mockErrorHandler, @@ -50,15 +50,15 @@ import { } from '@backstage/backend-test-utils'; import { AutocompleteHandler, + CreatedTemplateFilter, + CreatedTemplateGlobal, createTemplateFilter, 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'; -import { loggerToWinstonLogger } from '../util/loggerToWinstonLogger'; +import { createDebugLogAction } from '../scaffolder/actions/builtin'; import { ScmIntegrations } from '@backstage/integration'; import { extractFilterMetadata, @@ -66,21 +66,8 @@ import { extractGlobalValueMetadata, } from '../util/templating'; import { createDefaultFilters } from '../lib/templating/filters/createDefaultFilters'; - -const mockAccess = jest.fn(); - -jest.mock('fs-extra', () => ({ - access: (...args: any[]) => mockAccess(...args), - promises: { - access: (...args: any[]) => mockAccess(...args), - }, - constants: { - F_OK: 0, - W_OK: 1, - }, - mkdir: jest.fn(), - remove: jest.fn(), -})); +import { createRouter } from './router'; +import { DatabaseTaskStore } from '../scaffolder/tasks/DatabaseTaskStore'; function createDatabase(): DatabaseService { return DatabaseManager.fromConfig( @@ -92,1620 +79,1403 @@ function createDatabase(): DatabaseService { }, }, }), - ).forPlugin('scaffolder'); + ).forPlugin('scaffolder', { + logger: mockServices.logger.mock(), + lifecycle: mockServices.lifecycle.mock(), + }); } -const mockUrlReader = UrlReaders.default({ - logger: mockServices.logger.mock(), - config: new ConfigReader({}), -}); - const config = new ConfigReader({}); -describe.each([ - { - desc: 'no template filters/globals', +// todo: this needs to return a new object every time as there seems to +// be some mutation in the tests. +const generateMockTemplate = () => ({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { + description: 'Create a new CRA website project', + name: 'create-react-app-template', + tags: ['experimental', 'react', 'cra'], + title: 'Create React App Template', + annotations: { + 'backstage.io/managed-by-location': 'url:https://dev.azure.com', + }, }, - { - desc: 'legacy template filters', - additionalTemplateFilters: { - foo: (s: any) => s, - bar: (bar: any) => !!bar, - baz: (what: string, ever: string) => what + ever, - } as Record<string, TemplateFilter>, - }, - { - desc: 'created template filters', - additionalTemplateFilters: [ - createTemplateFilter({ - id: 'foo', - schema: z => - z - .function() - .args(z.any().describe('a value')) - .returns(z.any().describe('same value')), - filter: s => s, - }), - createTemplateFilter({ - id: 'bar', - filter: bar => !!bar, - }), - createTemplateFilter({ - id: 'baz', - description: 'append the argument to the incoming value', - schema: z => - z - .function() - .args(z.string(), z.string().describe('value to append to input')) - .returns(z.string().describe('input+suffix')), - filter: (what, ever) => what + ever, - }), - createTemplateFilter({ - id: 'blah', - schema: z => - z - .function() - .args( - z.number(), - z.number().describe('factor by which to multiply input'), - z.number().describe('addend by which to increase input * factor'), - ), - filter: (base, factor, addend) => base * factor + addend, - }), - ], - }, - { - desc: 'legacy template globals', - additionalTemplateGlobals: { - nul: null, - nop: x => x, - } as Record<string, TemplateGlobal>, - }, - { - desc: 'created template globals', - additionalTemplateGlobals: [ - createTemplateGlobalValue({ - id: 'nul', - description: 'null value', - value: null, - }), - createTemplateGlobalFunction({ - id: 'nop', - description: 'nop function', - schema: z => - z - .function() - .args(z.any().describe('input')) - .returns(z.any().describe('output')), - fn: x => x, - }), - ], - }, -])( - 'createRouter, $desc', - ({ additionalTemplateFilters, additionalTemplateGlobals }) => { - let app: express.Express; - let loggerSpy: jest.SpyInstance; - let taskBroker: TaskBroker; - const catalogClient = 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', - kind: 'Template', - metadata: { - description: 'Create a new CRA website project', - name: 'create-react-app-template', - tags: ['experimental', 'react', 'cra'], - title: 'Create React App Template', - annotations: { - 'backstage.io/managed-by-location': 'url:https://dev.azure.com', + spec: { + owner: 'web@example.com', + type: 'website', + steps: [ + { + id: 'step-one', + name: 'First log', + action: 'debug:log', + input: { + message: 'hello', }, }, - spec: { - owner: 'web@example.com', - type: 'website', + { + id: 'step-two', + name: 'Second log', + action: 'debug:log', + input: { + message: 'world', + }, + 'backstage:permissions': { + tags: ['steps-tag'], + }, + }, + ], + parameters: [ + { + type: 'object', + required: ['requiredParameter1'], + properties: { + requiredParameter1: { + type: 'string', + description: 'Required parameter 1', + }, + }, + }, + { + type: 'object', + required: ['requiredParameter2'], + 'backstage:permissions': { + tags: ['parameters-tag'], + }, + properties: { + requiredParameter2: { + type: 'string', + description: 'Required parameter 2', + }, + }, + }, + ], + }, +}); + +const mockUser: UserEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: 'guest', + annotations: { + 'google.com/email': 'bobby@tables.com', + }, + }, + spec: { + profile: { + displayName: 'Robert Tables of the North', + }, + }, +}; + +const createTestRouter = async ( + overrides: { + additionalTemplateFilters?: + | Record<string, TemplateFilter> + | CreatedTemplateFilter<any, any>[]; + additionalTemplateGlobals?: + | Record<string, TemplateGlobal> + | CreatedTemplateGlobal[]; + autocompleteHandlers?: Record<string, AutocompleteHandler>; + } = {}, +) => { + const logger = mockServices.logger.mock({ + child: () => logger, + }); + + const databaseTaskStore = await DatabaseTaskStore.create({ + database: createDatabase(), + }); + + const taskBroker = new StorageTaskBroker(databaseTaskStore, logger, config); + + jest.spyOn(taskBroker, 'dispatch'); + jest.spyOn(taskBroker, 'claim'); + jest.spyOn(taskBroker, 'cancel'); + jest.spyOn(taskBroker, 'retry'); + jest.spyOn(taskBroker, 'list'); + jest.spyOn(taskBroker, 'get'); + jest.spyOn(taskBroker, 'vacuumTasks'); + jest.spyOn(taskBroker, 'event$'); + + const catalog = catalogServiceMock.mock(); + const permissions = mockServices.permissions(); + const auth = mockServices.auth(); + const httpAuth = mockServices.httpAuth(); + const events = mockServices.events(); + + catalog.getEntityByRef.mockImplementation(async ref => { + const { kind } = parseEntityRef(ref); + + if (kind.toLocaleLowerCase() === 'template') { + return generateMockTemplate(); + } + + if (kind.toLocaleLowerCase() === 'user') { + return mockUser; + } + + throw new Error(`no mock found for kind: ${kind}`); + }); + + const router = await createRouter({ + logger, + config: new ConfigReader({}), + database: createDatabase(), + catalog, + taskBroker, + permissions, + auth, + httpAuth, + events, + additionalTemplateFilters: overrides.additionalTemplateFilters, + additionalTemplateGlobals: overrides.additionalTemplateGlobals, + autocompleteHandlers: overrides.autocompleteHandlers, + actions: [ + createTemplateAction({ + id: 'test', + description: 'test', + schema: { + input: z => + z.object({ + test: z.string(), + }), + }, + handler: async () => {}, + }), + createDebugLogAction(), + ], + }); + + router.use(mockErrorHandler()); + const wrappedRouter = await wrapServer(express().use(router)); + return { + router: wrappedRouter, + unwrappedRouter: router, + logger, + taskBroker, + permissions, + catalog, + }; +}; + +describe('scaffolder router', () => { + const credentials = mockCredentials.user(); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('GET /v2/actions', () => { + it('lists available actions', async () => { + const { router } = await createTestRouter(); + const response = await request(router).get('/v2/actions').send(); + expect(response.status).toEqual(200); + expect(response.body[0].id).toBeDefined(); + expect(response.body.length).toBe(2); + }); + }); + + describe('GET /v2/templating-extensions', () => { + it('lists template filters and globals', async () => { + const { router } = await createTestRouter(); + const response = await request(router) + .get('/v2/templating-extensions') + .send(); + expect(response.status).toEqual(200); + const integrations = ScmIntegrations.fromConfig(config); + + expect(response.body).toMatchObject({ + filters: { + ...extractFilterMetadata(createDefaultFilters({ integrations })), + }, + globals: { + functions: {}, + values: {}, + }, + }); + }); + + it('should include legacy template filters in templating extensions', async () => { + const { router } = await createTestRouter({ + additionalTemplateFilters: { + foo: (s: JsonValue) => s, + bar: (bar: JsonValue) => !!bar, + baz: (what: JsonValue, ever: JsonValue) => + String(what) + String(ever), + } as Record<string, TemplateFilter>, + }); + + const response = await request(router) + .get('/v2/templating-extensions') + .send(); + expect(response.status).toEqual(200); + + const integrations = ScmIntegrations.fromConfig(config); + + expect(response.body).toMatchObject({ + filters: { + ...extractFilterMetadata(createDefaultFilters({ integrations })), + ...extractFilterMetadata({ + foo: (s: JsonValue) => s, + bar: (bar: JsonValue) => !!bar, + baz: (what: JsonValue, ever: JsonValue) => + String(what) + String(ever), + }), + }, + globals: { + functions: {}, + values: {}, + }, + }); + }); + + it('should include created template filters in templating extensions', async () => { + const { router } = await createTestRouter({ + additionalTemplateFilters: [ + createTemplateFilter({ + id: 'foo', + schema: z => + z + .function() + .args(z.any().describe('a value')) + .returns(z.any().describe('same value')), + filter: s => s, + }), + createTemplateFilter({ + id: 'bar', + filter: bar => !!bar, + }), + createTemplateFilter({ + id: 'baz', + description: 'append the argument to the incoming value', + schema: z => + z + .function() + .args( + z.string(), + z.string().describe('value to append to input'), + ) + .returns(z.string().describe('input+suffix')), + filter: (what, ever) => what + ever, + }), + createTemplateFilter({ + id: 'blah', + schema: z => + z + .function() + .args( + z.number(), + z.number().describe('factor by which to multiply input'), + z + .number() + .describe('addend by which to increase input * factor'), + ), + filter: (base, factor, addend) => base * factor + addend, + }), + ], + }); + + const response = await request(router) + .get('/v2/templating-extensions') + .send(); + + expect(response.status).toEqual(200); + + const integrations = ScmIntegrations.fromConfig(config); + + expect(response.body.filters).toMatchObject({ + ...extractFilterMetadata(createDefaultFilters({ integrations })), + foo: expect.any(Object), + bar: expect.any(Object), + baz: expect.any(Object), + blah: expect.any(Object), + }); + }); + + it('should include legacy template globals in templating extensions', async () => { + const { router } = await createTestRouter({ + additionalTemplateGlobals: { + nul: null, + nop: (x: JsonValue) => x, + } as Record<string, TemplateGlobal>, + }); + + const response = await request(router) + .get('/v2/templating-extensions') + .send(); + + expect(response.status).toEqual(200); + + expect(response.body.globals).toMatchObject({ + functions: extractGlobalFunctionMetadata({ + nul: null, + nop: (x: JsonValue) => x, + }), + values: extractGlobalValueMetadata({ + nul: null, + nop: (x: JsonValue) => x, + }), + }); + }); + + it('should include created template globals in templating extensions', async () => { + const { router } = await createTestRouter({ + additionalTemplateGlobals: [ + createTemplateGlobalValue({ + id: 'nul', + description: 'null value', + value: null, + }), + createTemplateGlobalFunction({ + id: 'nop', + description: 'nop function', + schema: z => + z + .function() + .args(z.any().describe('input')) + .returns(z.any().describe('output')), + fn: (x: JsonValue) => x, + }), + ], + }); + const response = await request(router) + .get('/v2/templating-extensions') + .send(); + expect(response.status).toEqual(200); + + expect(response.body.globals).toMatchObject({ + functions: expect.objectContaining({ + nop: expect.any(Object), + }), + values: expect.objectContaining({ + nul: expect.any(Object), + }), + }); + }); + }); + + describe('GET /v2/templates/:namespace/:kind/:name/parameter-schema', () => { + it('returns the parameter schema', async () => { + const { router, permissions } = await createTestRouter(); + jest + .spyOn(permissions, 'authorizeConditional') + .mockImplementationOnce(async () => [ + { + result: AuthorizeResult.ALLOW, + }, + { + result: AuthorizeResult.ALLOW, + }, + ]); + const response = await request(router) + .get( + '/v2/templates/default/Template/create-react-app-template/parameter-schema', + ) + .send(); + + expect(response.status).toEqual(200); + + expect(response.body).toEqual({ + title: 'Create React App Template', + description: 'Create a new CRA website project', steps: [ { - id: 'step-one', - name: 'First log', - action: 'debug:log', - input: { - message: 'hello', - }, - }, - { - id: 'step-two', - name: 'Second log', - action: 'debug:log', - input: { - message: 'world', - }, - 'backstage:permissions': { - tags: ['steps-tag'], - }, - }, - ], - parameters: [ - { - type: 'object', - required: ['requiredParameter1'], - properties: { - requiredParameter1: { - type: 'string', - description: 'Required parameter 1', + title: 'Please enter the following information', + schema: { + required: ['requiredParameter1'], + type: 'object', + properties: { + requiredParameter1: { + description: 'Required parameter 1', + type: 'string', + }, }, }, }, { - type: 'object', - required: ['requiredParameter2'], - 'backstage:permissions': { - tags: ['parameters-tag'], - }, - properties: { - requiredParameter2: { - type: 'string', - description: 'Required parameter 2', + title: 'Please enter the following information', + schema: { + type: 'object', + required: ['requiredParameter2'], + 'backstage:permissions': { + tags: ['parameters-tag'], + }, + properties: { + requiredParameter2: { + type: 'string', + description: 'Required parameter 2', + }, }, }, }, ], - }, + }); }); - const mockUser: UserEntity = { - apiVersion: 'backstage.io/v1alpha1', - kind: 'User', - metadata: { - name: 'guest', - annotations: { - 'google.com/email': 'bobby@tables.com', - }, - }, - spec: { - profile: { - displayName: 'Robert Tables of the North', - }, - }, - }; - - describe('not providing an identity api', () => { - beforeEach(async () => { - const logger = loggerToWinstonLogger(mockServices.logger.mock()); - const databaseTaskStore = await DatabaseTaskStore.create({ - database: createDatabase(), - }); - taskBroker = new StorageTaskBroker(databaseTaskStore, logger, config); - - jest.spyOn(taskBroker, 'dispatch'); - jest.spyOn(taskBroker, 'get'); - jest.spyOn(taskBroker, 'list'); - jest.spyOn(taskBroker, 'event$'); - loggerSpy = jest.spyOn(logger, 'info'); - - const router = await createRouter({ - logger: logger, - config: new ConfigReader({}), - database: createDatabase(), - catalogClient, - reader: mockUrlReader, - taskBroker, - permissions: permissionApi, - auth, - httpAuth, - discovery, - events, - additionalTemplateFilters, - additionalTemplateGlobals, - }); - app = express().use(router); - - catalogClient.getEntityByRef.mockImplementation(async ref => { - const { kind } = parseEntityRef(ref); - - if (kind.toLocaleLowerCase() === 'template') { - return getMockTemplate(); - } - - if (kind.toLocaleLowerCase() === 'user') { - return mockUser; - } - - throw new Error(`no mock found for kind: ${kind}`); - }); - - jest - .spyOn(permissionApi, 'authorizeConditional') - .mockImplementation(async () => [ - { - result: AuthorizeResult.ALLOW, - }, - { - result: AuthorizeResult.ALLOW, - }, - ]); - jest.spyOn(permissionApi, 'authorize').mockImplementation(async () => [ + it('filters parameters that the user is not authorized to see', async () => { + const { router, permissions } = await createTestRouter(); + jest + .spyOn(permissions, 'authorizeConditional') + .mockImplementationOnce(async () => [ + { + result: AuthorizeResult.DENY, + }, { result: AuthorizeResult.ALLOW, }, ]); - }); - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('GET /v2/actions', () => { - it('lists available actions', async () => { - 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); - }); - }); - - describe('GET /v2/templating-extensions', () => { - it('lists template filters and globals', async () => { - const response = await request(app) - .get('/v2/templating-extensions') - .send(); - expect(response.status).toEqual(200); - const integrations = ScmIntegrations.fromConfig(config); - - expect(response.body).toMatchObject({ - filters: { - ...extractFilterMetadata(createDefaultFilters({ integrations })), - ...extractFilterMetadata(additionalTemplateFilters), - }, - globals: { - functions: extractGlobalFunctionMetadata( - additionalTemplateGlobals, - ), - values: extractGlobalValueMetadata(additionalTemplateGlobals), - }, - }); - }); - }); - - describe('POST /v2/tasks', () => { - it('rejects template values which do not match the template schema definition', async () => { - const response = await request(app) - .post('/v2/tasks') - .send({ - templateRef: stringifyEntityRef({ - kind: 'template', - name: 'create-react-app-template', - }), - values: { - storePath: 'https://github.com/backstage/backstage', - }, - }); - - expect(response.status).toEqual(400); - }); - - it('return the template id', async () => { - const broker = - taskBroker.dispatch as jest.Mocked<TaskBroker>['dispatch']; - broker.mockResolvedValue({ - taskId: 'a-random-id', - }); - - const response = await request(app) - .post('/v2/tasks') - .send({ - templateRef: stringifyEntityRef({ - kind: 'template', - name: 'create-react-app-template', - }), - values: { - requiredParameter1: 'required-value-1', - requiredParameter2: 'required-value-2', - }, - }); - - expect(response.status).toEqual(201); - expect(response.body.id).toBe('a-random-id'); - }); - - it('should call the broker with a correct spec', async () => { - const broker = - taskBroker.dispatch as jest.Mocked<TaskBroker>['dispatch']; - const mockToken = mockCredentials.user.token(); - const mockTemplate = getMockTemplate(); - - await request(app) - .post('/v2/tasks') - .set('Authorization', `Bearer ${mockToken}`) - .send({ - templateRef: stringifyEntityRef({ - kind: 'template', - name: 'create-react-app-template', - }), - values: { - requiredParameter1: 'required-value-1', - requiredParameter2: 'required-value-2', - }, - }); - - expect(broker).toHaveBeenCalledWith( - expect.objectContaining({ - createdBy: 'user:default/mock', - secrets: { - backstageToken: token, - __initiatorCredentials: JSON.stringify(credentials), - }, - - spec: { - apiVersion: mockTemplate.apiVersion, - steps: mockTemplate.spec.steps.map((step, index) => ({ - ...step, - id: step.id ?? `step-${index + 1}`, - name: step.name ?? step.action, - })), - output: mockTemplate.spec.output ?? {}, - parameters: { - requiredParameter1: 'required-value-1', - requiredParameter2: 'required-value-2', - }, - user: { - entity: mockUser, - ref: 'user:default/mock', - }, - templateInfo: { - entityRef: stringifyEntityRef({ - kind: 'Template', - namespace: 'Default', - name: mockTemplate.metadata?.name, - }), - baseUrl: 'https://dev.azure.com', - entity: { - metadata: mockTemplate.metadata, - }, - }, - }, - }), - ); - }); - - it('should emit auditlog containing user identifier when backstage auth is passed', async () => { - const mockToken = mockCredentials.user.token(); - - await request(app) - .post('/v2/tasks') - .set('Authorization', `Bearer ${mockToken}`) - .send({ - templateRef: stringifyEntityRef({ - kind: 'template', - name: 'create-react-app-template', - }), - values: { - requiredParameter1: 'required-value-1', - requiredParameter2: 'required-value-2', - }, - }); - - expect(loggerSpy).toHaveBeenCalledTimes(1); - expect(loggerSpy).toHaveBeenCalledWith( - 'Scaffolding task for template:default/create-react-app-template created by user:default/mock', - ); - }); - }); - - describe('GET /v2/tasks', () => { - it('return all tasks', async () => { - ( - taskBroker.list as jest.Mocked<Required<TaskBroker>>['list'] - ).mockResolvedValue({ - tasks: [ - { - id: 'a-random-id', - spec: {} as any, - status: 'completed', - createdAt: '', - createdBy: '', - }, - ], - totalTasks: 1, - }); - - const response = await request(app).get(`/v2/tasks`); - expect(taskBroker.list).toHaveBeenCalledWith({ - filters: {}, - pagination: {}, - }); - expect(response.status).toEqual(200); - expect(response.body).toStrictEqual({ - tasks: [ - { - id: 'a-random-id', - spec: {} as any, - status: 'completed', - createdAt: '', - createdBy: '', - }, - ], - totalTasks: 1, - }); - }); - - it('return filtered tasks', async () => { - ( - taskBroker.list as jest.Mocked<Required<TaskBroker>>['list'] - ).mockResolvedValue({ - tasks: [ - { - id: 'a-random-id', - spec: {} as any, - status: 'completed', - createdAt: '', - createdBy: 'user:default/foo', - }, - ], - totalTasks: 1, - }); - - const response = await request(app).get( - `/v2/tasks?createdBy=user:default/foo&createdBy=user:default/bar&status=completed&status=open&limit=1&offset=0&order=desc:created_at`, - ); - - expect(response.status).toEqual(200); - expect(response.body).toStrictEqual({ - tasks: [ - { - id: 'a-random-id', - spec: {} as any, - status: 'completed', - createdAt: '', - createdBy: 'user:default/foo', - }, - ], - totalTasks: 1, - }); - expect(taskBroker.list).toHaveBeenCalledWith({ - filters: { - createdBy: ['user:default/foo', 'user:default/bar'], - status: ['completed', 'open'], - }, - pagination: { - limit: 1, - offset: 0, - }, - order: [{ order: 'desc', field: 'created_at' }], - }); - }); - }); - - describe('GET /v2/tasks/:taskId', () => { - it('does not divulge secrets', async () => { - (taskBroker.get as jest.Mocked<TaskBroker>['get']).mockResolvedValue({ - id: 'a-random-id', - spec: {} as any, - status: 'completed', - createdAt: '', - secrets: { - backstageToken: token, - __initiatorCredentials: JSON.stringify(credentials), - }, - createdBy: '', - }); - - const response = await request(app).get(`/v2/tasks/a-random-id`); - expect(response.status).toEqual(200); - expect(response.body.status).toBe('completed'); - expect(response.body.secrets).toBeUndefined(); - }); - }); - - describe('GET /v2/tasks/:taskId/eventstream', () => { - it('should return log messages', async () => { - let subscriber: ZenObservable.SubscriptionObserver<any>; - ( - taskBroker.event$ as jest.Mocked<TaskBroker>['event$'] - ).mockImplementation(({ taskId }) => { - return new ObservableImpl(observer => { - subscriber = observer; - setImmediate(() => { - observer.next({ - events: [ - { - id: 0, - taskId, - type: 'log', - createdAt: '', - body: { message: 'My log message' }, - }, - ], - }); - observer.next({ - events: [ - { - id: 1, - taskId, - type: 'completion', - createdAt: '', - body: { message: 'Finished!' }, - }, - ], - }); - }); - }); - // emit after this function returned - }); - - let statusCode: any = undefined; - let headers: any = {}; - const responseDataFn = jest.fn(); - - const req = request(app) - .get('/v2/tasks/a-random-id/eventstream') - .set('accept', 'text/event-stream') - .parse((res, _) => { - ({ statusCode, headers } = res as any); - - res.on('data', chunk => { - responseDataFn(chunk.toString()); - - // the server expects the client to abort the request - if (chunk.includes('completion')) { - req.abort(); - } - }); - }); - - // wait for the request to finish - await req.catch(() => { - // ignore 'aborted' error - }); - - expect(statusCode).toBe(200); - expect(headers['content-type']).toBe('text/event-stream'); - expect(responseDataFn).toHaveBeenCalledTimes(2); - expect(responseDataFn).toHaveBeenCalledWith(`event: log -data: {"id":0,"taskId":"a-random-id","type":"log","createdAt":"","body":{"message":"My log message"}} - -`); - expect(responseDataFn).toHaveBeenCalledWith(`event: completion -data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{"message":"Finished!"}} - -`); - - expect(taskBroker.event$).toHaveBeenCalledTimes(1); - expect(taskBroker.event$).toHaveBeenCalledWith({ - taskId: 'a-random-id', - }); - expect(subscriber!.closed).toBe(true); - }); - - it('should return log messages with after query', async () => { - let subscriber: ZenObservable.SubscriptionObserver<any>; - ( - taskBroker.event$ as jest.Mocked<TaskBroker>['event$'] - ).mockImplementation(({ taskId }) => { - return new ObservableImpl(observer => { - subscriber = observer; - setImmediate(() => { - observer.next({ - events: [ - { - id: 1, - taskId, - type: 'completion', - createdAt: '', - body: { message: 'Finished!' }, - }, - ], - }); - }); - }); - }); - - let statusCode: any = undefined; - let headers: any = {}; - - const req = request(app) - .get('/v2/tasks/a-random-id/eventstream') - .query({ after: 10 }) - .set('accept', 'text/event-stream') - .parse((res, _) => { - ({ statusCode, headers } = res as any); - - res.on('data', () => { - // close immediately - req.abort(); - }); - }); - - // wait for the request to finish - await req.catch(() => { - // ignore 'aborted' error - }); - - expect(statusCode).toBe(200); - expect(headers['content-type']).toBe('text/event-stream'); - - expect(taskBroker.event$).toHaveBeenCalledTimes(1); - expect(taskBroker.event$).toHaveBeenCalledWith({ - taskId: 'a-random-id', - after: 10, - }); - - expect(subscriber!.closed).toBe(true); - }); - }); - - describe('GET /v2/tasks/:taskId/events', () => { - it('should return log messages', async () => { - let subscriber: ZenObservable.SubscriptionObserver<any>; - ( - taskBroker.event$ as jest.Mocked<TaskBroker>['event$'] - ).mockImplementation(({ taskId }) => { - return new ObservableImpl(observer => { - subscriber = observer; - observer.next({ - events: [ - { - id: 0, - taskId, - type: 'log', - createdAt: '', - body: { message: 'My log message' }, - }, - { - id: 1, - taskId, - type: 'completion', - createdAt: '', - body: { message: 'Finished!' }, - }, - ], - }); - }); - }); - - const response = await request(app).get( - '/v2/tasks/a-random-id/events', - ); - - expect(response.status).toEqual(200); - expect(response.body).toEqual([ - { - id: 0, - taskId: 'a-random-id', - type: 'log', - createdAt: '', - body: { message: 'My log message' }, - }, - { - id: 1, - taskId: 'a-random-id', - type: 'completion', - createdAt: '', - body: { message: 'Finished!' }, - }, - ]); - - expect(taskBroker.event$).toHaveBeenCalledTimes(1); - expect(taskBroker.event$).toHaveBeenCalledWith({ - taskId: 'a-random-id', - }); - expect(subscriber!.closed).toBe(true); - }); - - it('should return log messages with after query', async () => { - let subscriber: ZenObservable.SubscriptionObserver<any>; - ( - taskBroker.event$ as jest.Mocked<TaskBroker>['event$'] - ).mockImplementation(() => { - return new ObservableImpl(observer => { - subscriber = observer; - observer.next({ events: [] }); - }); - }); - - const response = await request(app) - .get('/v2/tasks/a-random-id/events') - .query({ after: 10 }); - - expect(response.status).toEqual(200); - expect(response.body).toEqual([]); - - expect(taskBroker.event$).toHaveBeenCalledTimes(1); - expect(taskBroker.event$).toHaveBeenCalledWith({ - taskId: 'a-random-id', - after: 10, - }); - expect(subscriber!.closed).toBe(true); - }); - }); - - describe('POST /v2/dry-run', () => { - it('should get user entity', async () => { - const mockToken = mockCredentials.user.token(); - const mockTemplate = getMockTemplate(); - - await request(app) - .post('/v2/dry-run') - .set('Authorization', `Bearer ${mockToken}`) - .send({ - template: mockTemplate, - values: { - requiredParameter1: 'required-value-1', - requiredParameter2: 'required-value-2', - }, - directoryContents: [], - }); - - expect(catalogClient.getEntityByRef).toHaveBeenCalledTimes(1); - - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( - 'user:default/mock', - expect.anything(), - ); - }); + const response = await request(router) + .get( + '/v2/templates/default/Template/create-react-app-template/parameter-schema', + ) + .send(); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + title: 'Create React App Template', + description: 'Create a new CRA website project', + steps: [], }); }); - describe('providing an identity api', () => { - beforeEach(async () => { - const logger = loggerToWinstonLogger(mockServices.logger.mock()); - const databaseTaskStore = await DatabaseTaskStore.create({ - database: createDatabase(), - }); - taskBroker = new StorageTaskBroker(databaseTaskStore, logger, config); - - jest.spyOn(taskBroker, 'dispatch'); - jest.spyOn(taskBroker, 'get'); - jest.spyOn(taskBroker, 'list'); - jest.spyOn(taskBroker, 'event$'); - loggerSpy = jest.spyOn(logger, 'info'); - - const router = await createRouter({ - logger: logger, - config: new ConfigReader({}), - database: createDatabase(), - catalogClient, - reader: mockUrlReader, - taskBroker, - permissions: permissionApi, - auth, - httpAuth, - discovery, - }); - app = express().use(router); - - catalogClient.getEntityByRef.mockImplementation(async ref => { - const { kind } = parseEntityRef(ref); - - if (kind.toLocaleLowerCase() === 'template') { - return getMockTemplate(); - } - - if (kind.toLocaleLowerCase() === 'user') { - return mockUser; - } - throw new Error(`no mock found for kind: ${kind}`); - }); - - jest - .spyOn(permissionApi, 'authorizeConditional') - .mockImplementation(async () => [ - { - result: AuthorizeResult.ALLOW, + it('filters parameters that the user is not authorized to see in case of conditional decision', async () => { + const { permissions, router } = await createTestRouter(); + jest + .spyOn(permissions, 'authorizeConditional') + .mockImplementation(async () => [ + { + conditions: { + resourceType: 'scaffolder-template', + rule: 'HAS_TAG', + params: { tag: 'parameters-tag' }, }, - { - result: AuthorizeResult.ALLOW, - }, - ]); - jest.spyOn(permissionApi, 'authorize').mockImplementation(async () => [ + pluginId: 'scaffolder', + resourceType: 'scaffolder-template', + result: AuthorizeResult.CONDITIONAL, + }, { result: AuthorizeResult.ALLOW, }, ]); - }); - afterEach(() => { - jest.resetAllMocks(); - }); + const response = await request(router) + .get( + '/v2/templates/default/Template/create-react-app-template/parameter-schema', + ) + .send(); - describe('GET /v2/actions', () => { - it('lists available actions', async () => { - 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); - }); - }); - - describe('GET /v2/templates/:namespace/:kind/:name/parameter-schema', () => { - it('returns the parameter schema', async () => { - const response = await request(app) - .get( - '/v2/templates/default/Template/create-react-app-template/parameter-schema', - ) - .send(); - - expect(response.status).toEqual(200); - expect(response.body).toEqual({ - title: 'Create React App Template', - description: 'Create a new CRA website project', - steps: [ - { - title: 'Please enter the following information', - schema: { - required: ['requiredParameter1'], - type: 'object', - properties: { - requiredParameter1: { - description: 'Required parameter 1', - type: 'string', - }, - }, + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + title: 'Create React App Template', + description: 'Create a new CRA website project', + steps: [ + { + title: 'Please enter the following information', + schema: { + type: 'object', + required: ['requiredParameter2'], + 'backstage:permissions': { + tags: ['parameters-tag'], + }, + properties: { + requiredParameter2: { + type: 'string', + description: 'Required parameter 2', }, }, - { - title: 'Please enter the following information', - schema: { - type: 'object', - required: ['requiredParameter2'], - 'backstage:permissions': { - tags: ['parameters-tag'], - }, - properties: { - requiredParameter2: { - type: 'string', - description: 'Required parameter 2', - }, - }, - }, - }, - ], - }); - }); - - it('filters parameters that the user is not authorized to see', async () => { - jest - .spyOn(permissionApi, 'authorizeConditional') - .mockImplementationOnce(async () => [ - { - result: AuthorizeResult.DENY, - }, - { - result: AuthorizeResult.ALLOW, - }, - ]); - const response = await request(app) - .get( - '/v2/templates/default/Template/create-react-app-template/parameter-schema', - ) - .send(); - expect(response.status).toEqual(200); - expect(response.body).toEqual({ - title: 'Create React App Template', - description: 'Create a new CRA website project', - steps: [], - }); - }); - - it('filters parameters that the user is not authorized to see in case of conditional decision', async () => { - jest - .spyOn(permissionApi, 'authorizeConditional') - .mockImplementationOnce(async () => [ - { - conditions: { - resourceType: 'scaffolder-template', - rule: 'HAS_TAG', - params: { tag: 'parameters-tag' }, - }, - pluginId: 'scaffolder', - resourceType: 'scaffolder-template', - result: AuthorizeResult.CONDITIONAL, - }, - { - result: AuthorizeResult.ALLOW, - }, - ]); - const response = await request(app) - .get( - '/v2/templates/default/Template/create-react-app-template/parameter-schema', - ) - .send(); - expect(response.status).toEqual(200); - expect(response.body).toEqual({ - title: 'Create React App Template', - description: 'Create a new CRA website project', - steps: [ - { - title: 'Please enter the following information', - schema: { - type: 'object', - required: ['requiredParameter2'], - 'backstage:permissions': { - tags: ['parameters-tag'], - }, - properties: { - requiredParameter2: { - type: 'string', - description: 'Required parameter 2', - }, - }, - }, - }, - ], - }); - }); - }); - - describe('POST /v2/tasks', () => { - it('rejects template values which do not match the template schema definition', async () => { - const response = await request(app) - .post('/v2/tasks') - .send({ - templateRef: stringifyEntityRef({ - kind: 'template', - name: 'create-react-app-template', - }), - values: { - storePath: 'https://github.com/backstage/backstage', - }, - }); - - expect(response.status).toEqual(400); - }); - - it('filters steps that the user is not authorized to see', async () => { - jest - .spyOn(permissionApi, 'authorizeConditional') - .mockImplementation(async () => [ - { - result: AuthorizeResult.ALLOW, - }, - { - result: AuthorizeResult.DENY, - }, - ]); - - const broker = - taskBroker.dispatch as jest.Mocked<TaskBroker>['dispatch']; - const mockTemplate = getMockTemplate(); - - await request(app) - .post('/v2/tasks') - .send({ - templateRef: stringifyEntityRef({ - kind: 'template', - name: 'create-react-app-template', - }), - values: { - requiredParameter1: 'required-value-1', - requiredParameter2: 'required-value-2', - }, - }); - expect(broker).toHaveBeenCalledWith( - expect.objectContaining({ - createdBy: 'user:default/mock', - secrets: { - backstageToken: token, - __initiatorCredentials: JSON.stringify(credentials), - }, - - spec: { - apiVersion: mockTemplate.apiVersion, - steps: [], - output: mockTemplate.spec.output ?? {}, - parameters: { - requiredParameter1: 'required-value-1', - requiredParameter2: 'required-value-2', - }, - user: { - entity: mockUser, - ref: 'user:default/mock', - }, - templateInfo: { - entityRef: stringifyEntityRef({ - kind: 'Template', - namespace: 'Default', - name: mockTemplate.metadata?.name, - }), - baseUrl: 'https://dev.azure.com', - entity: { - metadata: mockTemplate.metadata, - }, - }, - }, - }), - ); - }); - - it('filters steps that the user is not authorized to see in case of conditional decision', async () => { - jest - .spyOn(permissionApi, 'authorizeConditional') - .mockImplementation(async () => [ - { - result: AuthorizeResult.ALLOW, - }, - { - conditions: { - resourceType: 'scaffolder-template', - rule: 'HAS_TAG', - params: { tag: 'steps-tag' }, - }, - pluginId: 'scaffolder', - resourceType: 'scaffolder-template', - result: AuthorizeResult.CONDITIONAL, - }, - ]); - - const broker = - taskBroker.dispatch as jest.Mocked<TaskBroker>['dispatch']; - const mockTemplate = getMockTemplate(); - await request(app) - .post('/v2/tasks') - .send({ - templateRef: stringifyEntityRef({ - kind: 'template', - name: 'create-react-app-template', - }), - values: { - requiredParameter1: 'required-value-1', - requiredParameter2: 'required-value-2', - }, - }); - expect(broker).toHaveBeenCalledWith( - expect.objectContaining({ - createdBy: 'user:default/mock', - secrets: { - backstageToken: token, - __initiatorCredentials: JSON.stringify(credentials), - }, - - spec: { - apiVersion: mockTemplate.apiVersion, - steps: [ - { - id: 'step-two', - name: 'Second log', - action: 'debug:log', - input: { - message: 'world', - }, - 'backstage:permissions': { - tags: ['steps-tag'], - }, - }, - ], - output: mockTemplate.spec.output ?? {}, - parameters: { - requiredParameter1: 'required-value-1', - requiredParameter2: 'required-value-2', - }, - user: { - entity: mockUser, - ref: 'user:default/mock', - }, - templateInfo: { - entityRef: stringifyEntityRef({ - kind: 'Template', - namespace: 'Default', - name: mockTemplate.metadata?.name, - }), - baseUrl: 'https://dev.azure.com', - entity: { - metadata: mockTemplate.metadata, - }, - }, - }, - }), - ); - }); - - it('return the template id', async () => { - const broker = - taskBroker.dispatch as jest.Mocked<TaskBroker>['dispatch']; - broker.mockResolvedValue({ - taskId: 'a-random-id', - }); - - const response = await request(app) - .post('/v2/tasks') - .send({ - templateRef: stringifyEntityRef({ - kind: 'template', - name: 'create-react-app-template', - }), - values: { - requiredParameter1: 'required-value-1', - requiredParameter2: 'required-value-2', - }, - }); - - expect(response.body.id).toBe('a-random-id'); - expect(response.status).toEqual(201); - }); - - it('should call the broker with a correct spec', async () => { - const broker = - taskBroker.dispatch as jest.Mocked<TaskBroker>['dispatch']; - const mockTemplate = getMockTemplate(); - - await request(app) - .post('/v2/tasks') - .send({ - templateRef: stringifyEntityRef({ - kind: 'template', - name: 'create-react-app-template', - }), - values: { - requiredParameter1: 'required-value-1', - requiredParameter2: 'required-value-2', - }, - }); - expect(broker).toHaveBeenCalledWith( - expect.objectContaining({ - createdBy: 'user:default/mock', - secrets: { - backstageToken: token, - __initiatorCredentials: JSON.stringify(credentials), - }, - - spec: { - apiVersion: mockTemplate.apiVersion, - steps: mockTemplate.spec.steps.map((step, index) => ({ - ...step, - id: step.id ?? `step-${index + 1}`, - name: step.name ?? step.action, - })), - output: mockTemplate.spec.output ?? {}, - parameters: { - requiredParameter1: 'required-value-1', - requiredParameter2: 'required-value-2', - }, - user: { - entity: mockUser, - ref: 'user:default/mock', - }, - templateInfo: { - entityRef: stringifyEntityRef({ - kind: 'Template', - namespace: 'Default', - name: mockTemplate.metadata?.name, - }), - baseUrl: 'https://dev.azure.com', - entity: { - metadata: mockTemplate.metadata, - }, - }, - }, - }), - ); - }); - - it('should emit auditlog containing user identifier when backstage auth is passed', async () => { - await request(app) - .post('/v2/tasks') - .send({ - templateRef: stringifyEntityRef({ - kind: 'template', - name: 'create-react-app-template', - }), - values: { - requiredParameter1: 'required-value-1', - requiredParameter2: 'required-value-2', - }, - }); - - expect(loggerSpy).toHaveBeenCalledTimes(1); - expect(loggerSpy).toHaveBeenCalledWith( - 'Scaffolding task for template:default/create-react-app-template created by user:default/mock', - ); - }); - }); - - describe('GET /v2/tasks', () => { - it('return all tasks', async () => { - ( - taskBroker.list as jest.Mocked<Required<TaskBroker>>['list'] - ).mockResolvedValue({ - tasks: [ - { - id: 'a-random-id', - spec: {} as any, - status: 'completed', - createdAt: '', - createdBy: '', - }, - ], - totalTasks: 1, - }); - - const response = await request(app).get(`/v2/tasks`); - expect(taskBroker.list).toHaveBeenCalledWith({ - pagination: {}, - filters: {}, - }); - expect(response.status).toEqual(200); - expect(response.body).toStrictEqual({ - tasks: [ - { - id: 'a-random-id', - spec: {} as any, - status: 'completed', - createdAt: '', - createdBy: '', - }, - ], - totalTasks: 1, - }); - }); - - it('return filtered tasks', async () => { - ( - taskBroker.list as jest.Mocked<Required<TaskBroker>>['list'] - ).mockResolvedValue({ - tasks: [ - { - id: 'a-random-id', - spec: {} as any, - status: 'completed', - createdAt: '', - createdBy: 'user:default/foo', - }, - ], - totalTasks: 1, - }); - - const response = await request(app).get( - `/v2/tasks?createdBy=user:default/foo`, - ); - expect(taskBroker.list).toHaveBeenCalledWith({ - filters: { - createdBy: ['user:default/foo'], }, - pagination: {}, - }); + }, + ], + }); + }); + }); - expect(response.status).toEqual(200); - expect(response.body).toStrictEqual({ - tasks: [ - { - id: 'a-random-id', - spec: {} as any, - status: 'completed', - createdAt: '', - createdBy: 'user:default/foo', - }, - ], - totalTasks: 1, - }); + describe('POST /v2/tasks', () => { + it('rejects template values which do not match the template schema definition', async () => { + const { router } = await createTestRouter(); + const response = await request(router) + .post('/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + storePath: 'https://github.com/backstage/backstage', + }, }); + + expect(response.status).toEqual(400); + }); + + it('return the template id', async () => { + const { router, taskBroker } = await createTestRouter(); + const broker = taskBroker.dispatch as jest.Mocked<TaskBroker>['dispatch']; + + broker.mockResolvedValue({ + taskId: 'a-random-id', }); - describe('GET /v2/tasks/:taskId', () => { - it('does not divulge secrets', async () => { - (taskBroker.get as jest.Mocked<TaskBroker>['get']).mockResolvedValue({ - id: 'a-random-id', - spec: {} as any, - status: 'completed', - createdAt: '', - secrets: { - backstageToken: token, - __initiatorCredentials: JSON.stringify(credentials), - }, - createdBy: '', - }); - - const response = await request(app).get(`/v2/tasks/a-random-id`); - expect(response.status).toEqual(200); - expect(response.body.status).toBe('completed'); - expect(response.body.secrets).toBeUndefined(); - }); - }); - - describe('GET /v2/tasks/:taskId/eventstream', () => { - it('should return log messages', async () => { - let subscriber: ZenObservable.SubscriptionObserver<any>; - ( - taskBroker.event$ as jest.Mocked<TaskBroker>['event$'] - ).mockImplementation(({ taskId }) => { - return new ObservableImpl(observer => { - subscriber = observer; - setImmediate(() => { - observer.next({ - events: [ - { - id: 0, - taskId, - type: 'log', - createdAt: '', - body: { message: 'My log message' }, - }, - ], - }); - observer.next({ - events: [ - { - id: 1, - taskId, - type: 'completion', - createdAt: '', - body: { message: 'Finished!' }, - }, - ], - }); - }); - }); - // emit after this function returned - }); - - let statusCode: any = undefined; - let headers: any = {}; - const responseDataFn = jest.fn(); - - const req = request(app) - .get('/v2/tasks/a-random-id/eventstream') - .set('accept', 'text/event-stream') - .parse((res, _) => { - ({ statusCode, headers } = res as any); - - res.on('data', chunk => { - responseDataFn(chunk.toString()); - - // the server expects the client to abort the request - if (chunk.includes('completion')) { - req.abort(); - } - }); - }); - - // wait for the request to finish - await req.catch(() => { - // ignore 'aborted' error - }); - - expect(statusCode).toBe(200); - expect(headers['content-type']).toBe('text/event-stream'); - expect(responseDataFn).toHaveBeenCalledTimes(2); - expect(responseDataFn).toHaveBeenCalledWith(`event: log -data: {"id":0,"taskId":"a-random-id","type":"log","createdAt":"","body":{"message":"My log message"}} - -`); - expect(responseDataFn).toHaveBeenCalledWith(`event: completion -data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{"message":"Finished!"}} - -`); - - expect(taskBroker.event$).toHaveBeenCalledTimes(1); - expect(taskBroker.event$).toHaveBeenCalledWith({ - taskId: 'a-random-id', - }); - expect(subscriber!.closed).toBe(true); + const response = await request(router) + .post('/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, }); - it('should return log messages with after query', async () => { - let subscriber: ZenObservable.SubscriptionObserver<any>; - ( - taskBroker.event$ as jest.Mocked<TaskBroker>['event$'] - ).mockImplementation(({ taskId }) => { - return new ObservableImpl(observer => { - subscriber = observer; - setImmediate(() => { - observer.next({ - events: [ - { - id: 1, - taskId, - type: 'completion', - createdAt: '', - body: { message: 'Finished!' }, - }, - ], - }); - }); - }); - }); + expect(response.status).toEqual(201); + expect(response.body.id).toBe('a-random-id'); + }); - let statusCode: any = undefined; - let headers: any = {}; + it('should call the broker with a correct spec', async () => { + const { router, taskBroker } = await createTestRouter(); + const broker = taskBroker.dispatch as jest.Mocked<TaskBroker>['dispatch']; + const mockToken = mockCredentials.user.token(); + const mockTemplate = generateMockTemplate(); - const req = request(app) - .get('/v2/tasks/a-random-id/eventstream') - .query({ after: 10 }) - .set('accept', 'text/event-stream') - .parse((res, _) => { - ({ statusCode, headers } = res as any); - - res.on('data', () => { - // close immediately - req.abort(); - }); - }); - - // wait for the request to finish - await req.catch(() => { - // ignore 'aborted' error - }); - - expect(statusCode).toBe(200); - expect(headers['content-type']).toBe('text/event-stream'); - - expect(taskBroker.event$).toHaveBeenCalledTimes(1); - expect(taskBroker.event$).toHaveBeenCalledWith({ - taskId: 'a-random-id', - after: 10, - }); - - expect(subscriber!.closed).toBe(true); - }); - }); - - describe('GET /v2/tasks/:taskId/events', () => { - it('should return log messages', async () => { - let subscriber: ZenObservable.SubscriptionObserver<any>; - ( - taskBroker.event$ as jest.Mocked<TaskBroker>['event$'] - ).mockImplementation(({ taskId }) => { - return new ObservableImpl(observer => { - subscriber = observer; - observer.next({ - events: [ - { - id: 0, - taskId, - type: 'log', - createdAt: '', - body: { message: 'My log message' }, - }, - { - id: 1, - taskId, - type: 'completion', - createdAt: '', - body: { message: 'Finished!' }, - }, - ], - }); - }); - }); - - const response = await request(app).get( - '/v2/tasks/a-random-id/events', - ); - - expect(response.status).toEqual(200); - expect(response.body).toEqual([ - { - id: 0, - taskId: 'a-random-id', - type: 'log', - createdAt: '', - body: { message: 'My log message' }, - }, - { - id: 1, - taskId: 'a-random-id', - type: 'completion', - createdAt: '', - body: { message: 'Finished!' }, - }, - ]); - - expect(taskBroker.event$).toHaveBeenCalledTimes(1); - expect(taskBroker.event$).toHaveBeenCalledWith({ - taskId: 'a-random-id', - }); - expect(subscriber!.closed).toBe(true); + await request(router) + .post('/v2/tasks') + .set('Authorization', `Bearer ${mockToken}`) + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, }); - it('should return log messages with after query', async () => { - let subscriber: ZenObservable.SubscriptionObserver<any>; - ( - taskBroker.event$ as jest.Mocked<TaskBroker>['event$'] - ).mockImplementation(() => { - return new ObservableImpl(observer => { - subscriber = observer; - observer.next({ events: [] }); - }); - }); - - const response = await request(app) - .get('/v2/tasks/a-random-id/events') - .query({ after: 10 }); - - expect(response.status).toEqual(200); - expect(response.body).toEqual([]); - - expect(taskBroker.event$).toHaveBeenCalledTimes(1); - expect(taskBroker.event$).toHaveBeenCalledWith({ - taskId: 'a-random-id', - after: 10, - }); - expect(subscriber!.closed).toBe(true); - }); - }); - - describe('GET /v2/autocomplete/:provider/:resource', () => { - let handleAutocompleteRequest: AutocompleteHandler; - - beforeEach(async () => { - handleAutocompleteRequest = jest.fn().mockResolvedValue({ - results: [{ title: 'blob' }], - }); - - const router = await createRouter({ - logger: loggerToWinstonLogger(mockServices.logger.mock()), - config: new ConfigReader({}), - database: createDatabase(), - catalogClient, - reader: mockUrlReader, - taskBroker, - permissions: permissionApi, - auth, - httpAuth, - discovery, - autocompleteHandlers: { - 'test-provider': handleAutocompleteRequest, - }, - }); - - app = express().use(router).use(mockErrorHandler()); - }); - - it('should throw an error when the provider is not registered', async () => { - const response = await request(app) - .post('/v2/autocomplete/unknown-provider/resource') - .send({ - token: 'token', - context: {}, - }); - - expect(response.status).toEqual(400); - expect(response.body).toEqual( - expect.objectContaining({ - error: { - message: 'Unsupported provider: unknown-provider', - name: 'InputError', - }, - }), - ); - }); - - it('should call the autocomplete handler', async () => { - const context = { mock: 'context' }; - const mockToken = 'mocktoken'; - - const response = await request(app) - .post('/v2/autocomplete/test-provider/resource') - .send({ + expect(broker).toHaveBeenCalledWith( + expect.objectContaining({ + createdBy: 'user:default/mock', + secrets: { + __initiatorCredentials: JSON.stringify({ + ...credentials, token: mockToken, - context, + }), + backstageToken: mockToken, + }, + spec: { + apiVersion: mockTemplate.apiVersion, + steps: mockTemplate.spec.steps.map((step, index) => ({ + ...step, + id: step.id ?? `step-${index + 1}`, + name: step.name ?? step.action, + })), + output: {}, + parameters: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + user: { + entity: mockUser, + ref: 'user:default/mock', + }, + templateInfo: { + entityRef: stringifyEntityRef({ + kind: 'Template', + namespace: 'Default', + name: mockTemplate.metadata?.name, + }), + baseUrl: 'https://dev.azure.com', + entity: { + metadata: mockTemplate.metadata, + }, + }, + }, + }), + ); + }); + + it('should emit auditlog containing user identifier when backstage auth is passed', async () => { + const { logger, router } = await createTestRouter(); + const mockToken = mockCredentials.user.token(); + + const { status, body } = await request(router) + .post('/v2/tasks') + .set('Authorization', `Bearer ${mockToken}`) + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + }); + + expect(status).toBe(201); + expect(body).toMatchObject({ + id: expect.any(String), + }); + expect(logger.info).toHaveBeenCalledTimes(1); + expect(logger.info).toHaveBeenCalledWith( + 'Scaffolding task for template:default/create-react-app-template created by user:default/mock', + ); + }); + + it('filters steps that the user is not authorized to see', async () => { + const { router, permissions, taskBroker } = await createTestRouter(); + jest + .spyOn(permissions, 'authorizeConditional') + .mockImplementation(async () => [ + { + result: AuthorizeResult.ALLOW, + }, + { + result: AuthorizeResult.DENY, + }, + ]); + + const broker = taskBroker.dispatch as jest.Mocked<TaskBroker>['dispatch']; + const mockTemplate = generateMockTemplate(); + + await request(router) + .post('/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + }); + expect(broker).toHaveBeenCalledWith( + expect.objectContaining({ + createdBy: 'user:default/mock', + secrets: { + __initiatorCredentials: JSON.stringify({ + ...credentials, + token: mockCredentials.user.token(), + }), + backstageToken: mockCredentials.user.token(), + }, + + spec: { + apiVersion: mockTemplate.apiVersion, + steps: [], + output: {}, + parameters: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + user: { + entity: mockUser, + ref: 'user:default/mock', + }, + templateInfo: { + entityRef: stringifyEntityRef({ + kind: 'Template', + namespace: 'Default', + name: mockTemplate.metadata?.name, + }), + baseUrl: 'https://dev.azure.com', + entity: { + metadata: mockTemplate.metadata, + }, + }, + }, + }), + ); + }); + + it('filters steps that the user is not authorized to see in case of conditional decision', async () => { + const { permissions, router, taskBroker } = await createTestRouter(); + jest + .spyOn(permissions, 'authorizeConditional') + .mockImplementation(async () => [ + { + result: AuthorizeResult.ALLOW, + }, + { + conditions: { + resourceType: 'scaffolder-template', + rule: 'HAS_TAG', + params: { tag: 'steps-tag' }, + }, + pluginId: 'scaffolder', + resourceType: 'scaffolder-template', + result: AuthorizeResult.CONDITIONAL, + }, + ]); + + const broker = taskBroker.dispatch as jest.Mocked<TaskBroker>['dispatch']; + const mockTemplate = generateMockTemplate(); + await request(router) + .post('/v2/tasks') + .send({ + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + }); + expect(broker).toHaveBeenCalledWith( + expect.objectContaining({ + createdBy: 'user:default/mock', + secrets: { + __initiatorCredentials: JSON.stringify({ + ...credentials, + token: mockCredentials.user.token(), + }), + backstageToken: mockCredentials.user.token(), + }, + + spec: { + apiVersion: mockTemplate.apiVersion, + steps: [ + { + id: 'step-two', + name: 'Second log', + action: 'debug:log', + input: { + message: 'world', + }, + 'backstage:permissions': { + tags: ['steps-tag'], + }, + }, + ], + output: {}, + parameters: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + user: { + entity: mockUser, + ref: 'user:default/mock', + }, + templateInfo: { + entityRef: stringifyEntityRef({ + kind: 'Template', + namespace: 'Default', + name: mockTemplate.metadata?.name, + }), + baseUrl: 'https://dev.azure.com', + entity: { + metadata: mockTemplate.metadata, + }, + }, + }, + }), + ); + }); + }); + + describe('GET /v2/tasks', () => { + it('return all tasks', async () => { + const { router, taskBroker } = await createTestRouter(); + ( + taskBroker.list as jest.Mocked<Required<TaskBroker>>['list'] + ).mockResolvedValue({ + tasks: [ + { + id: 'a-random-id', + spec: {} as TaskSpec, + status: 'completed', + createdAt: '', + createdBy: '', + }, + ], + totalTasks: 1, + }); + + const response = await request(router).get(`/v2/tasks`); + expect(taskBroker.list).toHaveBeenCalledWith({ + filters: {}, + pagination: {}, + }); + expect(response.status).toEqual(200); + expect(response.body).toStrictEqual({ + tasks: [ + { + id: 'a-random-id', + spec: {} as TaskSpec, + status: 'completed', + createdAt: '', + createdBy: '', + }, + ], + totalTasks: 1, + }); + }); + + it('return filtered tasks', async () => { + const { router, taskBroker } = await createTestRouter(); + ( + taskBroker.list as jest.Mocked<Required<TaskBroker>>['list'] + ).mockResolvedValue({ + tasks: [ + { + id: 'a-random-id', + spec: {} as TaskSpec, + status: 'completed', + createdAt: '', + createdBy: 'user:default/foo', + }, + ], + totalTasks: 1, + }); + + const response = await request(router).get( + `/v2/tasks?createdBy=user:default/foo&createdBy=user:default/bar&status=completed&status=open&limit=1&offset=0&order=desc:created_at`, + ); + + expect(response.status).toEqual(200); + expect(response.body).toStrictEqual({ + tasks: [ + { + id: 'a-random-id', + spec: {} as TaskSpec, + status: 'completed', + createdAt: '', + createdBy: 'user:default/foo', + }, + ], + totalTasks: 1, + }); + expect(taskBroker.list).toHaveBeenCalledWith({ + filters: { + createdBy: ['user:default/foo', 'user:default/bar'], + status: ['completed', 'open'], + }, + pagination: { + limit: 1, + offset: 0, + }, + order: [{ order: 'desc', field: 'created_at' }], + }); + }); + + it('disallows users from seeing tasks they do not own', async () => { + const { router, taskBroker, permissions } = await createTestRouter(); + jest + .spyOn(permissions, 'authorizeConditional') + .mockImplementationOnce(async () => [ + { + conditions: { + resourceType: 'scaffolder-task', + rule: 'IS_TASK_OWNER', + params: { createdBy: ['user'] }, + }, + pluginId: 'scaffolder', + resourceType: 'scaffolder-task', + result: AuthorizeResult.CONDITIONAL, + }, + ]); + const response = await request(router).get( + `/v2/tasks?createdBy=not-user`, + ); + expect(taskBroker.list).toHaveBeenCalledWith({ + filters: { createdBy: ['not-user'], status: undefined }, + order: undefined, + pagination: { limit: undefined, offset: undefined }, + permissionFilters: { key: 'created_by', values: ['user'] }, + }); + expect(response.status).toBe(200); + expect(response.body.totalTasks).toBe(0); + expect(response.body.tasks).toEqual([]); + }); + }); + + describe('GET /v2/tasks/:taskId', () => { + it('does not divulge secrets', async () => { + const { router, taskBroker } = await createTestRouter(); + (taskBroker.get as jest.Mocked<TaskBroker>['get']).mockResolvedValue({ + id: 'a-random-id', + spec: {} as TaskSpec, + status: 'completed', + createdAt: '', + secrets: { + __initiatorCredentials: JSON.stringify(credentials), + }, + createdBy: '', + }); + + const response = await request(router).get(`/v2/tasks/a-random-id`); + expect(response.status).toEqual(200); + expect(response.body.status).toBe('completed'); + expect(response.body.secrets).toBeUndefined(); + }); + it('disallows users from seeing tasks they do not own', async () => { + const { router, permissions, taskBroker } = await createTestRouter(); + jest + .spyOn(permissions, 'authorizeConditional') + .mockImplementationOnce(async () => [ + { + conditions: { + resourceType: 'scaffolder-task', + rule: 'IS_TASK_OWNER', + params: { createdBy: ['user'] }, + }, + pluginId: 'scaffolder', + resourceType: 'scaffolder-task', + result: AuthorizeResult.CONDITIONAL, + }, + ]); + (taskBroker.get as jest.Mocked<TaskBroker>['get']).mockResolvedValue({ + id: 'a-random-id', + spec: {} as any, + status: 'completed', + createdAt: '', + secrets: { + __initiatorCredentials: JSON.stringify(credentials), + }, + createdBy: 'not-user', + }); + + const response = await request(router).get(`/v2/tasks/a-random-id`); + expect(taskBroker.get).toHaveBeenCalledWith('a-random-id'); + expect(response.error).not.toBeFalsy(); + }); + }); + + describe('GET /v2/tasks/:taskId/eventstream', () => { + it('should return log messages', async () => { + const { unwrappedRouter: router, taskBroker } = await createTestRouter(); + (taskBroker.get as jest.Mocked<TaskBroker>['get']).mockResolvedValue({ + id: 'a-random-id', + spec: {} as any, + status: 'completed', + createdAt: '', + secrets: { + __initiatorCredentials: JSON.stringify(credentials), + }, + createdBy: '', + }); + let subscriber: ZenObservable.SubscriptionObserver<{ + events: SerializedTaskEvent[]; + }>; + ( + taskBroker.event$ as jest.Mocked<TaskBroker>['event$'] + ).mockImplementation(({ taskId }) => { + return new ObservableImpl(observer => { + subscriber = observer; + setImmediate(() => { + observer.next({ + events: [ + { + id: 0, + taskId, + type: 'log', + createdAt: '', + body: { message: 'My log message' }, + }, + ], }); + observer.next({ + events: [ + { + id: 1, + taskId, + type: 'completion', + createdAt: '', + body: { message: 'Finished!' }, + }, + ], + }); + }); + }); + // emit after this function returned + }); - expect(response.status).toEqual(200); + let statusCode: number | undefined = undefined; + let headers: Record<string, string> = {}; + const responseDataFn = jest.fn(); - expect(response.body).toEqual({ results: [{ title: 'blob' }] }); - expect(handleAutocompleteRequest).toHaveBeenCalledWith({ - token: mockToken, - context, - resource: 'resource', + const req = request(router) + .get('/v2/tasks/a-random-id/eventstream') + .set('accept', 'text/event-stream') + .parse((res, _) => { + ({ statusCode, headers } = res as unknown as { + statusCode: number; + headers: Record<string, string>; + }); + + res.on('data', chunk => { + responseDataFn(chunk.toString()); + + // the server expects the client to abort the request + if (chunk.includes('completion')) { + req.abort(); + } + }); + }); + + // wait for the request to finish + await req.catch(() => { + // ignore 'aborted' error + }); + + expect(statusCode).toBe(200); + expect(headers['content-type']).toBe('text/event-stream'); + expect(responseDataFn).toHaveBeenCalledTimes(2); + expect(responseDataFn).toHaveBeenCalledWith(`event: log +data: {"id":0,"taskId":"a-random-id","type":"log","createdAt":"","body":{"message":"My log message"}} + +`); + expect(responseDataFn).toHaveBeenCalledWith(`event: completion +data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{"message":"Finished!"}} + +`); + + expect(taskBroker.event$).toHaveBeenCalledTimes(1); + expect(taskBroker.event$).toHaveBeenCalledWith({ + taskId: 'a-random-id', + }); + expect(subscriber!.closed).toBe(true); + }); + + it('should return log messages with after query', async () => { + const { unwrappedRouter: router, taskBroker } = await createTestRouter(); + (taskBroker.get as jest.Mocked<TaskBroker>['get']).mockResolvedValue({ + id: 'a-random-id', + spec: {} as any, + status: 'completed', + createdAt: '', + secrets: { + __initiatorCredentials: JSON.stringify(credentials), + }, + createdBy: '', + }); + let subscriber: ZenObservable.SubscriptionObserver<{ + events: SerializedTaskEvent[]; + }>; + ( + taskBroker.event$ as jest.Mocked<TaskBroker>['event$'] + ).mockImplementation(({ taskId }) => { + return new ObservableImpl(observer => { + subscriber = observer; + setImmediate(() => { + observer.next({ + events: [ + { + id: 1, + taskId, + type: 'completion', + createdAt: '', + body: { message: 'Finished!' }, + }, + ], + }); }); }); }); + + let statusCode: number | undefined = undefined; + let headers: Record<string, string> = {}; + + const req = request(router) + .get('/v2/tasks/a-random-id/eventstream') + .query({ after: 10 }) + .set('accept', 'text/event-stream') + .parse((res, _) => { + ({ statusCode, headers } = res as unknown as { + statusCode: number; + headers: Record<string, string>; + }); + + res.on('data', () => { + // close immediately + req.abort(); + }); + }); + + // wait for the request to finish + await req.catch(() => { + // ignore 'aborted' error + }); + + expect(statusCode).toBe(200); + expect(headers['content-type']).toBe('text/event-stream'); + + expect(taskBroker.event$).toHaveBeenCalledTimes(1); + expect(taskBroker.event$).toHaveBeenCalledWith({ + taskId: 'a-random-id', + after: 10, + }); + + expect(subscriber!.closed).toBe(true); }); - }, -); + }); + + describe('GET /v2/tasks/:taskId/events', () => { + it('should return log messages', async () => { + const { router, taskBroker } = await createTestRouter(); + (taskBroker.get as jest.Mocked<TaskBroker>['get']).mockResolvedValue({ + id: 'a-random-id', + spec: {} as any, + status: 'completed', + createdAt: '', + secrets: { + __initiatorCredentials: JSON.stringify(credentials), + }, + createdBy: '', + }); + let subscriber: ZenObservable.SubscriptionObserver<{ + events: SerializedTaskEvent[]; + }>; + ( + taskBroker.event$ as jest.Mocked<TaskBroker>['event$'] + ).mockImplementation(({ taskId }) => { + return new ObservableImpl(observer => { + subscriber = observer; + observer.next({ + events: [ + { + id: 0, + taskId, + type: 'log', + createdAt: '', + body: { message: 'My log message' }, + }, + { + id: 1, + taskId, + type: 'completion', + createdAt: '', + body: { message: 'Finished!' }, + }, + ], + }); + }); + }); + + const response = await request(router).get( + '/v2/tasks/a-random-id/events', + ); + + expect(response.status).toEqual(200); + expect(response.body).toEqual([ + { + id: 0, + taskId: 'a-random-id', + type: 'log', + createdAt: '', + body: { message: 'My log message' }, + }, + { + id: 1, + taskId: 'a-random-id', + type: 'completion', + createdAt: '', + body: { message: 'Finished!' }, + }, + ]); + + expect(taskBroker.event$).toHaveBeenCalledTimes(1); + expect(taskBroker.event$).toHaveBeenCalledWith({ + taskId: 'a-random-id', + }); + expect(subscriber!.closed).toBe(true); + }); + + it('should return log messages with after query', async () => { + const { router, taskBroker } = await createTestRouter(); + (taskBroker.get as jest.Mocked<TaskBroker>['get']).mockResolvedValue({ + id: 'a-random-id', + spec: {} as any, + status: 'completed', + createdAt: '', + secrets: { + __initiatorCredentials: JSON.stringify(credentials), + }, + createdBy: '', + }); + let subscriber: ZenObservable.SubscriptionObserver<{ + events: SerializedTaskEvent[]; + }>; + ( + taskBroker.event$ as jest.Mocked<TaskBroker>['event$'] + ).mockImplementation(() => { + return new ObservableImpl(observer => { + subscriber = observer; + observer.next({ events: [] }); + }); + }); + + const response = await request(router) + .get('/v2/tasks/a-random-id/events') + .query({ after: 10 }); + + expect(response.status).toEqual(200); + expect(response.body).toEqual([]); + + expect(taskBroker.event$).toHaveBeenCalledTimes(1); + expect(taskBroker.event$).toHaveBeenCalledWith({ + taskId: 'a-random-id', + after: 10, + }); + expect(subscriber!.closed).toBe(true); + }); + it('disallows users from seeing events for tasks they do not own', async () => { + const { permissions, router, taskBroker } = await createTestRouter(); + + jest + .spyOn(permissions, 'authorizeConditional') + .mockImplementationOnce(async () => [ + { + conditions: { + resourceType: 'scaffolder-task', + rule: 'IS_TASK_OWNER', + params: { createdBy: ['user'] }, + }, + pluginId: 'scaffolder', + resourceType: 'scaffolder-task', + result: AuthorizeResult.CONDITIONAL, + }, + ]); + (taskBroker.get as jest.Mocked<TaskBroker>['get']).mockResolvedValue({ + id: 'a-random-id', + spec: {} as any, + status: 'completed', + createdAt: '', + secrets: { + __initiatorCredentials: JSON.stringify(credentials), + }, + createdBy: 'not-user', + }); + + const response = await request(router).get( + `/v2/tasks/a-random-id/events`, + ); + expect(taskBroker.get).toHaveBeenCalledWith('a-random-id'); + expect(response.error).not.toBeFalsy(); + }); + }); + + describe('POST /v2/dry-run', () => { + it('should get user entity', async () => { + const { router, catalog } = await createTestRouter(); + const mockToken = mockCredentials.user.token(); + const mockTemplate = generateMockTemplate(); + + await request(router) + .post('/v2/dry-run') + .set('Authorization', `Bearer ${mockToken}`) + .send({ + template: mockTemplate, + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + directoryContents: [], + }); + + expect(catalog.getEntityByRef).toHaveBeenCalledTimes(1); + + expect(catalog.getEntityByRef).toHaveBeenCalledWith( + 'user:default/mock', + expect.anything(), + ); + }); + it('disallows users from seeing tasks they do not own', async () => { + const { permissions, router, taskBroker } = await createTestRouter(); + jest + .spyOn(permissions, 'authorizeConditional') + .mockImplementationOnce(async () => [ + { + conditions: { + resourceType: 'scaffolder-task', + rule: 'IS_TASK_OWNER', + params: { createdBy: ['user'] }, + }, + pluginId: 'scaffolder', + resourceType: 'scaffolder-task', + result: AuthorizeResult.CONDITIONAL, + }, + ]); + const response = await request(router).get( + `/v2/tasks?createdBy=not-user`, + ); + expect(taskBroker.list).toHaveBeenCalledWith({ + filters: { createdBy: ['not-user'], status: undefined }, + order: undefined, + pagination: { limit: undefined, offset: undefined }, + permissionFilters: { key: 'created_by', values: ['user'] }, + }); + expect(response.status).toBe(200); + expect(response.body.totalTasks).toBe(0); + expect(response.body.tasks).toEqual([]); + }); + }); + + describe('GET /v2/autocomplete/:provider/:resource', () => { + it('should throw an error when the provider is not registered', async () => { + const handleAutocompleteRequest = jest.fn().mockResolvedValue({ + results: [{ title: 'blob' }], + }); + const { router } = await createTestRouter({ + autocompleteHandlers: { + 'test-provider': handleAutocompleteRequest, + }, + }); + + const response = await request(router) + .post('/v2/autocomplete/unknown-provider/resource') + .send({ + token: 'token', + context: {}, + }); + + expect(response.status).toEqual(400); + expect(response.body).toEqual( + expect.objectContaining({ + error: { + message: 'Unsupported provider: unknown-provider', + name: 'InputError', + }, + }), + ); + }); + + it('should call the autocomplete handler', async () => { + const handleAutocompleteRequest = jest.fn().mockResolvedValue({ + results: [{ id: 'a-random-id', title: 'blob' }], + }); + + const { router } = await createTestRouter({ + autocompleteHandlers: { + 'test-provider': handleAutocompleteRequest, + }, + }); + + const context = { mock: 'context' }; + const mockToken = 'mocktoken'; + + const response = await request(router) + .post('/v2/autocomplete/test-provider/resource') + .send({ + token: mockToken, + context, + }); + + expect(response.status).toEqual(200); + + expect(response.body).toEqual({ + results: [{ id: 'a-random-id', title: 'blob' }], + }); + expect(handleAutocompleteRequest).toHaveBeenCalledWith({ + token: mockToken, + context, + resource: 'resource', + }); + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index e5c384987a..9315e8ccb2 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,30 +34,28 @@ 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, + createConditionTransformer, + ConditionTransformer, } from '@backstage/plugin-permission-node'; import { TaskSpec, - TemplateEntityStepV1beta3, TemplateEntityV1beta3, templateEntityV1beta3Validator, - TemplateParametersV1beta3, } from '@backstage/plugin-scaffolder-common'; import { RESOURCE_TYPE_SCAFFOLDER_ACTION, RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, + RESOURCE_TYPE_SCAFFOLDER_TASK, scaffolderActionPermissions, + scaffolderTaskPermissions, scaffolderPermissions, scaffolderTemplatePermissions, taskCancelPermission, @@ -82,17 +77,14 @@ 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, @@ -100,16 +92,19 @@ import { import { createDryRunner } from '../scaffolder/dryrun'; import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker'; import { InternalTaskSecrets } from '../scaffolder/tasks/types'; -import { checkPermission } from '../util/checkPermissions'; +import { createOpenApiRouter } from '../schema/openapi'; +import { + checkPermission, + checkTaskPermission, + getAuthorizeConditions, +} from '../util/checkPermissions'; import { findTemplate, getEntityBaseUrl, getWorkingDirectory, - parseNumberParam, parseStringsParam, } from './helpers'; -import { scaffolderActionRules, scaffolderTemplateRules } from './rules'; -import { HostDiscovery } from '@backstage/backend-defaults/discovery'; + import { convertFiltersToRecord, convertGlobalsToRecord, @@ -118,63 +113,36 @@ import { extractGlobalValueMetadata, } from '../util/templating'; import { createDefaultFilters } from '../lib/templating/filters/createDefaultFilters'; +import { + ScaffolderPermissionRuleInput, + TaskPermissionRuleInput, + isTaskPermissionRuleInput, + ActionPermissionRuleInput, + isActionPermissionRuleInput, + isTemplatePermissionRuleInput, + TemplatePermissionRuleInput, +} from './permissions'; +import { CatalogService } from '@backstage/plugin-catalog-node'; -/** - * - * @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; -} +import { + scaffolderActionRules, + scaffolderTemplateRules, + scaffolderTaskRules, +} from './rules'; -/** - * - * @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 { TaskFilters } from '@backstage/plugin-scaffolder-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<any, any, any>[]; - /** - * @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 @@ -189,13 +157,9 @@ export interface RouterOptions { | CreatedTemplateGlobal[]; additionalWorkspaceProviders?: Record<string, WorkspaceProvider>; permissions?: PermissionsService; - permissionRules?: Array< - TemplatePermissionRuleInput | ActionPermissionRuleInput - >; - auth?: AuthService; - httpAuth?: HttpAuthService; - identity?: IdentityApi; - discovery?: DiscoveryService; + permissionRules?: Array<ScaffolderPermissionRuleInput>; + auth: AuthService; + httpAuth: HttpAuthService; events?: EventsService; auditor?: AuditorService; autocompleteHandlers?: Record<string, AutocompleteHandler>; @@ -205,70 +169,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,43 +182,33 @@ 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, ): Promise<express.Router> { - const router = Router(); + const router = await createOpenApiRouter(); // Be generous in upload size to support a wide range of templates in dry-run mode. router.use(express.json({ limit: '10MB' })); 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 +281,24 @@ 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, + config, + 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()); @@ -450,15 +329,24 @@ export async function createRouter( const actionRules: ActionPermissionRuleInput[] = Object.values( scaffolderActionRules, ); + const taskRules: TaskPermissionRuleInput[] = + Object.values(scaffolderTaskRules); if (permissionRules) { templateRules.push( ...permissionRules.filter(isTemplatePermissionRuleInput), ); actionRules.push(...permissionRules.filter(isActionPermissionRuleInput)); + taskRules.push(...permissionRules.filter(isTaskPermissionRuleInput)); } - const isAuthorized = createConditionAuthorizer(Object.values(templateRules)); + const isTemplateAuthorized = createConditionAuthorizer( + Object.values(templateRules), + ); + const isTaskAuthorized = createConditionAuthorizer(Object.values(taskRules)); + + const taskTransformConditions: ConditionTransformer<TaskFilters> = + createConditionTransformer(Object.values(taskRules)); const permissionIntegrationRouter = createPermissionIntegrationRouter({ resources: [ @@ -472,6 +360,18 @@ export async function createRouter( permissions: scaffolderActionPermissions, rules: actionRules, }, + { + resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, + permissions: scaffolderTaskPermissions, + rules: taskRules, + getResources: async resourceRefs => { + return Promise.all( + resourceRefs.map(async taskId => { + return await taskBroker.get(taskId); + }), + ); + }, + }, ], permissions: scaffolderPermissions, }); @@ -493,16 +393,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(); @@ -520,8 +411,10 @@ export async function createRouter( description: template.metadata.description, 'ui:options': template.metadata['ui:options'], steps: parameters.map(schema => ({ - title: schema.title ?? 'Please enter the following information', - description: schema.description, + title: + (schema.title as string) ?? + 'Please enter the following information', + description: schema.description as string, schema, })), EXPERIMENTAL_formDecorators: @@ -581,17 +474,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 +492,6 @@ export async function createRouter( const template = await authorizeTemplate( { kind, namespace, name }, - token, credentials, ); @@ -652,7 +539,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 @@ -685,11 +572,6 @@ export async function createRouter( try { const credentials = await httpAuth.credentials(req); - await checkPermission({ - credentials, - permissions: [taskReadPermission], - permissionService: permissions, - }); if (!taskBroker.list) { throw new Error( @@ -714,8 +596,14 @@ export async function createRouter( }; }); - const limit = parseNumberParam(req.query.limit, 'limit'); - const offset = parseNumberParam(req.query.offset, 'offset'); + const { limit, offset } = req.query; + + const taskPermissionFilters = await getAuthorizeConditions({ + credentials: credentials, + permission: taskReadPermission, + permissionService: permissions, + transformConditions: taskTransformConditions, + }); const tasks = await taskBroker.list({ filters: { @@ -724,9 +612,10 @@ export async function createRouter( }, order, pagination: { - limit: limit ? limit[0] : undefined, - offset: offset ? offset[0] : undefined, + limit, + offset, }, + permissionFilters: taskPermissionFilters, }); await auditorEvent?.success(); @@ -751,13 +640,17 @@ export async function createRouter( try { const credentials = await httpAuth.credentials(req); - await checkPermission({ + + const task = await taskBroker.get(taskId); + + await checkTaskPermission({ credentials, permissions: [taskReadPermission], permissionService: permissions, + task: task, + isTaskAuthorized, }); - const task = await taskBroker.get(taskId); if (!task) { throw new NotFoundError(`Task with id ${taskId} does not exist`); } @@ -787,11 +680,14 @@ export async function createRouter( try { const credentials = await httpAuth.credentials(req); + const task = await taskBroker.get(taskId); // Requires both read and cancel permissions - await checkPermission({ + await checkTaskPermission({ credentials, permissions: [taskCancelPermission, taskReadPermission], permissionService: permissions, + task: task, + isTaskAuthorized, }); await taskBroker.cancel?.(taskId); @@ -819,23 +715,50 @@ export async function createRouter( try { const credentials = await httpAuth.credentials(req); - // Requires both read and cancel permissions + const task = await taskBroker.get(taskId); + + // Requires both read and create permissions await checkPermission({ credentials, - permissions: [taskCreatePermission, taskReadPermission], + permissions: [taskCreatePermission], permissionService: permissions, }); + await checkTaskPermission({ + credentials, + permissions: [taskReadPermission], + permissionService: permissions, + task: task, + isTaskAuthorized, + }); + await auditorEvent?.success(); - await taskBroker.retry?.(taskId); + const { token } = await auth.getPluginRequestToken({ + onBehalfOf: credentials, + targetPluginId: 'catalog', + }); + + const secrets: InternalTaskSecrets = { + ...req.body.secrets, + backstageToken: token, + __initiatorCredentials: JSON.stringify({ + ...credentials, + // credentials.token is nonenumerable and will not be serialized, so we need to add it explicitly + token: (credentials as any).token, + }), + }; + + await taskBroker.retry?.({ secrets, taskId }); res.status(201).json({ id: taskId }); } catch (err) { await auditorEvent?.fail({ error: err }); throw err; } - }) - .get('/v2/tasks/:taskId/eventstream', async (req, res) => { + }); + (router as express.Router).get( + '/v2/tasks/:taskId/eventstream', + async (req, res) => { const { taskId } = req.params; const auditorEvent = await auditor?.createEvent({ @@ -849,10 +772,14 @@ export async function createRouter( try { const credentials = await httpAuth.credentials(req); - await checkPermission({ + const task = await taskBroker.get(taskId); + + await checkTaskPermission({ credentials, permissions: [taskReadPermission], permissionService: permissions, + task: task, + isTaskAuthorized, }); const after = @@ -906,7 +833,9 @@ export async function createRouter( await auditorEvent?.fail({ error: err }); throw err; } - }) + }, + ); + router .get('/v2/tasks/:taskId/events', async (req, res) => { const { taskId } = req.params; @@ -921,10 +850,14 @@ export async function createRouter( try { const credentials = await httpAuth.credentials(req); - await checkPermission({ + const task = await taskBroker.get(taskId); + + await checkTaskPermission({ credentials, permissions: [taskReadPermission], permissionService: permissions, + task: task, + isTaskAuthorized, }); const after = Number(req.query.after) || undefined; @@ -995,17 +928,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}:${ @@ -1071,7 +999,7 @@ export async function createRouter( })), secrets: { ...body.secrets, - ...(token && { backstageToken: token }), + backstageToken: (credentials as any).token, }, credentials, }); @@ -1134,13 +1062,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)) { @@ -1167,18 +1094,18 @@ export async function createRouter( // Authorize parameters if (Array.isArray(template.spec.parameters)) { template.spec.parameters = template.spec.parameters.filter(step => - isAuthorized(parameterDecision, step), + isTemplateAuthorized(parameterDecision, step), ); } else if ( template.spec.parameters && - !isAuthorized(parameterDecision, template.spec.parameters) + !isTemplateAuthorized(parameterDecision, template.spec.parameters) ) { template.spec.parameters = undefined; } // Authorize steps template.spec.steps = template.spec.steps.filter(step => - isAuthorized(stepDecision, step), + isTemplateAuthorized(stepDecision, step), ); return template; diff --git a/plugins/scaffolder-backend/src/service/rules.test.ts b/plugins/scaffolder-backend/src/service/rules.test.ts index 8e24cacb32..85e393247a 100644 --- a/plugins/scaffolder-backend/src/service/rules.test.ts +++ b/plugins/scaffolder-backend/src/service/rules.test.ts @@ -22,10 +22,13 @@ import { hasProperty, hasStringProperty, hasTag, + isTaskOwner, } from './rules'; import { createConditionAuthorizer } from '@backstage/plugin-permission-node'; import { RESOURCE_TYPE_SCAFFOLDER_ACTION } from '@backstage/plugin-scaffolder-common/alpha'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { SerializedTask } from '@backstage/plugin-scaffolder-node'; +import { TaskSpec } from '@backstage/plugin-scaffolder-common'; describe('hasTag', () => { describe('apply', () => { @@ -523,3 +526,77 @@ describe('hasStringProperty', () => { ); }); }); + +describe('isTaskOwner', () => { + describe('apply', () => { + const task: SerializedTask = { + id: 'a-random-id', + spec: {} as TaskSpec, + status: 'completed', + createdAt: '', + createdBy: 'user:default/user-1', + }; + it('returns false when createdBy is an empty array', () => { + expect( + isTaskOwner.apply(task, { + createdBy: [], + }), + ).toEqual(false); + }); + it('returns false when createdBy is not matched (single user in createdBy)', () => { + expect( + isTaskOwner.apply(task, { + createdBy: ['not-matched'], + }), + ).toEqual(false); + }); + it('returns true when createdBy matches (single user in createdBy)', () => { + expect( + isTaskOwner.apply(task, { + createdBy: ['user:default/user-1'], + }), + ).toEqual(true); + }); + it('returns false when createdBy is not matched (multiple users in createdBy)', () => { + expect( + isTaskOwner.apply(task, { + createdBy: [ + 'user:default/user-2', + 'user:default/user-3', + 'user:default/user-4', + ], + }), + ).toEqual(false); + }); + it('returns true when createdBy matches (multiple users in createdBy)', () => { + expect( + isTaskOwner.apply(task, { + createdBy: [ + 'user:default/user-1', + 'user:default/user-2', + 'user:default/user-3', + ], + }), + ).toEqual(true); + }); + }); + describe('toQuery', () => { + it('returns the correct query filter with values (single user in createdBy)', () => { + expect( + isTaskOwner.toQuery({ + createdBy: ['user:default/user-1'], + }), + ).toEqual({ key: 'created_by', values: ['user:default/user-1'] }); + }); + }); + it('returns the correct query filter with values (multiple users in createdBy)', () => { + expect( + isTaskOwner.toQuery({ + createdBy: ['user:default/user-1', 'user:default/user-2'], + }), + ).toEqual({ + key: 'created_by', + values: ['user:default/user-1', 'user:default/user-2'], + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/service/rules.ts b/plugins/scaffolder-backend/src/service/rules.ts index b197810757..5753668089 100644 --- a/plugins/scaffolder-backend/src/service/rules.ts +++ b/plugins/scaffolder-backend/src/service/rules.ts @@ -15,9 +15,11 @@ */ import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; + import { RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, RESOURCE_TYPE_SCAFFOLDER_ACTION, + RESOURCE_TYPE_SCAFFOLDER_TASK, } from '@backstage/plugin-scaffolder-common/alpha'; import { @@ -25,6 +27,8 @@ import { TemplateParametersV1beta3, } from '@backstage/plugin-scaffolder-common'; +import { SerializedTask, TaskFilter } from '@backstage/plugin-scaffolder-node'; + import { z } from 'zod'; import { JsonObject, JsonPrimitive } from '@backstage/types'; import { get } from 'lodash'; @@ -129,6 +133,37 @@ function buildHasProperty<Schema extends z.ZodType<JsonPrimitive>>({ }); } +export const createTaskPermissionRule = makeCreatePermissionRule< + SerializedTask, + TaskFilter, + typeof RESOURCE_TYPE_SCAFFOLDER_TASK +>(); + +export const isTaskOwner = createTaskPermissionRule({ + name: 'IS_TASK_OWNER', + description: 'Allows tasks created by certain users to be accessible', + resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, + paramsSchema: z.object({ + createdBy: z + .array(z.string()) + .describe( + 'List of creater entity refs; only tasks created by these users will be viewable', + ), + }), + apply: (resource, { createdBy }) => { + if (!resource.createdBy) { + return false; + } + return createdBy.includes(resource.createdBy); + }, + toQuery: ({ createdBy }) => { + return { + key: 'created_by', + values: createdBy, + }; + }, +}); + export const scaffolderTemplateRules = { hasTag }; export const scaffolderActionRules = { hasActionId, @@ -136,3 +171,4 @@ export const scaffolderActionRules = { hasNumberProperty, hasStringProperty, }; +export const scaffolderTaskRules = { isTaskOwner }; diff --git a/plugins/scaffolder-backend/src/util/checkPermissions.ts b/plugins/scaffolder-backend/src/util/checkPermissions.ts index b6c0395fbb..08a4ae1b13 100644 --- a/plugins/scaffolder-backend/src/util/checkPermissions.ts +++ b/plugins/scaffolder-backend/src/util/checkPermissions.ts @@ -21,7 +21,13 @@ import { NotAllowedError } from '@backstage/errors'; import { AuthorizeResult, BasicPermission, + PermissionCriteria, + PolicyDecision, + ResourcePermission, } from '@backstage/plugin-permission-common'; +import { ConditionTransformer } from '@backstage/plugin-permission-node'; +import { SerializedTask } from '@backstage/plugin-scaffolder-node'; +import { TaskFilters } from '@backstage/plugin-scaffolder-node'; export type checkPermissionOptions = { credentials: BackstageCredentials; @@ -29,6 +35,24 @@ export type checkPermissionOptions = { permissionService?: PermissionsService; }; +export type checkTaskPermissionOptions = { + credentials: BackstageCredentials; + permissions: ResourcePermission[]; + permissionService?: PermissionsService; + task: SerializedTask; + isTaskAuthorized: ( + decision: PolicyDecision, + resource: SerializedTask | undefined, + ) => boolean; +}; + +export type authorizeConditionsOptions = { + credentials: BackstageCredentials; + permission: ResourcePermission; + permissionService?: PermissionsService; + transformConditions: ConditionTransformer<TaskFilters>; +}; + /** * Does a basic check on permissions. Throws 403 error if any permission responds with AuthorizeResult.DENY * @public @@ -51,3 +75,55 @@ export async function checkPermission(options: checkPermissionOptions) { } } } + +/** + * Does a conditional permission check for scaffolder task reading and cancellation. + * Throws 403 error if permission responds with AuthorizeResult.DENY, or does not resolve to true during the conditional rule check + * @public + */ +export async function checkTaskPermission(options: checkTaskPermissionOptions) { + const { + permissions, + permissionService, + credentials, + task, + isTaskAuthorized, + } = options; + if (permissionService) { + const permissionRequest = permissions.map(permission => ({ + permission, + })); + const authorizationResponses = await permissionService.authorizeConditional( + permissionRequest, + { credentials }, + ); + for (const response of authorizationResponses) { + if ( + response.result === AuthorizeResult.DENY || + !isTaskAuthorized(response, task) + ) { + throw new NotAllowedError(); + } + } + } +} + +/** Fetches and transforms authorization conditions into filters, or returns `undefined` if the decision is not conditional. + * @public + */ +export const getAuthorizeConditions = async ( + options: authorizeConditionsOptions, +): Promise<PermissionCriteria<TaskFilters> | undefined> => { + const { permission, permissionService, credentials, transformConditions } = + options; + if (permissionService) { + const [taskDecision] = await permissionService.authorizeConditional( + [{ permission: permission }], + { credentials }, + ); + if (taskDecision.result === AuthorizeResult.CONDITIONAL) { + return transformConditions(taskDecision.conditions); + } + } + return undefined; +}; diff --git a/plugins/scaffolder-common/.eslintrc.js b/plugins/scaffolder-common/.eslintrc.js index e2a53a6ad2..c8ec42e57a 100644 --- a/plugins/scaffolder-common/.eslintrc.js +++ b/plugins/scaffolder-common/.eslintrc.js @@ -1 +1,10 @@ -module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + overrides: [ + { + files: ['src/schema/openapi/generated/models/*.model.ts'], + rules: { + '@typescript-eslint/no-redeclare': 'off', + }, + }, + ], +}); diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index 85dc444a53..ef8efc3273 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,63 @@ # @backstage/plugin-scaffolder-common +## 1.7.0-next.0 + +### Minor Changes + +- c08cbc4: Move Scaffolder API to OpenAPI + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.1 + +## 1.6.0 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + +## 1.6.0-next.1 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +## 1.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + +## 1.5.11 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/types@1.2.1 + ## 1.5.11-next.0 ### Patch Changes diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index 487b5a92a1..2801c519b2 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-common", - "version": "1.5.11-next.0", + "version": "1.7.0-next.0", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", "backstage": { "role": "common-library", @@ -61,10 +61,20 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/integration": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", - "@backstage/types": "workspace:^" + "@backstage/types": "workspace:^", + "@microsoft/fetch-event-source": "^2.0.1", + "@types/json-schema": "^7.0.9", + "cross-fetch": "^4.0.0", + "json-schema": "^0.4.0", + "uri-template": "^2.0.0", + "zen-observable": "^0.10.0" }, "devDependencies": { - "@backstage/cli": "workspace:^" + "@backstage/cli": "workspace:^", + "@backstage/test-utils": "workspace:^", + "msw": "^1.0.0" } } diff --git a/plugins/scaffolder-common/report-alpha.api.md b/plugins/scaffolder-common/report-alpha.api.md index 97a3de63c5..0bed459cc8 100644 --- a/plugins/scaffolder-common/report-alpha.api.md +++ b/plugins/scaffolder-common/report-alpha.api.md @@ -12,6 +12,9 @@ export const actionExecutePermission: ResourcePermission<'scaffolder-action'>; // @alpha export const RESOURCE_TYPE_SCAFFOLDER_ACTION = 'scaffolder-action'; +// @alpha +export const RESOURCE_TYPE_SCAFFOLDER_TASK = 'scaffolder-task'; + // @alpha export const RESOURCE_TYPE_SCAFFOLDER_TEMPLATE = 'scaffolder-template'; @@ -23,22 +26,26 @@ export const scaffolderPermissions: ( | BasicPermission | ResourcePermission<'scaffolder-action'> | ResourcePermission<'scaffolder-template'> + | ResourcePermission<'scaffolder-task'> )[]; // @alpha -export const scaffolderTaskPermissions: BasicPermission[]; +export const scaffolderTaskPermissions: ( + | BasicPermission + | ResourcePermission<'scaffolder-task'> +)[]; // @alpha export const scaffolderTemplatePermissions: ResourcePermission<'scaffolder-template'>[]; // @alpha -export const taskCancelPermission: BasicPermission; +export const taskCancelPermission: ResourcePermission<'scaffolder-task'>; // @alpha export const taskCreatePermission: BasicPermission; // @alpha -export const taskReadPermission: BasicPermission; +export const taskReadPermission: ResourcePermission<'scaffolder-task'>; // @alpha export const templateManagementPermission: BasicPermission; diff --git a/plugins/scaffolder-common/report.api.md b/plugins/scaffolder-common/report.api.md index 455921c085..c6c5141cf2 100644 --- a/plugins/scaffolder-common/report.api.md +++ b/plugins/scaffolder-common/report.api.md @@ -7,15 +7,353 @@ import { Entity } from '@backstage/catalog-model'; import type { EntityMeta } from '@backstage/catalog-model'; import type { JsonArray } from '@backstage/types'; import { JsonObject } from '@backstage/types'; -import type { JsonValue } from '@backstage/types'; +import { JSONSchema7 } from 'json-schema'; +import { JsonValue } from '@backstage/types'; import { KindValidator } from '@backstage/catalog-model'; +import { Observable } from '@backstage/types'; +import { ScmIntegrationRegistry } from '@backstage/integration'; import type { UserEntity } from '@backstage/catalog-model'; +// @public +export type Action = { + id: string; + description?: string; + schema?: { + input?: JSONSchema7; + output?: JSONSchema7; + }; + examples?: ScaffolderUsageExample[]; +}; + // @public export const isTemplateEntityV1beta3: ( entity: Entity, ) => entity is TemplateEntityV1beta3; +// @public +export type ListActionsResponse = Array<Action>; + +// @public +export type ListTemplatingExtensionsResponse = { + filters: Record<string, TemplateFilter>; + globals: { + functions: Record<string, TemplateGlobalFunction>; + values: Record<string, TemplateGlobalValue>; + }; +}; + +// @public +export type LogEvent = { + type: TaskEventType; + body: { + message: string; + stepId?: string; + status?: ScaffolderTaskStatus; + }; + createdAt: string; + id: number; + taskId: string; +}; + +// @public +export interface ScaffolderApi { + // (undocumented) + autocomplete?( + request: { + token: string; + provider: string; + resource: string; + context: Record<string, string>; + }, + options?: ScaffolderRequestOptions, + ): Promise<{ + results: { + title?: string; + id: string; + }[]; + }>; + cancelTask( + taskId: string, + options?: ScaffolderRequestOptions, + ): Promise<{ + status?: ScaffolderTaskStatus; + }>; + // (undocumented) + dryRun?( + request: ScaffolderDryRunOptions, + options?: ScaffolderRequestOptions, + ): Promise<ScaffolderDryRunResponse>; + // (undocumented) + getIntegrationsList( + options: ScaffolderGetIntegrationsListOptions, + ): Promise<ScaffolderGetIntegrationsListResponse>; + // (undocumented) + getTask( + taskId: string, + options?: ScaffolderRequestOptions, + ): Promise<ScaffolderTask>; + // (undocumented) + getTemplateParameterSchema( + templateRef: string, + options?: ScaffolderRequestOptions, + ): Promise<TemplateParameterSchema>; + listActions(options?: ScaffolderRequestOptions): Promise<ListActionsResponse>; + // (undocumented) + listTasks?( + request: { + filterByOwnership: 'owned' | 'all'; + limit?: number; + offset?: number; + }, + options?: ScaffolderRequestOptions, + ): Promise<{ + tasks: ScaffolderTask[]; + totalTasks?: number; + }>; + listTemplatingExtensions?( + options?: ScaffolderRequestOptions, + ): Promise<ListTemplatingExtensionsResponse>; + retry?( + taskId: string, + options?: ScaffolderRequestOptions, + ): Promise<{ + id: string; + }>; + scaffold( + request: ScaffolderScaffoldOptions, + options?: ScaffolderRequestOptions, + ): Promise<ScaffolderScaffoldResponse>; + // (undocumented) + streamLogs( + request: ScaffolderStreamLogsOptions, + options?: ScaffolderRequestOptions, + ): Observable<LogEvent>; +} + +// @public +export class ScaffolderClient implements ScaffolderApi { + constructor(options: { + discoveryApi: { + getBaseUrl(pluginId: string): Promise<string>; + }; + fetchApi: { + fetch: typeof fetch; + }; + identityApi?: { + getBackstageIdentity(): Promise<{ + type: 'user'; + userEntityRef: string; + ownershipEntityRefs: string[]; + }>; + }; + scmIntegrationsApi: ScmIntegrationRegistry; + useLongPollingLogs?: boolean; + }); + autocomplete({ + token, + resource, + provider, + context, + }: { + token: string; + provider: string; + resource: string; + context: Record<string, string>; + }): Promise<{ + results: { + title?: string; + id: string; + }[]; + }>; + cancelTask( + taskId: string, + options?: ScaffolderRequestOptions, + ): Promise<{ + status?: ScaffolderTaskStatus; + }>; + // (undocumented) + dryRun( + request: ScaffolderDryRunOptions, + options?: ScaffolderRequestOptions, + ): Promise<ScaffolderDryRunResponse>; + // (undocumented) + getIntegrationsList( + options: ScaffolderGetIntegrationsListOptions, + ): Promise<ScaffolderGetIntegrationsListResponse>; + // (undocumented) + getTask( + taskId: string, + options?: ScaffolderRequestOptions, + ): Promise<ScaffolderTask>; + // (undocumented) + getTemplateParameterSchema( + templateRef: string, + options?: ScaffolderRequestOptions, + ): Promise<TemplateParameterSchema>; + listActions(options?: ScaffolderRequestOptions): Promise<ListActionsResponse>; + // (undocumented) + listTasks( + request: { + filterByOwnership: 'owned' | 'all'; + limit?: number; + offset?: number; + }, + options?: ScaffolderRequestOptions, + ): Promise<{ + tasks: ScaffolderTask[]; + totalTasks?: number; + }>; + listTemplatingExtensions( + options?: ScaffolderRequestOptions, + ): Promise<ListTemplatingExtensionsResponse>; + retry?( + taskId: string, + options?: ScaffolderRequestOptions, + ): Promise<{ + id: string; + }>; + scaffold( + request: ScaffolderScaffoldOptions, + options?: ScaffolderRequestOptions, + ): Promise<ScaffolderScaffoldResponse>; + // (undocumented) + streamLogs( + request: ScaffolderStreamLogsOptions, + options?: ScaffolderRequestOptions, + ): Observable<LogEvent>; +} + +// @public (undocumented) +export interface ScaffolderDryRunOptions { + // (undocumented) + directoryContents: { + path: string; + base64Content: string; + }[]; + // (undocumented) + secrets?: Record<string, string>; + // (undocumented) + template: TemplateEntityV1beta3; + // (undocumented) + values: JsonObject; +} + +// @public (undocumented) +export interface ScaffolderDryRunResponse { + // (undocumented) + directoryContents: Array<{ + path: string; + base64Content: string; + executable?: boolean; + }>; + // (undocumented) + log: Array<Pick<LogEvent, 'body'>>; + // (undocumented) + output: ScaffolderTaskOutput; + // (undocumented) + steps: TaskStep[]; +} + +// @public +export interface ScaffolderGetIntegrationsListOptions { + // (undocumented) + allowedHosts: string[]; +} + +// @public +export interface ScaffolderGetIntegrationsListResponse { + // (undocumented) + integrations: { + type: string; + title: string; + host: string; + }[]; +} + +// @public (undocumented) +export type ScaffolderOutputLink = { + title?: string; + icon?: string; + url?: string; + entityRef?: string; +}; + +// @public (undocumented) +export type ScaffolderOutputText = { + title?: string; + icon?: string; + content?: string; + default?: boolean; +}; + +// @public +export interface ScaffolderRequestOptions { + // (undocumented) + token?: string; +} + +// @public +export interface ScaffolderScaffoldOptions { + // (undocumented) + secrets?: Record<string, string>; + // (undocumented) + templateRef: string; + // (undocumented) + values: Record<string, JsonValue>; +} + +// @public +export interface ScaffolderScaffoldResponse { + // (undocumented) + taskId: string; +} + +// @public +export interface ScaffolderStreamLogsOptions { + // (undocumented) + after?: number; + // (undocumented) + isTaskRecoverable?: boolean; + // (undocumented) + taskId: string; +} + +// @public +export type ScaffolderTask = { + id: string; + spec: TaskSpec; + status: ScaffolderTaskStatus; + lastHeartbeatAt?: string; + createdAt: string; +}; + +// @public (undocumented) +export type ScaffolderTaskOutput = { + links?: ScaffolderOutputLink[]; + text?: ScaffolderOutputText[]; +} & { + [key: string]: unknown; +}; + +// @public (undocumented) +export type ScaffolderTaskStatus = + | 'cancelled' + | 'completed' + | 'failed' + | 'open' + | 'processing' + | 'skipped'; + +// @public +export type ScaffolderUsageExample = { + description?: string; + example: string; + notes?: string; +}; + +// @public (undocumented) +export type TaskEventType = 'cancelled' | 'completion' | 'log' | 'recovered'; + // @public export type TaskRecoverStrategy = 'none' | 'startOver'; @@ -94,6 +432,33 @@ export interface TemplateEntityV1beta3 extends Entity { // @public export const templateEntityV1beta3Validator: KindValidator; +// @public +export type TemplateFilter = { + description?: string; + schema?: { + input?: JSONSchema7; + arguments?: JSONSchema7[]; + output?: JSONSchema7; + }; + examples?: ScaffolderUsageExample[]; +}; + +// @public +export type TemplateGlobalFunction = { + description?: string; + schema?: { + arguments?: JSONSchema7[]; + output?: JSONSchema7; + }; + examples?: ScaffolderUsageExample[]; +}; + +// @public +export type TemplateGlobalValue = { + description?: string; + value: JsonValue; +}; + // @public export type TemplateInfo = { entityRef: string; @@ -103,6 +468,22 @@ export type TemplateInfo = { }; }; +// @public +export type TemplateParameterSchema = { + title: string; + description?: string; + presentation?: TemplatePresentationV1beta3; + steps: Array<{ + title: string; + description?: string; + schema: JsonObject; + }>; + EXPERIMENTAL_formDecorators?: { + id: string; + input?: JsonObject; + }[]; +}; + // @public export interface TemplateParametersV1beta3 extends JsonObject { // (undocumented) diff --git a/plugins/scaffolder/src/api.test.ts b/plugins/scaffolder-common/src/ScaffolderClient.test.ts similarity index 97% rename from plugins/scaffolder/src/api.test.ts rename to plugins/scaffolder-common/src/ScaffolderClient.test.ts index 1df2dacb17..03064d7086 100644 --- a/plugins/scaffolder/src/api.test.ts +++ b/plugins/scaffolder-common/src/ScaffolderClient.test.ts @@ -14,12 +14,15 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; -import { MockFetchApi, registerMswTestHooks } from '@backstage/test-utils'; +import { + MockFetchApi, + mockApis, + registerMswTestHooks, +} from '@backstage/test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { ScaffolderClient } from './api'; +import { ScaffolderClient } from './ScaffolderClient'; import { fetchEventSource } from '@microsoft/fetch-event-source'; jest.mock('@microsoft/fetch-event-source'); @@ -31,6 +34,17 @@ const server = setupServer(); describe('api', () => { registerMswTestHooks(server); + const githubIntegrationConfig = mockApis.config({ + data: { + integrations: { + github: [ + { + host: 'hello.com', + }, + ], + }, + }, + }); const mockBaseUrl = 'http://backstage/api'; const discoveryApi = { getBaseUrl: async () => mockBaseUrl }; @@ -43,15 +57,7 @@ describe('api', () => { }; const scmIntegrationsApi = ScmIntegrations.fromConfig( - new ConfigReader({ - integrations: { - github: [ - { - host: 'hello.com', - }, - ], - }, - }), + githubIntegrationConfig, ); let apiClient: ScaffolderClient; diff --git a/plugins/scaffolder-common/src/ScaffolderClient.ts b/plugins/scaffolder-common/src/ScaffolderClient.ts new file mode 100644 index 0000000000..d40f7a4637 --- /dev/null +++ b/plugins/scaffolder-common/src/ScaffolderClient.ts @@ -0,0 +1,426 @@ +/* + * 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 { parseEntityRef } from '@backstage/catalog-model'; +import { ResponseError } from '@backstage/errors'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { Observable } from '@backstage/types'; +import { + EventSourceMessage, + fetchEventSource, +} from '@microsoft/fetch-event-source'; +import ObservableImpl from 'zen-observable'; + +import { type TemplateParameterSchema } from './TemplateEntityV1beta3'; +import { + ListActionsResponse, + ListTemplatingExtensionsResponse, + LogEvent, + ScaffolderApi, + ScaffolderDryRunOptions, + ScaffolderDryRunResponse, + ScaffolderGetIntegrationsListOptions, + ScaffolderGetIntegrationsListResponse, + ScaffolderRequestOptions, + ScaffolderScaffoldOptions, + ScaffolderScaffoldResponse, + ScaffolderStreamLogsOptions, + ScaffolderTask, +} from './api'; +import { DefaultApiClient, TaskStatus, TypedResponse } from './schema/openapi'; + +/** + * An API to interact with the scaffolder backend. + * + * @public + */ +export class ScaffolderClient implements ScaffolderApi { + private readonly apiClient: DefaultApiClient; + private readonly discoveryApi: { + getBaseUrl(pluginId: string): Promise<string>; + }; + private readonly scmIntegrationsApi: ScmIntegrationRegistry; + private readonly fetchApi: { fetch: typeof fetch }; + private readonly identityApi?: { + getBackstageIdentity(): Promise<{ + type: 'user'; + userEntityRef: string; + ownershipEntityRefs: string[]; + }>; + }; + private readonly useLongPollingLogs: boolean; + + constructor(options: { + discoveryApi: { getBaseUrl(pluginId: string): Promise<string> }; + fetchApi: { fetch: typeof fetch }; + identityApi?: { + getBackstageIdentity(): Promise<{ + type: 'user'; + userEntityRef: string; + ownershipEntityRefs: string[]; + }>; + }; + scmIntegrationsApi: ScmIntegrationRegistry; + useLongPollingLogs?: boolean; + }) { + this.apiClient = new DefaultApiClient(options); + this.discoveryApi = options.discoveryApi; + this.fetchApi = options.fetchApi ?? { fetch }; + this.scmIntegrationsApi = options.scmIntegrationsApi; + this.useLongPollingLogs = options.useLongPollingLogs ?? false; + this.identityApi = options.identityApi; + } + + /** + * {@inheritdoc ScaffolderApi.listTasks} + */ + async listTasks( + request: { + filterByOwnership: 'owned' | 'all'; + limit?: number; + offset?: number; + }, + options?: ScaffolderRequestOptions, + ): Promise<{ tasks: ScaffolderTask[]; totalTasks?: number }> { + if (!this.identityApi) { + throw new Error( + 'IdentityApi is not available in the ScaffolderClient, please pass through the IdentityApi to the ScaffolderClient constructor in order to use the listTasks method', + ); + } + + const { userEntityRef } = await this.identityApi.getBackstageIdentity(); + + return await this.requestRequired( + await this.apiClient.listTasks( + { + query: { + createdBy: + request.filterByOwnership === 'owned' + ? [userEntityRef] + : undefined, + limit: request.limit, + offset: request.offset, + }, + }, + options, + ), + ); + } + + async getIntegrationsList( + options: ScaffolderGetIntegrationsListOptions, + ): Promise<ScaffolderGetIntegrationsListResponse> { + const integrations = [ + ...this.scmIntegrationsApi.azure.list(), + ...this.scmIntegrationsApi.bitbucket + .list() + .filter( + item => + !this.scmIntegrationsApi.bitbucketCloud.byHost(item.config.host) && + !this.scmIntegrationsApi.bitbucketServer.byHost(item.config.host), + ), + ...this.scmIntegrationsApi.bitbucketCloud.list(), + ...this.scmIntegrationsApi.bitbucketServer.list(), + ...this.scmIntegrationsApi.gerrit.list(), + ...this.scmIntegrationsApi.gitea.list(), + ...this.scmIntegrationsApi.github.list(), + ...this.scmIntegrationsApi.gitlab.list(), + ] + .map(c => ({ type: c.type, title: c.title, host: c.config.host })) + .filter(c => options.allowedHosts.includes(c.host)); + + return { + integrations, + }; + } + + /** + * {@inheritdoc ScaffolderApi.getTemplateParameterSchema} + */ + async getTemplateParameterSchema( + templateRef: string, + options?: ScaffolderRequestOptions, + ): Promise<TemplateParameterSchema> { + return await this.requestRequired( + await this.apiClient.getTemplateParameterSchema( + { + path: parseEntityRef(templateRef, { + defaultKind: 'template', + }), + }, + options, + ), + ); + } + + /** + * {@inheritdoc ScaffolderApi.scaffold} + */ + async scaffold( + request: ScaffolderScaffoldOptions, + options?: ScaffolderRequestOptions, + ): Promise<ScaffolderScaffoldResponse> { + const response = await this.apiClient.scaffold( + { + body: request, + }, + options, + ); + + if (response.status !== 201) { + const status = `${response.status} ${response.statusText}`; + const body = await response.text(); + throw new Error(`Backend request failed, ${status} ${body.trim()}`); + } + + const { id } = await response.json(); + return { taskId: id }; + } + + /** + * {@inheritdoc ScaffolderApi.getTask} + */ + async getTask( + taskId: string, + options?: ScaffolderRequestOptions, + ): Promise<ScaffolderTask> { + return await this.requestRequired( + await this.apiClient.getTask( + { + path: { taskId }, + }, + options, + ), + ); + } + + /** + * {@inheritdoc ScaffolderApi.streamLogs} + */ + streamLogs( + request: ScaffolderStreamLogsOptions, + options?: ScaffolderRequestOptions, + ): Observable<LogEvent> { + if (this.useLongPollingLogs) { + return this.streamLogsPolling(request, options); + } + + return this.streamLogsEventStream(request); + } + + /** + * {@inheritdoc ScaffolderApi.dryRun} + */ + async dryRun( + request: ScaffolderDryRunOptions, + options?: ScaffolderRequestOptions, + ): Promise<ScaffolderDryRunResponse> { + return await this.requestRequired( + await this.apiClient.dryRun( + { + body: { + template: request.template, + values: request.values, + secrets: request.secrets, + directoryContents: request.directoryContents, + }, + }, + options, + ), + ); + } + + private streamLogsEventStream({ + isTaskRecoverable, + taskId, + after, + }: ScaffolderStreamLogsOptions): Observable<LogEvent> { + return new ObservableImpl(subscriber => { + const params = new URLSearchParams(); + if (after !== undefined) { + params.set('after', String(Number(after))); + } + + this.discoveryApi.getBaseUrl('scaffolder').then( + baseUrl => { + const url = `${baseUrl}/v2/tasks/${encodeURIComponent( + taskId, + )}/eventstream`; + + const processEvent = (event: any) => { + if (event.data) { + try { + subscriber.next(JSON.parse(event.data)); + } catch (ex) { + subscriber.error(ex); + } + } + }; + + const ctrl = new AbortController(); + void fetchEventSource(url, { + fetch: this.fetchApi.fetch, + signal: ctrl.signal, + onmessage(e: EventSourceMessage) { + if (e.event === 'log') { + processEvent(e); + return; + } else if (e.event === 'completion' && !isTaskRecoverable) { + processEvent(e); + subscriber.complete(); + ctrl.abort(); + return; + } + processEvent(e); + }, + onerror(err) { + subscriber.error(err); + }, + }); + }, + error => { + subscriber.error(error); + }, + ); + }); + } + + private streamLogsPolling( + { + taskId, + after: inputAfter, + }: { + taskId: string; + after?: number; + }, + options?: ScaffolderRequestOptions, + ): Observable<LogEvent> { + let after = inputAfter; + + return new ObservableImpl(subscriber => { + (async () => { + while (!subscriber.closed) { + const response = await this.apiClient.streamLogsPolling( + { + path: { taskId }, + query: { after }, + }, + options, + ); + + if (!response.ok) { + // wait for one second to not run into an + await new Promise(resolve => setTimeout(resolve, 1000)); + continue; + } + + const logs = (await response.json()) as LogEvent[]; + + for (const event of logs) { + after = Number(event.id); + + subscriber.next(event); + + if (event.type === 'completion') { + subscriber.complete(); + return; + } + } + } + })(); + }); + } + + /** + * {@inheritdoc ScaffolderApi.listActions} + */ + async listActions( + options?: ScaffolderRequestOptions, + ): Promise<ListActionsResponse> { + return await this.requestRequired( + await this.apiClient.listActions(null as any, options), + ); + } + + /** + * {@inheritdoc ScaffolderApi.listTemplatingExtensions} + */ + async listTemplatingExtensions( + options?: ScaffolderRequestOptions, + ): Promise<ListTemplatingExtensionsResponse> { + return await this.requestRequired( + await this.apiClient.listTemplatingExtensions(null as any, options), + ); + } + + /** + * {@inheritdoc ScaffolderApi.cancelTask} + */ + async cancelTask( + taskId: string, + options?: ScaffolderRequestOptions, + ): Promise<{ status?: TaskStatus }> { + return await this.requestRequired( + await this.apiClient.cancelTask({ path: { taskId } }, options), + ); + } + + /** + * {@inheritdoc ScaffolderApi.retry} + */ + async retry?( + taskId: string, + options?: ScaffolderRequestOptions, + ): Promise<{ id: string }> { + return await this.requestRequired( + await this.apiClient.retry({ body: {}, path: { taskId } }, options), + ); + } + + /** + * {@inheritdoc ScaffolderApi.retry} + */ + async autocomplete({ + token, + resource, + provider, + context, + }: { + token: string; + provider: string; + resource: string; + context: Record<string, string>; + }): Promise<{ results: { title?: string; id: string }[] }> { + return await this.requestRequired( + await this.apiClient.autocomplete({ + path: { provider, resource }, + body: { token, context }, + }), + ); + } + + // + // Private methods + // + + private async requestRequired<T>(response: TypedResponse<T>): Promise<T> { + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + + return response.json(); + } +} diff --git a/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts b/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts index 304057af77..1851b7e70f 100644 --- a/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts +++ b/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts @@ -142,6 +142,24 @@ export interface TemplateEntityStepV1beta3 extends JsonObject { 'backstage:permissions'?: TemplatePermissionsV1beta3; } +/** + * The shape of each entry of parameters which gets rendered + * as a separate step in the wizard input + * + * @public + */ +export type TemplateParameterSchema = { + title: string; + description?: string; + presentation?: TemplatePresentationV1beta3; + steps: Array<{ + title: string; + description?: string; + schema: JsonObject; + }>; + EXPERIMENTAL_formDecorators?: { id: string; input?: JsonObject }[]; +}; + /** * Parameter that is part of a Template Entity. * diff --git a/plugins/scaffolder-common/src/api.ts b/plugins/scaffolder-common/src/api.ts new file mode 100644 index 0000000000..5425607d34 --- /dev/null +++ b/plugins/scaffolder-common/src/api.ts @@ -0,0 +1,337 @@ +/* + * 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 { JsonObject, JsonValue, Observable } from '@backstage/types'; +import { JSONSchema7 } from 'json-schema'; +import { TaskSpec, TaskStep } from './TaskSpec'; +import type { + TemplateEntityV1beta3, + TemplateParameterSchema, +} from './TemplateEntityV1beta3'; +import type { + TaskEventType, + TaskStatus as ScaffolderTaskStatus, +} from './schema/openapi'; + +export type { ScaffolderTaskStatus, TaskEventType }; + +/** + * Options you can pass into a Scaffolder request for additional information. + * + * @public + */ +export interface ScaffolderRequestOptions { + token?: string; +} + +/** + * The shape of each task returned from the `scaffolder-backend` + * + * @public + */ +export type ScaffolderTask = { + id: string; + spec: TaskSpec; + status: ScaffolderTaskStatus; + lastHeartbeatAt?: string; + createdAt: string; +}; + +/** + * A single scaffolder usage example + * + * @public + */ +export type ScaffolderUsageExample = { + description?: string; + example: string; + notes?: string; +}; + +/** + * The response shape for a single action in the `listActions` call to the `scaffolder-backend` + * + * @public + */ +export type Action = { + id: string; + description?: string; + schema?: { + input?: JSONSchema7; + output?: JSONSchema7; + }; + examples?: ScaffolderUsageExample[]; +}; + +/** + * The response shape for the `listActions` call to the `scaffolder-backend` + * + * @public + */ +export type ListActionsResponse = Array<Action>; + +/** + * The response shape for a single filter in the `listTemplatingExtensions` call to the `scaffolder-backend` + * + * @public + */ +export type TemplateFilter = { + description?: string; + schema?: { + input?: JSONSchema7; + arguments?: JSONSchema7[]; + output?: JSONSchema7; + }; + examples?: ScaffolderUsageExample[]; +}; + +/** + * The response shape for a single global function in the `listTemplatingExtensions` call to the `scaffolder-backend` + * + * @public + */ +export type TemplateGlobalFunction = { + description?: string; + schema?: { + arguments?: JSONSchema7[]; + output?: JSONSchema7; + }; + examples?: ScaffolderUsageExample[]; +}; + +/** + * The response shape for a single global value in the `listTemplatingExtensions` call to the `scaffolder-backend` + * + * @public + */ +export type TemplateGlobalValue = { + description?: string; + value: JsonValue; +}; + +/** + * The response shape for the `listTemplatingExtensions` call to the `scaffolder-backend` + * + * @public + */ +export type ListTemplatingExtensionsResponse = { + filters: Record<string, TemplateFilter>; + globals: { + functions: Record<string, TemplateGlobalFunction>; + values: Record<string, TemplateGlobalValue>; + }; +}; + +/** @public */ +export type ScaffolderOutputLink = { + title?: string; + icon?: string; + url?: string; + entityRef?: string; +}; + +/** @public */ +export type ScaffolderOutputText = { + title?: string; + icon?: string; + content?: string; + default?: boolean; +}; + +/** @public */ +export type ScaffolderTaskOutput = { + links?: ScaffolderOutputLink[]; + text?: ScaffolderOutputText[]; +} & { + [key: string]: unknown; +}; + +/** + * The shape of a `LogEvent` message from the `scaffolder-backend` + * + * @public + */ +export type LogEvent = { + type: TaskEventType; + body: { + message: string; + stepId?: string; + status?: ScaffolderTaskStatus; + }; + createdAt: string; + id: number; + taskId: string; +}; + +/** + * The input options to the `scaffold` method of the `ScaffolderClient`. + * + * @public + */ +export interface ScaffolderScaffoldOptions { + templateRef: string; + values: Record<string, JsonValue>; + secrets?: Record<string, string>; +} + +/** + * The response shape of the `scaffold` method of the `ScaffolderClient`. + * + * @public + */ +export interface ScaffolderScaffoldResponse { + taskId: string; +} + +/** + * The arguments for `getIntegrationsList`. + * + * @public + */ +export interface ScaffolderGetIntegrationsListOptions { + allowedHosts: string[]; +} + +/** + * The response shape for `getIntegrationsList`. + * + * @public + */ +export interface ScaffolderGetIntegrationsListResponse { + integrations: { type: string; title: string; host: string }[]; +} + +/** + * The input options to the `streamLogs` method of the `ScaffolderClient`. + * + * @public + */ +export interface ScaffolderStreamLogsOptions { + isTaskRecoverable?: boolean; + taskId: string; + after?: number; +} + +/** @public */ +export interface ScaffolderDryRunOptions { + template: TemplateEntityV1beta3; + values: JsonObject; + secrets?: Record<string, string>; + directoryContents: { path: string; base64Content: string }[]; +} + +/** @public */ +export interface ScaffolderDryRunResponse { + directoryContents: Array<{ + path: string; + base64Content: string; + executable?: boolean; + }>; + log: Array<Pick<LogEvent, 'body'>>; + steps: TaskStep[]; + output: ScaffolderTaskOutput; +} +/** + * An API to interact with the scaffolder backend. + * + * @public + */ +export interface ScaffolderApi { + getTemplateParameterSchema( + templateRef: string, + options?: ScaffolderRequestOptions, + ): Promise<TemplateParameterSchema>; + + /** + * Executes the scaffolding of a component, given a template and its + * parameter values. + * + * @param options - The {@link ScaffolderScaffoldOptions} the scaffolding. + */ + scaffold( + request: ScaffolderScaffoldOptions, + options?: ScaffolderRequestOptions, + ): Promise<ScaffolderScaffoldResponse>; + + getTask( + taskId: string, + options?: ScaffolderRequestOptions, + ): Promise<ScaffolderTask>; + + /** + * Sends a signal to a task broker to cancel the running task by taskId. + * + * @param taskId - the id of the task + */ + cancelTask( + taskId: string, + options?: ScaffolderRequestOptions, + ): Promise<{ status?: ScaffolderTaskStatus }>; + + /** + * Starts the task again from the point where it failed. + * + * @param taskId - the id of the task + */ + retry?( + taskId: string, + options?: ScaffolderRequestOptions, + ): Promise<{ id: string }>; + + listTasks?( + request: { + filterByOwnership: 'owned' | 'all'; + limit?: number; + offset?: number; + }, + options?: ScaffolderRequestOptions, + ): Promise<{ tasks: ScaffolderTask[]; totalTasks?: number }>; + + getIntegrationsList( + options: ScaffolderGetIntegrationsListOptions, + ): Promise<ScaffolderGetIntegrationsListResponse>; + + /** + * Returns a list of all installed actions. + */ + listActions(options?: ScaffolderRequestOptions): Promise<ListActionsResponse>; + + /** + * Returns a structure describing the available templating extensions. + */ + listTemplatingExtensions?( + options?: ScaffolderRequestOptions, + ): Promise<ListTemplatingExtensionsResponse>; + + streamLogs( + request: ScaffolderStreamLogsOptions, + options?: ScaffolderRequestOptions, + ): Observable<LogEvent>; + + dryRun?( + request: ScaffolderDryRunOptions, + options?: ScaffolderRequestOptions, + ): Promise<ScaffolderDryRunResponse>; + + autocomplete?( + request: { + token: string; + provider: string; + resource: string; + context: Record<string, string>; + }, + options?: ScaffolderRequestOptions, + ): Promise<{ results: { title?: string; id: string }[] }>; +} diff --git a/plugins/scaffolder-common/src/index.ts b/plugins/scaffolder-common/src/index.ts index 4d9b5e39c6..ad37973a7b 100644 --- a/plugins/scaffolder-common/src/index.ts +++ b/plugins/scaffolder-common/src/index.ts @@ -30,7 +30,12 @@ export type { TemplatePresentationV1beta3, TemplateEntityV1beta3, TemplateEntityStepV1beta3, + TemplateParameterSchema, TemplateParametersV1beta3, TemplatePermissionsV1beta3, TemplateRecoveryV1beta3, } from './TemplateEntityV1beta3'; + +export * from './ScaffolderClient'; + +export * from './api'; diff --git a/plugins/scaffolder-common/src/permissions.ts b/plugins/scaffolder-common/src/permissions.ts index e758291e75..36c72072e9 100644 --- a/plugins/scaffolder-common/src/permissions.ts +++ b/plugins/scaffolder-common/src/permissions.ts @@ -30,6 +30,13 @@ export const RESOURCE_TYPE_SCAFFOLDER_TEMPLATE = 'scaffolder-template'; */ export const RESOURCE_TYPE_SCAFFOLDER_ACTION = 'scaffolder-action'; +/** + * Permission resource type which corresponds to scaffolder tasks + * + * @alpha + */ +export const RESOURCE_TYPE_SCAFFOLDER_TASK = 'scaffolder-task'; + /** * This permission is used to authorize actions that involve executing * an action from a template. @@ -89,6 +96,7 @@ export const taskReadPermission = createPermission({ attributes: { action: 'read', }, + resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, }); /** @@ -111,6 +119,7 @@ export const taskCreatePermission = createPermission({ export const taskCancelPermission = createPermission({ name: 'scaffolder.task.cancel', attributes: {}, + resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, }); /** diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/apis/Api.client.ts b/plugins/scaffolder-common/src/schema/openapi/generated/apis/Api.client.ts new file mode 100644 index 0000000000..ca5ae10b8f --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/apis/Api.client.ts @@ -0,0 +1,454 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { DiscoveryApi } from '../types/discovery'; +import { FetchApi } from '../types/fetch'; +import crossFetch from 'cross-fetch'; +import { pluginId } from '../pluginId'; +import * as parser from 'uri-template'; +import { Action } from '../models/Action.model'; +import { Autocomplete200Response } from '../models/Autocomplete200Response.model'; +import { AutocompleteRequest } from '../models/AutocompleteRequest.model'; +import { CancelTask200Response } from '../models/CancelTask200Response.model'; +import { DryRun200Response } from '../models/DryRun200Response.model'; +import { DryRunRequest } from '../models/DryRunRequest.model'; +import { ListTasksResponse } from '../models/ListTasksResponse.model'; +import { ListTemplatingExtensionsResponse } from '../models/ListTemplatingExtensionsResponse.model'; +import { RetryRequest } from '../models/RetryRequest.model'; +import { Scaffold201Response } from '../models/Scaffold201Response.model'; +import { ScaffolderScaffoldOptions } from '../models/ScaffolderScaffoldOptions.model'; +import { SerializedTask } from '../models/SerializedTask.model'; +import { SerializedTaskEvent } from '../models/SerializedTaskEvent.model'; +import { TemplateParameterSchema } from '../models/TemplateParameterSchema.model'; + +/** + * Wraps the Response type to convey a type on the json call. + * + * @public + */ +export type TypedResponse<T> = Omit<Response, 'json'> & { + json: () => Promise<T>; +}; + +/** + * Options you can pass into a request for additional information. + * + * @public + */ +export interface RequestOptions { + token?: string; +} +/** + * @public + */ +export type Autocomplete = { + path: { + provider: string; + resource: string; + }; + body: AutocompleteRequest; +}; +/** + * @public + */ +export type CancelTask = { + path: { + taskId: string; + }; +}; +/** + * @public + */ +export type DryRun = { + body: DryRunRequest; +}; +/** + * @public + */ +export type GetTask = { + path: { + taskId: string; + }; +}; +/** + * @public + */ +export type GetTemplateParameterSchema = { + path: { + namespace: string; + kind: string; + name: string; + }; +}; +/** + * @public + */ +export type ListActions = {}; +/** + * @public + */ +export type ListTasks = { + query: { + createdBy?: Array<string>; + limit?: number; + offset?: number; + order?: Array<string>; + status?: Array<string>; + }; +}; +/** + * @public + */ +export type ListTemplatingExtensions = {}; +/** + * @public + */ +export type Retry = { + path: { + taskId: string; + }; + body: RetryRequest; +}; +/** + * @public + */ +export type Scaffold = { + body: ScaffolderScaffoldOptions; +}; +/** + * @public + */ +export type StreamLogsPolling = { + path: { + taskId: string; + }; + query: { + after?: number; + }; +}; + +/** + * @public + */ +export class DefaultApiClient { + private readonly discoveryApi: DiscoveryApi; + private readonly fetchApi: FetchApi; + + constructor(options: { + discoveryApi: { getBaseUrl(pluginId: string): Promise<string> }; + fetchApi?: { fetch: typeof fetch }; + }) { + this.discoveryApi = options.discoveryApi; + this.fetchApi = options.fetchApi || { fetch: crossFetch }; + } + + /** + * Perform an autocomplete for the given provider and resource. + * @param provider - + * @param resource - + * @param autocompleteRequest - + */ + public async autocomplete( + // @ts-ignore + request: Autocomplete, + options?: RequestOptions, + ): Promise<TypedResponse<Autocomplete200Response>> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/v2/autocomplete/{provider}/{resource}`; + + const uri = parser.parse(uriTemplate).expand({ + provider: request.path.provider, + resource: request.path.resource, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'POST', + body: JSON.stringify(request.body), + }); + } + + /** + * Sends a signal to a task broker to cancel the running task by taskId. + * @param taskId - + */ + public async cancelTask( + // @ts-ignore + request: CancelTask, + options?: RequestOptions, + ): Promise<TypedResponse<CancelTask200Response>> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/v2/tasks/{taskId}/cancel`; + + const uri = parser.parse(uriTemplate).expand({ + taskId: request.path.taskId, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'POST', + }); + } + + /** + * Perform a dry-run of a template + * @param dryRunRequest - + */ + public async dryRun( + // @ts-ignore + request: DryRun, + options?: RequestOptions, + ): Promise<TypedResponse<DryRun200Response>> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/v2/dry-run`; + + const uri = parser.parse(uriTemplate).expand({}); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'POST', + body: JSON.stringify(request.body), + }); + } + + /** + * Get a task by ID. + * @param taskId - + */ + public async getTask( + // @ts-ignore + request: GetTask, + options?: RequestOptions, + ): Promise<TypedResponse<SerializedTask>> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/v2/tasks/{taskId}`; + + const uri = parser.parse(uriTemplate).expand({ + taskId: request.path.taskId, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Get template parameter schema. + * @param namespace - + * @param kind - + * @param name - + */ + public async getTemplateParameterSchema( + // @ts-ignore + request: GetTemplateParameterSchema, + options?: RequestOptions, + ): Promise<TypedResponse<TemplateParameterSchema>> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/v2/templates/{namespace}/{kind}/{name}/parameter-schema`; + + const uri = parser.parse(uriTemplate).expand({ + namespace: request.path.namespace, + kind: request.path.kind, + name: request.path.name, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Returns a list of all installed actions. + */ + public async listActions( + // @ts-ignore + request: ListActions, + options?: RequestOptions, + ): Promise<TypedResponse<Array<Action>>> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/v2/actions`; + + const uri = parser.parse(uriTemplate).expand({}); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Returns a list of tasks, filtering by ownership and/or status if given. + * @param createdBy - Created by + * @param limit - Number of records to return in the response. + * @param offset - Number of records to skip in the query page. + * @param order - Order + * @param status - Status + */ + public async listTasks( + // @ts-ignore + request: ListTasks, + options?: RequestOptions, + ): Promise<TypedResponse<ListTasksResponse>> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/v2/tasks{?createdBy*,limit,offset,order*,status*}`; + + const uri = parser.parse(uriTemplate).expand({ + ...request.query, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Returns a structure describing the available templating extensions. + */ + public async listTemplatingExtensions( + // @ts-ignore + request: ListTemplatingExtensions, + options?: RequestOptions, + ): Promise<TypedResponse<ListTemplatingExtensionsResponse>> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/v2/templating-extensions`; + + const uri = parser.parse(uriTemplate).expand({}); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Starts the task again from the point where it failed. + * @param taskId - + * @param retryRequest - + */ + public async retry( + // @ts-ignore + request: Retry, + options?: RequestOptions, + ): Promise<TypedResponse<Scaffold201Response>> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/v2/tasks/{taskId}/retry`; + + const uri = parser.parse(uriTemplate).expand({ + taskId: request.path.taskId, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'POST', + body: JSON.stringify(request.body), + }); + } + + /** + * Executes the scaffolding of a component, given a template and its parameter values. + * @param scaffolderScaffoldOptions - + */ + public async scaffold( + // @ts-ignore + request: Scaffold, + options?: RequestOptions, + ): Promise<TypedResponse<Scaffold201Response>> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/v2/tasks`; + + const uri = parser.parse(uriTemplate).expand({}); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'POST', + body: JSON.stringify(request.body), + }); + } + + /** + * Get events for a task by ID. + * @param taskId - + * @param after - Offset event ID to stream events after. + */ + public async streamLogsPolling( + // @ts-ignore + request: StreamLogsPolling, + options?: RequestOptions, + ): Promise<TypedResponse<Array<SerializedTaskEvent>>> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/v2/tasks/{taskId}/events{?after}`; + + const uri = parser.parse(uriTemplate).expand({ + taskId: request.path.taskId, + ...request.query, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/apis/index.ts b/plugins/scaffolder-common/src/schema/openapi/generated/apis/index.ts new file mode 100644 index 0000000000..fc7c83b736 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/apis/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './Api.client'; diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/index.ts b/plugins/scaffolder-common/src/schema/openapi/generated/index.ts new file mode 100644 index 0000000000..dc3055033d --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './apis'; +export * from './models'; diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/Action.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/Action.model.ts new file mode 100644 index 0000000000..f9c028f3e3 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/Action.model.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { ActionExample } from '../models/ActionExample.model'; +import { ActionSchema } from '../models/ActionSchema.model'; + +/** + * The response shape for a single action in the `listActions` call to the `scaffolder-backend` + * @public + */ +export interface Action { + id: string; + description?: string; + examples?: Array<ActionExample>; + schema?: ActionSchema; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ActionExample.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ActionExample.model.ts new file mode 100644 index 0000000000..50550e2883 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ActionExample.model.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * A single action example + * @public + */ +export interface ActionExample { + description: string; + example: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ActionSchema.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ActionSchema.model.ts new file mode 100644 index 0000000000..35d2014181 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ActionSchema.model.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface ActionSchema { + /** + * A type representing all allowed JSON object values. + */ + input?: { [key: string]: any }; + /** + * A type representing all allowed JSON object values. + */ + output?: { [key: string]: any }; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete200Response.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete200Response.model.ts new file mode 100644 index 0000000000..e3eec0aa20 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete200Response.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { Autocomplete200ResponseResultsInner } from '../models/Autocomplete200ResponseResultsInner.model'; + +/** + * @public + */ +export interface Autocomplete200Response { + results: Array<Autocomplete200ResponseResultsInner>; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete200ResponseResultsInner.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete200ResponseResultsInner.model.ts new file mode 100644 index 0000000000..00ccb41b0f --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete200ResponseResultsInner.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface Autocomplete200ResponseResultsInner { + title?: string; + id: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete400Response.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete400Response.model.ts new file mode 100644 index 0000000000..1456babb96 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/Autocomplete400Response.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface Autocomplete400Response { + message?: string; + name?: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/AutocompleteRequest.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/AutocompleteRequest.model.ts new file mode 100644 index 0000000000..769a929918 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/AutocompleteRequest.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface AutocompleteRequest { + context: { [key: string]: string }; + token: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/CancelTask200Response.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/CancelTask200Response.model.ts new file mode 100644 index 0000000000..f29c509d41 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/CancelTask200Response.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { TaskStatus } from '../models/TaskStatus.model'; + +/** + * @public + */ +export interface CancelTask200Response { + status?: TaskStatus; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200Response.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200Response.model.ts new file mode 100644 index 0000000000..62e3689d5d --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200Response.model.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { DryRun200ResponseAllOfDirectoryContentsInner } from '../models/DryRun200ResponseAllOfDirectoryContentsInner.model'; +import { DryRun200ResponseAllOfStepsInner } from '../models/DryRun200ResponseAllOfStepsInner.model'; +import { DryRunResultLogInner } from '../models/DryRunResultLogInner.model'; + +/** + * @public + */ +export interface DryRun200Response { + log: Array<DryRunResultLogInner>; + directoryContents: Array<DryRun200ResponseAllOfDirectoryContentsInner>; + /** + * A type representing all allowed JSON object values. + */ + output: { [key: string]: any }; + steps: Array<DryRun200ResponseAllOfStepsInner>; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOf.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOf.model.ts new file mode 100644 index 0000000000..5b6c2ff9c7 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOf.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { DryRun200ResponseAllOfDirectoryContentsInner } from '../models/DryRun200ResponseAllOfDirectoryContentsInner.model'; +import { DryRun200ResponseAllOfStepsInner } from '../models/DryRun200ResponseAllOfStepsInner.model'; + +/** + * @public + */ +export interface DryRun200ResponseAllOf { + steps: Array<DryRun200ResponseAllOfStepsInner>; + directoryContents?: Array<DryRun200ResponseAllOfDirectoryContentsInner>; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOfDirectoryContentsInner.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOfDirectoryContentsInner.model.ts new file mode 100644 index 0000000000..2646ca49f9 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOfDirectoryContentsInner.model.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface DryRun200ResponseAllOfDirectoryContentsInner { + path: string; + executable?: boolean; + base64Content: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOfStepsInner.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOfStepsInner.model.ts new file mode 100644 index 0000000000..2a462edefa --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRun200ResponseAllOfStepsInner.model.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface DryRun200ResponseAllOfStepsInner { + [key: string]: any; + + id: string; + name: string; + action: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunRequest.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunRequest.model.ts new file mode 100644 index 0000000000..212b36c26b --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunRequest.model.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { DryRunRequestDirectoryContentsInner } from '../models/DryRunRequestDirectoryContentsInner.model'; + +/** + * @public + */ +export interface DryRunRequest { + template: any; + /** + * A type representing all allowed JSON object values. + */ + values: { [key: string]: any }; + secrets?: { [key: string]: string }; + directoryContents: Array<DryRunRequestDirectoryContentsInner>; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunRequestDirectoryContentsInner.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunRequestDirectoryContentsInner.model.ts new file mode 100644 index 0000000000..ddcae4fd9d --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunRequestDirectoryContentsInner.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface DryRunRequestDirectoryContentsInner { + path?: string; + base64Content?: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResult.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResult.model.ts new file mode 100644 index 0000000000..6e2ad23ef4 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResult.model.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { DryRunResultLogInner } from '../models/DryRunResultLogInner.model'; +import { SerializedFile } from '../models/SerializedFile.model'; + +/** + * @public + */ +export interface DryRunResult { + log: Array<DryRunResultLogInner>; + directoryContents: Array<SerializedFile>; + /** + * A type representing all allowed JSON object values. + */ + output: { [key: string]: any }; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInner.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInner.model.ts new file mode 100644 index 0000000000..4c6b786d82 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInner.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { DryRunResultLogInnerBody } from '../models/DryRunResultLogInnerBody.model'; + +/** + * @public + */ +export interface DryRunResultLogInner { + body: DryRunResultLogInnerBody; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInnerBody.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInnerBody.model.ts new file mode 100644 index 0000000000..e3b19be045 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInnerBody.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { TaskStatus } from '../models/TaskStatus.model'; + +/** + * @public + */ +export interface DryRunResultLogInnerBody { + message: string; + status?: TaskStatus; + stepId?: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInnerBodyAllOf.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInnerBodyAllOf.model.ts new file mode 100644 index 0000000000..e67240ee54 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/DryRunResultLogInnerBodyAllOf.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { TaskStatus } from '../models/TaskStatus.model'; + +/** + * @public + */ +export interface DryRunResultLogInnerBodyAllOf { + message: string; + status?: TaskStatus; + stepId?: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorError.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorError.model.ts new file mode 100644 index 0000000000..e0265e95d7 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorError.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface ErrorError { + name: string; + message: string; + stack?: string; + code?: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorRequest.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorRequest.model.ts new file mode 100644 index 0000000000..3eb5e15740 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorRequest.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface ErrorRequest { + method: string; + url: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorResponse.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorResponse.model.ts new file mode 100644 index 0000000000..edbcc32df7 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ErrorResponse.model.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface ErrorResponse { + statusCode: number; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/JsonPrimitive.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/JsonPrimitive.model.ts new file mode 100644 index 0000000000..6aa6394c14 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/JsonPrimitive.model.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * A type representing all allowed JSON primitive values. + * @public + */ +export type JsonPrimitive = any | boolean | number | string; diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/JsonValue.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/JsonValue.model.ts new file mode 100644 index 0000000000..9fe2ac9de3 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/JsonValue.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { JsonPrimitive } from '../models/JsonPrimitive.model'; + +/** + * A type representing all allowed JSON values. + * @public + */ +export type JsonValue = + | Array<JsonValue> + | JsonPrimitive + | { [key: string]: any }; diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTasksResponse.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTasksResponse.model.ts new file mode 100644 index 0000000000..9347c766a9 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTasksResponse.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { SerializedTask } from '../models/SerializedTask.model'; + +/** + * The response shape for the `listTasks` call to the `scaffolder-backend` + * @public + */ +export interface ListTasksResponse { + tasks: Array<SerializedTask>; + totalTasks?: number; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTemplatingExtensionsResponse.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTemplatingExtensionsResponse.model.ts new file mode 100644 index 0000000000..74b7cd3780 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTemplatingExtensionsResponse.model.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { ListTemplatingExtensionsResponseGlobals } from '../models/ListTemplatingExtensionsResponseGlobals.model'; +import { TemplateFilter } from '../models/TemplateFilter.model'; + +/** + * The response shape for the `listTemplatingExtensions` call to the `scaffolder-backend` + * @public + */ +export interface ListTemplatingExtensionsResponse { + filters: { [key: string]: TemplateFilter }; + globals: ListTemplatingExtensionsResponseGlobals; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTemplatingExtensionsResponseGlobals.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTemplatingExtensionsResponseGlobals.model.ts new file mode 100644 index 0000000000..1f022a0675 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ListTemplatingExtensionsResponseGlobals.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { TemplateGlobalFunction } from '../models/TemplateGlobalFunction.model'; +import { TemplateGlobalValue } from '../models/TemplateGlobalValue.model'; + +/** + * @public + */ +export interface ListTemplatingExtensionsResponseGlobals { + functions: { [key: string]: TemplateGlobalFunction }; + values: { [key: string]: TemplateGlobalValue }; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ModelError.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ModelError.model.ts new file mode 100644 index 0000000000..958fde7d0b --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ModelError.model.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { ErrorError } from '../models/ErrorError.model'; +import { ErrorRequest } from '../models/ErrorRequest.model'; +import { ErrorResponse } from '../models/ErrorResponse.model'; + +/** + * @public + */ +export interface ModelError { + [key: string]: any; + + error: ErrorError; + request?: ErrorRequest; + response: ErrorResponse; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/RetryRequest.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/RetryRequest.model.ts new file mode 100644 index 0000000000..858fa67566 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/RetryRequest.model.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface RetryRequest { + secrets?: { [key: string]: string }; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/Scaffold201Response.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/Scaffold201Response.model.ts new file mode 100644 index 0000000000..1271642e81 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/Scaffold201Response.model.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface Scaffold201Response { + id: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/Scaffold400Response.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/Scaffold400Response.model.ts new file mode 100644 index 0000000000..e3cb7eaa45 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/Scaffold400Response.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { ValidationError } from '../models/ValidationError.model'; + +/** + * @public + */ +export interface Scaffold400Response { + errors: Array<ValidationError>; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ScaffolderScaffoldOptions.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ScaffolderScaffoldOptions.model.ts new file mode 100644 index 0000000000..93f4ae71c0 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ScaffolderScaffoldOptions.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * The input options to the `scaffold` method of the `ScaffolderClient`. + * @public + */ +export interface ScaffolderScaffoldOptions { + templateRef: string; + values: { [key: string]: any }; + secrets?: { [key: string]: string }; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ScaffolderUsageExample.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ScaffolderUsageExample.model.ts new file mode 100644 index 0000000000..3e0f4ef866 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ScaffolderUsageExample.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * A single scaffolder usage example + * @public + */ +export interface ScaffolderUsageExample { + description?: string; + example: string; + notes?: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedFile.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedFile.model.ts new file mode 100644 index 0000000000..65a3c714a7 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedFile.model.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface SerializedFile { + path: string; + content: string; + executable?: boolean; + symlink?: boolean; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedTask.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedTask.model.ts new file mode 100644 index 0000000000..67cbf14279 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedTask.model.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { TaskSecrets } from '../models/TaskSecrets.model'; +import { TaskStatus } from '../models/TaskStatus.model'; + +/** + * SerializedTask + * @public + */ +export interface SerializedTask { + id: string; + spec: any; + status: TaskStatus; + createdAt: string; + lastHeartbeatAt?: string; + createdBy?: string; + secrets?: TaskSecrets; + /** + * A type representing all allowed JSON object values. + */ + state?: { [key: string]: any }; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedTaskEvent.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedTaskEvent.model.ts new file mode 100644 index 0000000000..6e4ddaa296 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/SerializedTaskEvent.model.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { DryRunResultLogInnerBody } from '../models/DryRunResultLogInnerBody.model'; +import { TaskEventType } from '../models/TaskEventType.model'; + +/** + * SerializedTaskEvent + * @public + */ +export interface SerializedTaskEvent { + id: number; + isTaskRecoverable?: boolean; + taskId: string; + body: DryRunResultLogInnerBody; + type: TaskEventType; + createdAt: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskEventType.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskEventType.model.ts new file mode 100644 index 0000000000..d32d77a448 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskEventType.model.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export type TaskEventType = 'cancelled' | 'completion' | 'log' | 'recovered'; diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskSecrets.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskSecrets.model.ts new file mode 100644 index 0000000000..c78c39c1ec --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskSecrets.model.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. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * TaskSecrets + * @public + */ +export interface TaskSecrets { + backstageToken?: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskSecretsAllOf.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskSecretsAllOf.model.ts new file mode 100644 index 0000000000..0b33852cc9 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskSecretsAllOf.model.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface TaskSecretsAllOf { + backstageToken?: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskStatus.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskStatus.model.ts new file mode 100644 index 0000000000..1f209a3656 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TaskStatus.model.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export type TaskStatus = + | 'cancelled' + | 'completed' + | 'failed' + | 'open' + | 'processing' + | 'skipped'; diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateFilter.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateFilter.model.ts new file mode 100644 index 0000000000..7c631978e6 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateFilter.model.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { ScaffolderUsageExample } from '../models/ScaffolderUsageExample.model'; +import { TemplateFilterSchema } from '../models/TemplateFilterSchema.model'; + +/** + * The response shape for a single filter in the `listTemplatingExtensions` call to the `scaffolder-backend` + * @public + */ +export interface TemplateFilter { + description?: string; + schema?: TemplateFilterSchema; + examples?: Array<ScaffolderUsageExample>; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateFilterSchema.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateFilterSchema.model.ts new file mode 100644 index 0000000000..5a95a58ead --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateFilterSchema.model.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface TemplateFilterSchema { + /** + * A type representing all allowed JSON object values. + */ + input?: { [key: string]: any }; + arguments?: Array<{ [key: string]: any }>; + /** + * A type representing all allowed JSON object values. + */ + output?: { [key: string]: any }; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalFunction.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalFunction.model.ts new file mode 100644 index 0000000000..ee19ce723b --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalFunction.model.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { ScaffolderUsageExample } from '../models/ScaffolderUsageExample.model'; +import { TemplateGlobalFunctionSchema } from '../models/TemplateGlobalFunctionSchema.model'; + +/** + * The response shape for a single global function in the `listTemplatingExtensions` call to the `scaffolder-backend` + * @public + */ +export interface TemplateGlobalFunction { + description?: string; + schema?: TemplateGlobalFunctionSchema; + examples?: Array<ScaffolderUsageExample>; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalFunctionSchema.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalFunctionSchema.model.ts new file mode 100644 index 0000000000..b3a39c3b24 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalFunctionSchema.model.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface TemplateGlobalFunctionSchema { + arguments?: Array<{ [key: string]: any }>; + /** + * A type representing all allowed JSON object values. + */ + output?: { [key: string]: any }; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalValue.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalValue.model.ts new file mode 100644 index 0000000000..95d177a05a --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateGlobalValue.model.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * The response shape for a single global value in the `listTemplatingExtensions` call to the `scaffolder-backend` + * @public + */ +export interface TemplateGlobalValue { + description?: string; + value: any | null; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateParameterSchema.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateParameterSchema.model.ts new file mode 100644 index 0000000000..8638ee8114 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateParameterSchema.model.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { TemplateParameterSchemaStepsInner } from '../models/TemplateParameterSchemaStepsInner.model'; + +/** + * The shape of each entry of parameters which gets rendered as a separate step in the wizard input + * @public + */ +export interface TemplateParameterSchema { + [key: string]: any; + + title: string; + description?: string; + steps: Array<TemplateParameterSchemaStepsInner>; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateParameterSchemaStepsInner.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateParameterSchemaStepsInner.model.ts new file mode 100644 index 0000000000..2f7892fd41 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/TemplateParameterSchemaStepsInner.model.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export interface TemplateParameterSchemaStepsInner { + title: string; + description?: string; + schema: any; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationError.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationError.model.ts new file mode 100644 index 0000000000..7e762f616c --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationError.model.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** +import { ValidationErrorArgument } from '../models/ValidationErrorArgument.model'; +import { ValidationErrorPathInner } from '../models/ValidationErrorPathInner.model'; + +/** + * @public + */ +export interface ValidationError { + [key: string]: any; + + path: Array<ValidationErrorPathInner>; + property: string; + message: string; + instance: any; + name: string; + argument: ValidationErrorArgument; + stack: string; +} diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationErrorArgument.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationErrorArgument.model.ts new file mode 100644 index 0000000000..85c9576fd0 --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationErrorArgument.model.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export type ValidationErrorArgument = any | boolean | number | string; diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationErrorPathInner.model.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationErrorPathInner.model.ts new file mode 100644 index 0000000000..4535c735db --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/ValidationErrorPathInner.model.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ****************************************************************** +// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * +// ****************************************************************** + +/** + * @public + */ +export type ValidationErrorPathInner = number | string; diff --git a/plugins/scaffolder-common/src/schema/openapi/generated/models/index.ts b/plugins/scaffolder-common/src/schema/openapi/generated/models/index.ts new file mode 100644 index 0000000000..1bc447fa3d --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/generated/models/index.ts @@ -0,0 +1,65 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from '../models/Action.model'; +export * from '../models/ActionExample.model'; +export * from '../models/ActionSchema.model'; +export * from '../models/Autocomplete200Response.model'; +export * from '../models/Autocomplete200ResponseResultsInner.model'; +export * from '../models/Autocomplete400Response.model'; +export * from '../models/AutocompleteRequest.model'; +export * from '../models/CancelTask200Response.model'; +export * from '../models/DryRun200Response.model'; +export * from '../models/DryRun200ResponseAllOf.model'; +export * from '../models/DryRun200ResponseAllOfDirectoryContentsInner.model'; +export * from '../models/DryRun200ResponseAllOfStepsInner.model'; +export * from '../models/DryRunRequest.model'; +export * from '../models/DryRunRequestDirectoryContentsInner.model'; +export * from '../models/DryRunResult.model'; +export * from '../models/DryRunResultLogInner.model'; +export * from '../models/DryRunResultLogInnerBody.model'; +export * from '../models/DryRunResultLogInnerBodyAllOf.model'; +export * from '../models/ErrorError.model'; +export * from '../models/ErrorRequest.model'; +export * from '../models/ErrorResponse.model'; +export * from '../models/JsonPrimitive.model'; +export * from '../models/JsonValue.model'; +export * from '../models/ListTasksResponse.model'; +export * from '../models/ListTemplatingExtensionsResponse.model'; +export * from '../models/ListTemplatingExtensionsResponseGlobals.model'; +export * from '../models/ModelError.model'; +export * from '../models/RetryRequest.model'; +export * from '../models/Scaffold201Response.model'; +export * from '../models/Scaffold400Response.model'; +export * from '../models/ScaffolderScaffoldOptions.model'; +export * from '../models/ScaffolderUsageExample.model'; +export * from '../models/SerializedFile.model'; +export * from '../models/SerializedTask.model'; +export * from '../models/SerializedTaskEvent.model'; +export * from '../models/TaskEventType.model'; +export * from '../models/TaskSecrets.model'; +export * from '../models/TaskSecretsAllOf.model'; +export * from '../models/TaskStatus.model'; +export * from '../models/TemplateFilter.model'; +export * from '../models/TemplateFilterSchema.model'; +export * from '../models/TemplateGlobalFunction.model'; +export * from '../models/TemplateGlobalFunctionSchema.model'; +export * from '../models/TemplateGlobalValue.model'; +export * from '../models/TemplateParameterSchema.model'; +export * from '../models/TemplateParameterSchemaStepsInner.model'; +export * from '../models/ValidationError.model'; +export * from '../models/ValidationErrorArgument.model'; +export * from '../models/ValidationErrorPathInner.model'; diff --git a/plugins/scaffolder-backend/src/service/index.ts b/plugins/scaffolder-common/src/schema/openapi/generated/pluginId.ts similarity index 87% rename from plugins/scaffolder-backend/src/service/index.ts rename to plugins/scaffolder-common/src/schema/openapi/generated/pluginId.ts index dc5508d47f..b898028be5 100644 --- a/plugins/scaffolder-backend/src/service/index.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/pluginId.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,4 +14,4 @@ * limitations under the License. */ -export * from './conditionExports'; +export const pluginId = 'scaffolder'; diff --git a/plugins/kubernetes-backend/src/alpha.ts b/plugins/scaffolder-common/src/schema/openapi/generated/types/discovery.ts similarity index 78% rename from plugins/kubernetes-backend/src/alpha.ts rename to plugins/scaffolder-common/src/schema/openapi/generated/types/discovery.ts index 90e0a819ac..a7f87d3780 100644 --- a/plugins/kubernetes-backend/src/alpha.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/types/discovery.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -import { kubernetesPlugin as feature } from './plugin'; - -/** @alpha */ -const _feature = feature; -export default _feature; +/** + * This is a copy of the DiscoveryApi, to avoid importing core-plugin-api. + */ +export type DiscoveryApi = { + getBaseUrl(pluginId: string): Promise<string>; +}; diff --git a/packages/frontend-plugin-api/src/extensions/index.ts b/plugins/scaffolder-common/src/schema/openapi/generated/types/fetch.ts similarity index 82% rename from packages/frontend-plugin-api/src/extensions/index.ts rename to plugins/scaffolder-common/src/schema/openapi/generated/types/fetch.ts index f75c7474ac..3de56c028e 100644 --- a/packages/frontend-plugin-api/src/extensions/index.ts +++ b/plugins/scaffolder-common/src/schema/openapi/generated/types/fetch.ts @@ -14,4 +14,9 @@ * limitations under the License. */ -export { createComponentExtension } from './createComponentExtension'; +/** + * This is a copy of FetchApi, to avoid importing core-plugin-api. + */ +export type FetchApi = { + fetch: typeof fetch; +}; diff --git a/plugins/scaffolder-common/src/schema/openapi/index.ts b/plugins/scaffolder-common/src/schema/openapi/index.ts new file mode 100644 index 0000000000..196aad553a --- /dev/null +++ b/plugins/scaffolder-common/src/schema/openapi/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './generated'; diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md index 02f9c91a97..99650afc63 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,129 @@ # @backstage/plugin-scaffolder-node-test-utils +## 0.3.2-next.0 + +### Patch Changes + +- 812485c: Add step info to scaffolder action context to access the step id and name. +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.11.0-next.0 + - @backstage/backend-test-utils@1.7.1-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/types@1.2.1 + +## 0.3.1 + +### Patch Changes + +- dbde180: An internal refactor which adds additional types to experimental checkpoints +- Updated dependencies + - @backstage/backend-test-utils@1.7.0 + - @backstage/plugin-scaffolder-node@0.10.0 + - @backstage/backend-plugin-api@1.4.1 + +## 0.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.10.0-next.2 + +## 0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/backend-test-utils@1.7.0-next.1 + - @backstage/plugin-scaffolder-node@0.9.1-next.1 + +## 0.3.1-next.0 + +### Patch Changes + +- dbde180: An internal refactor which adds additional types to experimental checkpoints +- Updated dependencies + - @backstage/backend-test-utils@1.7.0-next.0 + - @backstage/plugin-scaffolder-node@0.9.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/types@1.2.1 + +## 0.3.0 + +### Minor Changes + +- 3cea7ee: **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. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0 + - @backstage/backend-test-utils@1.6.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/types@1.2.1 + +## 0.3.0-next.2 + +### Minor Changes + +- 3cea7ee: **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. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.9.0-next.2 + - @backstage/backend-test-utils@1.6.0-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/types@1.2.1 + +## 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 + +- b27c48d: Include optional `user` in `createMockActionContext` +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/backend-test-utils@1.5.0 + - @backstage/plugin-scaffolder-node@0.8.2 + - @backstage/types@1.2.1 + +## 0.2.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.2-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/backend-test-utils@1.5.0-next.3 + - @backstage/types@1.2.1 + ## 0.2.2-next.2 ### Patch Changes diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index 9473450114..166228cb61 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-next.2", + "version": "0.3.2-next.0", "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<TActionInput, TActionOutput>>, -) => ActionContext<TActionInput, TActionOutput>; +): ActionContext<TActionInput, TActionOutput>; // (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..6d2fa4f4d5 100644 --- a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts +++ b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts @@ -22,6 +22,7 @@ import { } from '@backstage/backend-test-utils'; import { JsonObject, JsonValue } from '@backstage/types'; import { ActionContext } from '@backstage/plugin-scaffolder-node'; +import { CheckpointContext } from '@backstage/plugin-scaffolder-node/alpha'; import { loggerToWinstonLogger } from './loggerToWinstonLogger'; /** @@ -30,29 +31,33 @@ 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<TActionInput, TActionOutput>>, -): ActionContext<TActionInput, TActionOutput> => { +): ActionContext<TActionInput, TActionOutput> { const credentials = mockCredentials.user(); + const defaultContext = { logger: loggerToWinstonLogger(mockServices.logger.mock()), logStream: new PassThrough(), output: jest.fn(), createTemporaryDirectory: jest.fn(), input: {} as TActionInput, - async checkpoint<T extends JsonValue | void>(opts: { - key: string; - fn: () => Promise<T> | T; - }): Promise<T> { + async checkpoint<T extends JsonValue | void>( + opts: CheckpointContext<T>, + ): Promise<T> { return opts.fn(); }, getInitiatorCredentials: () => Promise.resolve(credentials), task: { id: 'mock-task-id', }, + step: { + id: 'mock-step-id', + name: 'mock step name', + }, }; const createDefaultWorkspace = () => ({ @@ -66,16 +71,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 +81,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 9654b575de..c7b2c8a5c7 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,275 @@ # @backstage/plugin-scaffolder-node +## 0.11.0-next.0 + +### Minor Changes + +- c08cbc4: Move Scaffolder API to OpenAPI + +### Patch Changes + +- 812485c: Add step info to scaffolder action context to access the step id and name. +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.7.0-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.1 + +## 0.10.0 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- dbde180: An internal refactor which adds additional types to experimental checkpoints +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + - @backstage/integration@1.17.1 + - @backstage/plugin-scaffolder-common@1.6.0 + - @backstage/backend-plugin-api@1.4.1 + +## 0.10.0-next.2 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.6.0-next.1 + +## 0.9.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-scaffolder-common@1.5.12-next.0 + +## 0.9.1-next.0 + +### Patch Changes + +- dbde180: An internal refactor which adds additional types to experimental checkpoints +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 0.9.0 + +### Minor Changes + +- 5863b04: **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. + +### Patch Changes + +- e89d7b6: Use `LoggerService` instead of `Logger`. This is a non-breaking change, as the `LoggerService` is a subset of the `Logger` interface. +- 9c8ff0c: Update pull request creation filter to include .gitignore files in the created pull request +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @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.9.0-next.2 + +### Minor Changes + +- 5863b04: **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. + +### Patch Changes + +- 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.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 + +- 16e2e9c: trim leading and trailing slashes from parseRepoUrl query parameters +- 72d019d: Removed various typos +- ec42f8e: Generating new tokens on each Scaffolder Task Retry +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 0.8.2-next.3 + +### Patch Changes + +- 16e2e9c: trim leading and trailing slashes from parseRepoUrl query parameters +- ec42f8e: Generating new tokens on each Scaffolder Task Retry +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + ## 0.8.2-next.2 ### Patch Changes diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 011b09c91f..453abaaa82 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-next.2", + "version": "0.11.0-next.0", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library", @@ -58,6 +58,7 @@ "@backstage/catalog-model": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", + "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/types": "workspace:^", "@isomorphic-git/pgp-plugin": "^0.0.7", @@ -66,6 +67,7 @@ "globby": "^11.0.0", "isomorphic-git": "^1.23.0", "jsonschema": "^1.5.0", + "lodash": "^4.17.21", "p-limit": "^3.1.0", "tar": "^6.1.12", "winston": "^3.2.1", @@ -76,6 +78,7 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@backstage/config": "workspace:^" + "@backstage/config": "workspace:^", + "@types/lodash": "^4.14.151" } } diff --git a/plugins/scaffolder-node/report-alpha.api.md b/plugins/scaffolder-node/report-alpha.api.md index a96f312d2d..09541fbc56 100644 --- a/plugins/scaffolder-node/report-alpha.api.md +++ b/plugins/scaffolder-node/report-alpha.api.md @@ -27,6 +27,31 @@ export type AutocompleteHandler = ({ }[]; }>; +// @alpha +export type CheckpointContext<T extends JsonValue | void = JsonValue> = { + key: string; + fn: () => Promise<T> | T; +}; + +// @alpha +export type CheckpointState = { + [key: string]: CheckpointStateValue; +}; + +// @alpha +export type CheckpointStateValue<T extends JsonValue = JsonValue> = + | { + status: 'failed'; + reason: string; + } + | { + status: 'success'; + value: T; + }; + +// @alpha +export type CheckpointStatus = 'failed' | 'success'; + // @alpha (undocumented) export type CreatedTemplateFilter< TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], @@ -187,6 +212,11 @@ export type TemplateGlobalFunctionExample = { notes?: string; }; +// @alpha +export type UpdateTaskCheckpointOptions = { + key: string; +} & CheckpointStateValue; + // @alpha export interface WorkspaceProvider { // (undocumented) diff --git a/plugins/scaffolder-node/report.api.md b/plugins/scaffolder-node/report.api.md index 6dabcbf346..c97344867a 100644 --- a/plugins/scaffolder-node/report.api.md +++ b/plugins/scaffolder-node/report.api.md @@ -4,18 +4,20 @@ ```ts import { BackstageCredentials } from '@backstage/backend-plugin-api'; +import { CheckpointContext } from '@backstage/plugin-scaffolder-node/alpha'; 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 { PermissionCriteria } from '@backstage/plugin-permission-common'; import { Schema } from 'jsonschema'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmIntegrations } from '@backstage/integration'; import { SpawnOptionsWithoutStdio } from 'child_process'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TemplateInfo } from '@backstage/plugin-scaffolder-common'; +import { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node/alpha'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { UserEntity } from '@backstage/catalog-model'; import { Writable } from 'stream'; @@ -25,63 +27,37 @@ 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<T extends JsonValue | void>(opts: { - key: string; - fn: () => Promise<T> | T; - }): Promise<T>; - output( - name: keyof TActionOutput, - value: TActionOutput[keyof TActionOutput], - ): void; - createTemporaryDirectory(): Promise<string>; - getInitiatorCredentials(): Promise<BackstageCredentials>; - 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<T extends JsonValue | void>(opts: { - key: string; - fn: () => Promise<T> | T; - }): Promise<T>; - output( - name: keyof TActionOutput, - value: TActionOutput[keyof TActionOutput], - ): void; - createTemporaryDirectory(): Promise<string>; - getInitiatorCredentials(): Promise<BackstageCredentials>; - 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<T extends JsonValue | void>( + opts: CheckpointContext<T>, + ): Promise<T>; + output( + name: keyof TActionOutput, + value: TActionOutput[keyof TActionOutput], + ): void; + createTemporaryDirectory(): Promise<string>; + getInitiatorCredentials(): Promise<BackstageCredentials>; + task: { + id: string; + }; + templateInfo?: TemplateInfo; + isDryRun?: boolean; + user?: { + entity?: UserEntity; + ref?: string; + }; + signal?: AbortSignal; + each?: JsonObject; + step?: { + id?: string; + name?: string; + }; +}; // @public (undocumented) export function addFiles(options: { @@ -95,7 +71,7 @@ export function addFiles(options: { | { token: string; }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; }): Promise<void>; // @public (undocumented) @@ -110,7 +86,7 @@ export function cloneRepo(options: { | { token: string; }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; ref?: string | undefined; depth?: number | undefined; noCheckout?: boolean | undefined; @@ -127,7 +103,7 @@ export function commitAndPushBranch(options: { | { token: string; }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; commitMessage: string; gitAuthorInfo?: { name?: string; @@ -152,7 +128,7 @@ export function commitAndPushRepo(input: { | { token: string; }; - logger: Logger; + logger: LoggerService; commitMessage: string; gitAuthorInfo?: { name?: string; @@ -177,72 +153,70 @@ export function createBranch(options: { | { token: string; }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; }): Promise<void>; -// @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<TActionInput, TActionOutput, 'v1'>; - -// @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<TInputSchema>, - TActionOutput extends JsonObject = z.infer<TOutputSchema>, ->( - action: TemplateActionOptions< - TActionInput, - TActionOutput, - TInputSchema, - TOutputSchema, - 'v1' - >, -): TemplateAction<TActionInput, TActionOutput, 'v1'>; - // @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<ReturnType<TInputSchema[key]>>; - }, - { - [key in keyof TOutputSchema]: z.infer<ReturnType<TOutputSchema[key]>>; - }, + TInputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + ? { + [key in keyof TInputSchema]: z.infer<ReturnType<TInputSchema[key]>>; + } + : TInputSchema extends (zImpl: typeof z) => z.ZodType + ? z.infer<ReturnType<TInputSchema>> + : never, + TOutputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + ? { + [key in keyof TOutputSchema]: z.infer<ReturnType<TOutputSchema[key]>>; + } + : TOutputSchema extends (zImpl: typeof z) => z.ZodType + ? z.infer<ReturnType<TOutputSchema>> + : never, TInputSchema, TOutputSchema, 'v2' >, ): TemplateAction< - FlattenOptionalProperties<{ - [key in keyof TInputSchema]: z.output<ReturnType<TInputSchema[key]>>; - }>, - FlattenOptionalProperties<{ - [key in keyof TOutputSchema]: z.output<ReturnType<TOutputSchema[key]>>; - }>, + FlattenOptionalProperties< + TInputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + ? { + [key in keyof TInputSchema]: z.output<ReturnType<TInputSchema[key]>>; + } + : TInputSchema extends (zImpl: typeof z) => z.ZodType + ? z.output<ReturnType<TInputSchema>> + : never + >, + FlattenOptionalProperties< + TOutputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + ? { + [key in keyof TOutputSchema]: z.output< + ReturnType<TOutputSchema[key]> + >; + } + : TOutputSchema extends (zImpl: typeof z) => z.ZodType + ? z.output<ReturnType<TOutputSchema>> + : never + >, 'v2' >; @@ -304,7 +278,7 @@ export function initRepoAndPush(input: { | { token: string; }; - logger: Logger; + logger: LoggerService; defaultBranch?: string; commitMessage?: string; gitAuthorInfo?: { @@ -316,6 +290,9 @@ export function initRepoAndPush(input: { commitHash: string; }>; +// @public +export function isNotGitDirectoryOrContents(path: string): boolean; + // @public (undocumented) export const parseRepoUrl: ( repoUrl: string, @@ -367,7 +344,11 @@ export type SerializedTaskEvent = { id: number; isTaskRecoverable?: boolean; taskId: string; - body: JsonObject; + body: { + message: string; + stepId?: string; + status?: TaskStatus; + } & JsonObject; type: TaskEventType; createdAt: string; }; @@ -402,6 +383,7 @@ export interface TaskBroker { order: 'asc' | 'desc'; field: string; }[]; + permissionFilters?: PermissionCriteria<TaskFilters>; }): Promise<{ tasks: SerializedTask[]; totalTasks?: number; @@ -414,7 +396,7 @@ export interface TaskBroker { // (undocumented) recoverTasks?(): Promise<void>; // (undocumented) - retry?(taskId: string): Promise<void>; + retry?(options: { secrets?: TaskSecrets; taskId: string }): Promise<void>; // (undocumented) vacuumTasks(options: { timeoutS: number }): Promise<void>; } @@ -475,24 +457,31 @@ export interface TaskContext { // (undocumented) taskId?: string; // (undocumented) - updateCheckpoint?( - options: - | { - key: string; - status: 'success'; - value: JsonValue; - } - | { - key: string; - status: 'failed'; - reason: string; - }, - ): Promise<void>; + updateCheckpoint?(options: UpdateTaskCheckpointOptions): Promise<void>; } // @public export type TaskEventType = 'completion' | 'log' | 'cancelled' | 'recovered'; +// @public +export type TaskFilter = { + key: string; + values?: string[]; +}; + +// @public +export type TaskFilters = + | { + anyOf: TaskFilter[]; + } + | { + allOf: TaskFilter[]; + } + | { + not: TaskFilter; + } + | TaskFilter; + // @public export type TaskSecrets = Record<string, string> & { backstageToken?: string; @@ -504,13 +493,14 @@ export type TaskStatus = | 'completed' | 'failed' | 'open' - | 'processing'; + | 'processing' + | 'skipped'; // @public (undocumented) 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 +523,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<T extends { [key in string]: unknown }> = 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<TActionInput, TActionOutput, 'v1'>; -/** - * @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<TInputSchema>, - TActionOutput extends JsonObject = z.infer<TOutputSchema>, ->( - action: TemplateActionOptions< - TActionInput, - TActionOutput, - TInputSchema, - TOutputSchema, - 'v1' - >, -): TemplateAction<TActionInput, TActionOutput, 'v1'>; /** * 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<ReturnType<TInputSchema[key]>>; - }, - { - [key in keyof TOutputSchema]: z.infer<ReturnType<TOutputSchema[key]>>; - }, + TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + ? { + [key in keyof TInputSchema]: z.infer<ReturnType<TInputSchema[key]>>; + } + : TInputSchema extends (zImpl: typeof z) => z.ZodType + ? z.infer<ReturnType<TInputSchema>> + : never, + TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + ? { + [key in keyof TOutputSchema]: z.infer<ReturnType<TOutputSchema[key]>>; + } + : TOutputSchema extends (zImpl: typeof z) => z.ZodType + ? z.infer<ReturnType<TOutputSchema>> + : never, TInputSchema, TOutputSchema, 'v2' >, ): TemplateAction< - FlattenOptionalProperties<{ - [key in keyof TInputSchema]: z.output<ReturnType<TInputSchema[key]>>; - }>, - FlattenOptionalProperties<{ - [key in keyof TOutputSchema]: z.output<ReturnType<TOutputSchema[key]>>; - }>, + FlattenOptionalProperties< + TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + ? { + [key in keyof TInputSchema]: z.output<ReturnType<TInputSchema[key]>>; + } + : TInputSchema extends (zImpl: typeof z) => z.ZodType + ? z.output<ReturnType<TInputSchema>> + : never + >, + FlattenOptionalProperties< + TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + ? { + [key in keyof TOutputSchema]: z.output< + ReturnType<TOutputSchema[key]> + >; + } + : TOutputSchema extends (zImpl: typeof z) => z.ZodType + ? z.output<ReturnType<TOutputSchema>> + : 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<ReturnType<TInputSchema[key]>>; }> - : 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<ReturnType<TOutputSchema[key]>>; }> - : 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<TActionInput, TActionOutput, 'v2'> { const { inputSchema, outputSchema } = parseSchemas( action as TemplateActionOptions<any, any, any>, ); 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<void> { 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<void> { 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..04b1e77a7a 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'; @@ -25,6 +23,8 @@ import { BackstageCredentials, LoggerService, } from '@backstage/backend-plugin-api'; +import { CheckpointContext } from '@backstage/plugin-scaffolder-node/alpha'; + /** * ActionContext is passed into scaffolder actions. * @public @@ -32,143 +32,88 @@ 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<T extends JsonValue | void>(opts: { - key: string; - fn: () => Promise<T> | T; - }): Promise<T>; - 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<string>; + _TSchemaType extends 'v2' = 'v2', +> = { + logger: LoggerService; + secrets?: TaskSecrets; + workspacePath: string; + input: TActionInput; + checkpoint<T extends JsonValue | void>( + opts: CheckpointContext<T>, + ): Promise<T>; + 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<string>; - /** - * Get the credentials for the current request - */ - getInitiatorCredentials(): Promise<BackstageCredentials>; + /** + * Get the credentials for the current request + */ + getInitiatorCredentials(): Promise<BackstageCredentials>; - /** - * 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<T extends JsonValue | void>(opts: { - key: string; - fn: () => Promise<T> | T; - }): Promise<T>; - output( - name: keyof TActionOutput, - value: TActionOutput[keyof TActionOutput], - ): void; + /** + * Optional value of each invocation + */ + each?: JsonObject; - /** - * Creates a temporary directory for use by the action, which is then cleaned up automatically. - */ - createTemporaryDirectory(): Promise<string>; - - /** - * Get the credentials for the current request - */ - getInitiatorCredentials(): Promise<BackstageCredentials>; - - /** - * 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; - }; + /** + * Step information + */ + step?: { + /** + * The id of step which triggered the action + */ + id?: string; + /** + * The name of the step which triggered the action + */ + name?: string; + }; +}; /** @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 new file mode 100644 index 0000000000..792e306d00 --- /dev/null +++ b/plugins/scaffolder-node/src/actions/util.test.ts @@ -0,0 +1,273 @@ +/* + * 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 { ScmIntegrationRegistry } from '@backstage/integration'; +import { parseRepoUrl } from './util'; +import { mapValues } from 'lodash'; + +const queryString = ( + params: Partial< + Record<'owner' | 'organization' | 'workspace' | 'project' | 'repo', string> + > = {}, +): string => { + const pEntries = Object.entries(params); + if (pEntries.length) { + return `?${pEntries + .map(([k, v]) => `${k}=${encodeURIComponent(v)}`) + .join('&')}`; + } + return ''; +}; + +describe('scaffolder action utils', () => { + describe('parseRepoUrl', () => { + const byHost = jest.fn(); + const integrations = { + byHost, + } as unknown as ScmIntegrationRegistry; + + describe('rejects url when', () => { + it('empty', () => + expect(() => parseRepoUrl('', integrations)).toThrow( + /Invalid repo URL passed/, + )); + it('blank', () => + expect(() => parseRepoUrl(' ', integrations)).toThrow( + /Invalid repo URL passed/, + )); + }); + it('requires that host match an integration type', () => { + byHost.mockClear(); + expect(() => parseRepoUrl('foo', integrations)).toThrow( + /No matching integration configuration for host/, + ); + }); + describe('bitbucket', () => { + beforeEach(() => byHost.mockReturnValue({ type: 'bitbucket' })); + describe('cloud', () => { + const [host, workspace, project, repo] = [ + 'www.bitbucket.org', + 'foo', + 'bar', + 'baz', + ]; + it('requires workspace', () => + expect(() => + parseRepoUrl( + `${host}${queryString({ project, repo })}`, + integrations, + ), + ).toThrow(/missing workspace/)); + it('requires project', () => + expect(() => + parseRepoUrl( + `${host}${queryString({ workspace, repo })}`, + integrations, + ), + ).toThrow(/missing project/)); + it('requires repo', () => + expect(() => + parseRepoUrl( + `${host}${queryString({ workspace, project })}`, + integrations, + ), + ).toThrow(/missing repo/)); + it('happy path', () => + expect( + parseRepoUrl( + `${host}${queryString({ workspace, project, repo })}`, + integrations, + ), + ).toMatchObject({ + host, + workspace, + project, + repo, + })); + }); + describe('other', () => { + const [host, project, repo] = ['bitbucket.other', 'foo', 'bar']; + it('requires project', () => + expect(() => + parseRepoUrl(`${host}${queryString({ repo })}`, integrations), + ).toThrow(/missing project/)); + it('requires repo', () => + expect(() => + parseRepoUrl(`${host}${queryString({ project })}`, integrations), + ).toThrow(/missing repo/)); + it('happy path', () => + expect( + parseRepoUrl( + `${host}${queryString({ project, repo })}`, + integrations, + ), + ).toMatchObject({ + host, + project, + repo, + })); + }); + }); + describe('azure', () => { + beforeEach(() => byHost.mockReturnValue({ type: 'azure' })); + const [host, project, repo] = ['az.ure', 'foo', 'bar']; + it('requires project', () => + expect(() => + parseRepoUrl(`${host}${queryString({ repo })}`, integrations), + ).toThrow(/missing project/)); + it('requires repo', () => + expect(() => + parseRepoUrl(`${host}${queryString({ project })}`, integrations), + ).toThrow(/missing repo/)); + it('happy path', () => + expect( + parseRepoUrl( + `${host}${queryString({ project, repo })}`, + integrations, + ), + ).toMatchObject({ + host, + project, + repo, + })); + }); + describe('gitlab', () => { + beforeEach(() => byHost.mockReturnValue({ type: 'gitlab' })); + const [host, owner, repo, project] = ['gitl.ab', 'foo', 'bar', '123456']; + it('requires owner', () => + expect(() => + parseRepoUrl(`${host}${queryString({ repo })}`, integrations), + ).toThrow(/missing owner/)); + it('requires repo', () => + expect(() => + parseRepoUrl(`${host}${queryString({ owner })}`, integrations), + ).toThrow(/missing repo/)); + it('unless project specified', () => + expect( + parseRepoUrl(`${host}${queryString({ project })}`, integrations), + ).toMatchObject({ host, project })); + it('happy path', () => + expect( + parseRepoUrl(`${host}${queryString({ owner, repo })}`, integrations), + ).toMatchObject({ + host, + owner, + repo, + })); + }); + describe('gitea', () => { + beforeEach(() => byHost.mockReturnValue({ type: 'gitea' })); + const [host, repo] = ['git.ea', 'foo']; + it('requires repo', () => + expect(() => parseRepoUrl(host, integrations)).toThrow(/missing repo/)); + it('happy path', () => + expect( + parseRepoUrl(`${host}${queryString({ repo })}`, integrations), + ).toMatchObject({ host, repo })); + }); + describe('gerrit', () => { + beforeEach(() => byHost.mockReturnValue({ type: 'gerrit' })); + const [host, repo] = ['gerr.it', 'foo']; + it('requires repo', () => + expect(() => parseRepoUrl(host, integrations)).toThrow(/missing repo/)); + it('happy path', () => + expect( + parseRepoUrl(`${host}${queryString({ repo })}`, integrations), + ).toMatchObject({ + host, + repo, + })); + }); + describe('generic type', () => { + beforeEach(() => byHost.mockReturnValue({ type: 'generic' })); + const [host, owner, repo] = ['oth.er', 'foo', 'bar']; + it('requires owner', () => + expect(() => + parseRepoUrl(`${host}${queryString({ repo })}`, integrations), + ).toThrow(/missing owner/)); + it('requires repo', () => + expect(() => + parseRepoUrl(`${host}${queryString({ owner })}`, integrations), + ).toThrow(/missing repo/)); + it('happy path', () => + expect( + parseRepoUrl(`${host}${queryString({ owner, repo })}`, integrations), + ).toMatchObject({ + host, + owner, + repo, + })); + }); + describe('facilitates naive URL construction', () => { + beforeEach(() => byHost.mockReturnValue({ type: 'irrelevant' })); + it('decodes encoded params', () => { + const [host, owner, repo] = ['with_the_most', 'foo/bar/baz', 'blah']; + expect( + parseRepoUrl(`${host}${queryString({ owner, repo })}`, integrations), + ).toMatchObject({ host, owner, repo }); + }); + it('trims leading and trailing / from params', () => { + const [host, owner, organization, workspace, project, repo] = [ + 'anywhere', + 'anyone', + 'anything', + 'anyway', + 'anyhow', + 'any', + ]; + const junkedUp = mapValues( + { owner, organization, workspace, project, repo }, + v => `//${v}//`, + ); + return expect( + parseRepoUrl(`${host}${queryString(junkedUp)}`, integrations), + ).toMatchObject({ + host, + owner, + organization, + workspace, + project, + repo, + }); + }); + }); + }); + + 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 f1775bb318..34cc74c811 100644 --- a/plugins/scaffolder-node/src/actions/util.ts +++ b/plugins/scaffolder-node/src/actions/util.ts @@ -22,6 +22,7 @@ import { TemplateActionOptions } from './createTemplateAction'; import zodToJsonSchema from 'zod-to-json-schema'; import { z } from 'zod'; import { Schema } from 'jsonschema'; +import { trim } from 'lodash'; /** * @public @@ -67,11 +68,6 @@ export const parseRepoUrl = ( ); } const host = parsed.host; - const owner = parsed.searchParams.get('owner') ?? undefined; - const organization = parsed.searchParams.get('organization') ?? undefined; - const workspace = parsed.searchParams.get('workspace') ?? undefined; - const project = parsed.searchParams.get('project') ?? undefined; - const type = integrations.byHost(host)?.type; if (!type) { @@ -79,8 +75,14 @@ export const parseRepoUrl = ( `No matching integration configuration for host ${host}, please check your integrations config`, ); } - - const repo: string = parsed.searchParams.get('repo')!; + const { owner, organization, workspace, project, repo } = Object.fromEntries( + ['owner', 'organization', 'workspace', 'project', 'repo'].map(param => [ + param, + parsed.searchParams.has(param) + ? trim(parsed.searchParams.get(param)!, '/') + : undefined, + ]), + ); switch (type) { case 'bitbucket': { if (host === 'www.bitbucket.org') { @@ -113,8 +115,7 @@ export const parseRepoUrl = ( break; } } - - return { host, owner, repo, organization, workspace, project }; + return { host, owner, repo: repo!, organization, workspace, project }; }; function checkRequiredParams(repoUrl: URL, ...params: string[]) { @@ -129,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 ( @@ -143,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<any, any, any>, ): { 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)]), @@ -168,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( @@ -180,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-node/src/alpha/checkpoints/index.ts b/plugins/scaffolder-node/src/alpha/checkpoints/index.ts new file mode 100644 index 0000000000..16c936b235 --- /dev/null +++ b/plugins/scaffolder-node/src/alpha/checkpoints/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './types'; diff --git a/plugins/scaffolder-node/src/alpha/checkpoints/types.ts b/plugins/scaffolder-node/src/alpha/checkpoints/types.ts new file mode 100644 index 0000000000..5bd1eb5cd3 --- /dev/null +++ b/plugins/scaffolder-node/src/alpha/checkpoints/types.ts @@ -0,0 +1,57 @@ +/* + * 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 { JsonValue } from '@backstage/types'; + +/** + * The status of a checkpoint, indicating whether it succeeded or failed. + * + * @alpha + */ +export type CheckpointStatus = 'failed' | 'success'; + +/** + * Represents the union of all possible checkpoint state values. + * + * @alpha + */ +export type CheckpointStateValue<T extends JsonValue = JsonValue> = + | { status: 'failed'; reason: string } + | { status: 'success'; value: T }; + +/** + * A map of checkpoint keys to their states. + * + * @alpha + */ +export type CheckpointState = { + [key: string]: CheckpointStateValue; +}; + +/** + * Context for checkpoint function invocation. + * + * @alpha + */ +export type CheckpointContext<T extends JsonValue | void = JsonValue> = { + /** + * Unique key for the checkpoint + */ + key: string; + /** + * Function to execute for the checkpoint + */ + fn: () => Promise<T> | T; +}; diff --git a/plugins/scaffolder-node/src/alpha/index.ts b/plugins/scaffolder-node/src/alpha/index.ts index 9df968a52d..30f95f116a 100644 --- a/plugins/scaffolder-node/src/alpha/index.ts +++ b/plugins/scaffolder-node/src/alpha/index.ts @@ -28,6 +28,7 @@ export * from '../tasks/alpha'; export * from './filters'; export * from './globals'; export * from './types'; +export * from './checkpoints'; /** * Extension point for managing scaffolder actions. diff --git a/plugins/scaffolder-node/src/tasks/alpha.ts b/plugins/scaffolder-node/src/tasks/alpha.ts index fb6b09026c..4354a798cf 100644 --- a/plugins/scaffolder-node/src/tasks/alpha.ts +++ b/plugins/scaffolder-node/src/tasks/alpha.ts @@ -1,3 +1,5 @@ +import { CheckpointStateValue } from '../alpha'; + /* * Copyright 2024 The Backstage Authors * @@ -14,3 +16,12 @@ * limitations under the License. */ export * from './serializer'; + +/** + * Options for updating a checkpoint in a task. + * + * @alpha + */ +export type UpdateTaskCheckpointOptions = { + key: string; +} & CheckpointStateValue; diff --git a/plugins/scaffolder-node/src/tasks/index.ts b/plugins/scaffolder-node/src/tasks/index.ts index 930de95237..7e93e84cc0 100644 --- a/plugins/scaffolder-node/src/tasks/index.ts +++ b/plugins/scaffolder-node/src/tasks/index.ts @@ -18,6 +18,8 @@ export type { TaskSecrets, SerializedTask, SerializedTaskEvent, + TaskFilter, + TaskFilters, TaskBroker, TaskBrokerDispatchOptions, TaskBrokerDispatchResult, diff --git a/plugins/scaffolder-node/src/tasks/types.ts b/plugins/scaffolder-node/src/tasks/types.ts index fdfeb49a0b..aaac468fce 100644 --- a/plugins/scaffolder-node/src/tasks/types.ts +++ b/plugins/scaffolder-node/src/tasks/types.ts @@ -15,8 +15,10 @@ */ import { BackstageCredentials } from '@backstage/backend-plugin-api'; +import { PermissionCriteria } from '@backstage/plugin-permission-common'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; -import { JsonObject, JsonValue, Observable } from '@backstage/types'; +import { JsonObject, Observable } from '@backstage/types'; +import { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node/alpha'; /** * TaskSecrets @@ -37,7 +39,8 @@ export type TaskStatus = | 'completed' | 'failed' | 'open' - | 'processing'; + | 'processing' + | 'skipped'; /** * The state of a completed task. @@ -78,7 +81,11 @@ export type SerializedTaskEvent = { id: number; isTaskRecoverable?: boolean; taskId: string; - body: JsonObject; + body: { + message: string; + stepId?: string; + status?: TaskStatus; + } & JsonObject; type: TaskEventType; createdAt: string; }; @@ -104,6 +111,25 @@ export type TaskBrokerDispatchOptions = { createdBy?: string; }; +/** + * TaskFilter + * @public + */ +export type TaskFilter = { + key: string; + values?: string[]; +}; + +/** + * TaskFilters + * @public + */ +export type TaskFilters = + | { anyOf: TaskFilter[] } + | { allOf: TaskFilter[] } + | { not: TaskFilter } + | TaskFilter; + /** * Task * @@ -129,19 +155,7 @@ export interface TaskContext { | undefined >; - updateCheckpoint?( - options: - | { - key: string; - status: 'success'; - value: JsonValue; - } - | { - key: string; - status: 'failed'; - reason: string; - }, - ): Promise<void>; + updateCheckpoint?(options: UpdateTaskCheckpointOptions): Promise<void>; serializeWorkspace?(options: { path: string }): Promise<void>; @@ -165,7 +179,7 @@ export interface TaskContext { export interface TaskBroker { cancel?(taskId: string): Promise<void>; - retry?(taskId: string): Promise<void>; + retry?(options: { secrets?: TaskSecrets; taskId: string }): Promise<void>; claim(): Promise<TaskContext>; @@ -194,6 +208,7 @@ export interface TaskBroker { offset?: number; }; order?: { order: 'asc' | 'desc'; field: string }[]; + permissionFilters?: PermissionCriteria<TaskFilters>; }): Promise<{ tasks: SerializedTask[]; totalTasks?: number }>; /** diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index 97925ee2a1..a30f8d371d 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,220 @@ # @backstage/plugin-scaffolder-react +## 1.19.0-next.1 + +### Minor Changes + +- 4f99e10: **DEPRECATION**: The following types have been deprecated from this package and moved into `@backstage/plugin-scaffolder-common` and should be imported from there instead. + + `Action`, `ListActionsResponse`, `LogEvent`, `ScaffolderApi`, `ScaffolderDryRunOptions`, `ScaffolderDryRunResponse`, `ScaffolderGetIntegrationsListOptions`, `ScaffolderGetIntegrationsListResponse`, + `ScaffolderOutputLink`, `ScaffolderOutputText`, `ScaffolderScaffoldOptions`, `ScaffolderScaffoldResponse`, `ScaffolderStreamLogsOptions`, `ScaffolderTask`, `ScaffolderTaskOutput`, `ScaffolderTaskStatus`, + `ScaffolderUsageExample`, `TemplateFilter`, `TemplateGlobalFunction`, `TemplateGlobalValue`, `TemplateParameterSchema`. + +- c08cbc4: Move Scaffolder API to OpenAPI + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- c4b7c50: Export `FormField` type from `/alpha` in `-react` package, and internal refactor. +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.7.0-next.0 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.36 + +## 1.18.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## 1.18.0 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- 94c11a5: Scroll to the top of the page when navigating between steps in template forms. +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/plugin-scaffolder-common@1.6.0 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-permission-react@0.4.36 + +## 1.18.0-next.2 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- 94c11a5: Scroll to the top of the page when navigating between steps in template forms. +- Updated dependencies + - @backstage/theme@0.6.7-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/plugin-scaffolder-common@1.6.0-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + +## 1.17.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-scaffolder-common@1.5.12-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 1.17.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 1.17.0 + +### Minor Changes + +- 6c972fe: Added information about the `entityRef` and `taskId` to the analytics events whenever is possible. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 1.16.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.7 + - @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.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 + +- 4235e87: add templating extensions page + +### Patch Changes + +- 36ae651: Fixing a bug where the name for `templatingExtensions` was incorrectly set to `templateExtensions` +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 1.16.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + ## 1.16.0-next.2 ### Patch Changes diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 514da4fe93..c8706c790a 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-next.2", + "version": "1.19.0-next.1", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library", diff --git a/plugins/scaffolder-react/report-alpha.api.md b/plugins/scaffolder-react/report-alpha.api.md index 2014bff9be..6f81cd8a32 100644 --- a/plugins/scaffolder-react/report-alpha.api.md +++ b/plugins/scaffolder-react/report-alpha.api.md @@ -5,6 +5,7 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyApiRef } from '@backstage/core-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ApiHolder } from '@backstage/core-plugin-api'; import { ApiRef } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; @@ -12,13 +13,14 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { CustomFieldValidator } from '@backstage/plugin-scaffolder-react'; import { Dispatch } from 'react'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react'; import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; import { FieldSchema } from '@backstage/plugin-scaffolder-react'; import { FieldValidation } from '@rjsf/utils'; -import { FormField } from '@internal/scaffolder'; import { FormProps } from '@backstage/plugin-scaffolder-react'; import { IconComponent } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; @@ -134,11 +136,10 @@ export const Form: ( // @alpha export const FormDecoratorBlueprint: ExtensionBlueprint<{ kind: 'scaffolder-form-decorator'; - name: undefined; params: { decorator: ScaffolderFormDecorator; }; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< ScaffolderFormDecorator, 'scaffolder.form-decorator-loader', {} @@ -155,14 +156,19 @@ export const FormDecoratorBlueprint: ExtensionBlueprint<{ }; }>; +// @alpha (undocumented) +export interface FormField { + // (undocumented) + readonly $$type: '@backstage/scaffolder/FormField'; +} + // @alpha export const FormFieldBlueprint: ExtensionBlueprint<{ kind: 'scaffolder-form-field'; - name: undefined; params: { field: () => Promise<FormField>; }; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< () => Promise<FormField>, 'scaffolder.form-field-loader', {} @@ -202,7 +208,7 @@ export type FormFieldExtensionData< export const formFieldsApi: ExtensionDefinition<{ config: {}; configInput: {}; - output: ConfigurableExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>; + output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>; inputs: { formFields: ExtensionInput< ConfigurableExtensionDataRef< @@ -218,9 +224,13 @@ export const formFieldsApi: ExtensionDefinition<{ }; kind: 'api'; name: 'form-fields'; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory<TApi, TImpl, TDeps>, + ) => ExtensionBlueprintParams<AnyApiFactory>; }>; // @alpha @deprecated (undocumented) diff --git a/plugins/scaffolder-react/report.api.md b/plugins/scaffolder-react/report.api.md index d66ec36acb..4b81a18eb6 100644 --- a/plugins/scaffolder-react/report.api.md +++ b/plugins/scaffolder-react/report.api.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { Action as Action_2 } from '@backstage/plugin-scaffolder-common'; import { ApiHolder } from '@backstage/core-plugin-api'; import { ApiRef } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; @@ -23,9 +24,10 @@ import { IChangeEvent } from '@rjsf/core'; import { IdSchema } from '@rjsf/utils'; import { JsonObject } from '@backstage/types'; import { JSONSchema7 } from 'json-schema'; -import { JsonValue } from '@backstage/types'; import { JSX as JSX_2 } from 'react/jsx-runtime'; -import { Observable } from '@backstage/types'; +import { ListActionsResponse as ListActionsResponse_2 } from '@backstage/plugin-scaffolder-common'; +import { ListTemplatingExtensionsResponse as ListTemplatingExtensionsResponse_2 } from '@backstage/plugin-scaffolder-common'; +import { LogEvent as LogEvent_2 } from '@backstage/plugin-scaffolder-common'; import { PropsWithChildren } from 'react'; import { ReactNode } from 'react'; import { Ref } from 'react'; @@ -33,27 +35,34 @@ import { Registry } from '@rjsf/utils'; import { RegistryWidgetsType } from '@rjsf/utils'; import { RJSFSchema } from '@rjsf/utils'; import { RJSFValidationError } from '@rjsf/utils'; +import { ScaffolderApi as ScaffolderApi_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderDryRunOptions as ScaffolderDryRunOptions_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderDryRunResponse as ScaffolderDryRunResponse_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderGetIntegrationsListOptions as ScaffolderGetIntegrationsListOptions_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderGetIntegrationsListResponse as ScaffolderGetIntegrationsListResponse_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderOutputLink as ScaffolderOutputLink_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderOutputText as ScaffolderOutputText_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderScaffoldOptions as ScaffolderScaffoldOptions_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderScaffoldResponse as ScaffolderScaffoldResponse_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderStreamLogsOptions as ScaffolderStreamLogsOptions_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderTask as ScaffolderTask_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderTaskOutput as ScaffolderTaskOutput_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderTaskStatus as ScaffolderTaskStatus_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderUsageExample as ScaffolderUsageExample_2 } from '@backstage/plugin-scaffolder-common'; import { StrictRJSFSchema } from '@rjsf/utils'; -import { TaskSpec } from '@backstage/plugin-scaffolder-common'; -import { TaskStep } from '@backstage/plugin-scaffolder-common'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { TemplatePresentationV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { TemplateFilter as TemplateFilter_2 } from '@backstage/plugin-scaffolder-common'; +import { TemplateGlobalFunction as TemplateGlobalFunction_2 } from '@backstage/plugin-scaffolder-common'; +import { TemplateGlobalValue as TemplateGlobalValue_2 } from '@backstage/plugin-scaffolder-common'; +import { TemplateParameterSchema as TemplateParameterSchema_2 } from '@backstage/plugin-scaffolder-common'; import { TemplatesType } from '@rjsf/utils'; import { UIOptionsType } from '@rjsf/utils'; import { UiSchema } from '@rjsf/utils'; import { ValidatorType } from '@rjsf/utils'; import { z } from 'zod'; -// @public -export type Action = { - id: string; - description?: string; - schema?: { - input?: JSONSchema7; - output?: JSONSchema7; - }; - examples?: ActionExample[]; -}; +// @public @deprecated +export type Action = Action_2; // @public @deprecated export type ActionExample = ScaffolderUsageExample; @@ -162,30 +171,15 @@ export type LayoutTemplate<T = any> = NonNullable< FormProps_2<T>['uiSchema'] >['ui:ObjectFieldTemplate']; -// @public -export type ListActionsResponse = Array<Action>; +// @public @deprecated +export type ListActionsResponse = ListActionsResponse_2; -// @public -export type ListTemplatingExtensionsResponse = { - filters: Record<string, TemplateFilter>; - globals: { - functions: Record<string, TemplateGlobalFunction>; - values: Record<string, TemplateGlobalValue>; - }; -}; +// @public @deprecated +export type ListTemplatingExtensionsResponse = + ListTemplatingExtensionsResponse_2; -// @public -export type LogEvent = { - type: 'log' | 'completion' | 'cancelled' | 'recovered'; - body: { - message: string; - stepId?: string; - status?: ScaffolderTaskStatus; - }; - createdAt: string; - id: string; - taskId: string; -}; +// @public @deprecated +export type LogEvent = LogEvent_2; // @public (undocumented) export function makeFieldSchema< @@ -210,125 +204,39 @@ export type ReviewStepProps = { }[]; }; -// @public -export interface ScaffolderApi { - // (undocumented) - autocomplete?(options: { - token: string; - provider: string; - resource: string; - context?: Record<string, string>; - }): Promise<{ - results: { - title?: string; - id: string; - }[]; - }>; - cancelTask(taskId: string): Promise<void>; - // (undocumented) - dryRun?(options: ScaffolderDryRunOptions): Promise<ScaffolderDryRunResponse>; - // (undocumented) - getIntegrationsList( - options: ScaffolderGetIntegrationsListOptions, - ): Promise<ScaffolderGetIntegrationsListResponse>; - // (undocumented) - getTask(taskId: string): Promise<ScaffolderTask>; - // (undocumented) - getTemplateParameterSchema( - templateRef: string, - ): Promise<TemplateParameterSchema>; - listActions(): Promise<ListActionsResponse>; - // (undocumented) - listTasks?(options: { - filterByOwnership: 'owned' | 'all'; - limit?: number; - offset?: number; - }): Promise<{ - tasks: ScaffolderTask[]; - totalTasks?: number; - }>; - listTemplatingExtensions?(): Promise<ListTemplatingExtensionsResponse>; - retry?(taskId: string): Promise<void>; - scaffold( - options: ScaffolderScaffoldOptions, - ): Promise<ScaffolderScaffoldResponse>; - // (undocumented) - streamLogs(options: ScaffolderStreamLogsOptions): Observable<LogEvent>; -} +// @public @deprecated +export type ScaffolderApi = ScaffolderApi_2; // @public (undocumented) -export const scaffolderApiRef: ApiRef<ScaffolderApi>; +export const scaffolderApiRef: ApiRef<ScaffolderApi_2>; -// @public (undocumented) -export interface ScaffolderDryRunOptions { - // (undocumented) - directoryContents: { - path: string; - base64Content: string; - }[]; - // (undocumented) - secrets?: Record<string, string>; - // (undocumented) - template: JsonValue; - // (undocumented) - values: JsonObject; -} +// @public @deprecated (undocumented) +export type ScaffolderDryRunOptions = ScaffolderDryRunOptions_2; -// @public (undocumented) -export interface ScaffolderDryRunResponse { - // (undocumented) - directoryContents: Array<{ - path: string; - base64Content: string; - executable: boolean; - }>; - // (undocumented) - log: Array<Pick<LogEvent, 'body'>>; - // (undocumented) - output: ScaffolderTaskOutput; - // (undocumented) - steps: TaskStep[]; -} +// @public @deprecated (undocumented) +export type ScaffolderDryRunResponse = ScaffolderDryRunResponse_2; // @public export const ScaffolderFieldExtensions: React.ComponentType< React.PropsWithChildren<{}> >; -// @public -export interface ScaffolderGetIntegrationsListOptions { - // (undocumented) - allowedHosts: string[]; -} +// @public @deprecated +export type ScaffolderGetIntegrationsListOptions = + ScaffolderGetIntegrationsListOptions_2; -// @public -export interface ScaffolderGetIntegrationsListResponse { - // (undocumented) - integrations: { - type: string; - title: string; - host: string; - }[]; -} +// @public @deprecated +export type ScaffolderGetIntegrationsListResponse = + ScaffolderGetIntegrationsListResponse_2; // @public export const ScaffolderLayouts: ComponentType<PropsWithChildren<{}>>; -// @public (undocumented) -export type ScaffolderOutputLink = { - title?: string; - icon?: string; - url?: string; - entityRef?: string; -}; +// @public @deprecated (undocumented) +export type ScaffolderOutputLink = ScaffolderOutputLink_2; -// @public (undocumented) -export type ScaffolderOutputText = { - title?: string; - icon?: string; - content?: string; - default?: boolean; -}; +// @public @deprecated (undocumented) +export type ScaffolderOutputText = ScaffolderOutputText_2; // @public export type ScaffolderRJSFField< @@ -437,21 +345,11 @@ export type ScaffolderRJSFRegistryFieldsType< [name: string]: ScaffolderRJSFField<T, S, F>; }; -// @public -export interface ScaffolderScaffoldOptions { - // (undocumented) - secrets?: Record<string, string>; - // (undocumented) - templateRef: string; - // (undocumented) - values: Record<string, JsonValue>; -} +// @public @deprecated +export type ScaffolderScaffoldOptions = ScaffolderScaffoldOptions_2; -// @public -export interface ScaffolderScaffoldResponse { - // (undocumented) - taskId: string; -} +// @public @deprecated +export type ScaffolderScaffoldResponse = ScaffolderScaffoldResponse_2; // @public export type ScaffolderStep = { @@ -461,48 +359,20 @@ export type ScaffolderStep = { startedAt?: string; }; -// @public -export interface ScaffolderStreamLogsOptions { - // (undocumented) - after?: number; - // (undocumented) - isTaskRecoverable?: boolean; - // (undocumented) - taskId: string; -} +// @public @deprecated +export type ScaffolderStreamLogsOptions = ScaffolderStreamLogsOptions_2; -// @public -export type ScaffolderTask = { - id: string; - spec: TaskSpec; - status: 'failed' | 'completed' | 'processing' | 'open' | 'cancelled'; - lastHeartbeatAt: string; - createdAt: string; -}; +// @public @deprecated +export type ScaffolderTask = ScaffolderTask_2; -// @public (undocumented) -export type ScaffolderTaskOutput = { - links?: ScaffolderOutputLink[]; - text?: ScaffolderOutputText[]; -} & { - [key: string]: unknown; -}; +// @public @deprecated (undocumented) +export type ScaffolderTaskOutput = ScaffolderTaskOutput_2; -// @public -export type ScaffolderTaskStatus = - | 'cancelled' - | 'completed' - | 'failed' - | 'open' - | 'processing' - | 'skipped'; +// @public @deprecated +export type ScaffolderTaskStatus = ScaffolderTaskStatus_2; -// @public -export type ScaffolderUsageExample = { - description?: string; - example: string; - notes?: string; -}; +// @public @deprecated +export type ScaffolderUsageExample = ScaffolderUsageExample_2; // @public export interface ScaffolderUseTemplateSecrets { @@ -535,32 +405,14 @@ export type TaskStream = { output?: ScaffolderTaskOutput; }; -// @public -export type TemplateFilter = { - description?: string; - schema?: { - input?: JSONSchema7; - arguments?: JSONSchema7[]; - output?: JSONSchema7; - }; - examples?: ScaffolderUsageExample[]; -}; +// @public @deprecated +export type TemplateFilter = TemplateFilter_2; -// @public -export type TemplateGlobalFunction = { - description?: string; - schema?: { - arguments?: JSONSchema7[]; - output?: JSONSchema7; - }; - examples?: ScaffolderUsageExample[]; -}; +// @public @deprecated +export type TemplateGlobalFunction = TemplateGlobalFunction_2; -// @public -export type TemplateGlobalValue = { - description?: string; - value: JsonValue; -}; +// @public @deprecated +export type TemplateGlobalValue = TemplateGlobalValue_2; // @public (undocumented) export type TemplateGroupFilter = { @@ -568,21 +420,8 @@ export type TemplateGroupFilter = { filter: (entity: TemplateEntityV1beta3) => boolean; }; -// @public -export type TemplateParameterSchema = { - title: string; - description?: string; - presentation?: TemplatePresentationV1beta3; - steps: Array<{ - title: string; - description?: string; - schema: JsonObject; - }>; - EXPERIMENTAL_formDecorators?: { - id: string; - input?: JsonObject; - }[]; -}; +// @public @deprecated +export type TemplateParameterSchema = TemplateParameterSchema_2; // @public export const useCustomFieldExtensions: < diff --git a/plugins/scaffolder-react/src/api/types.ts b/plugins/scaffolder-react/src/api/types.ts index d4a9bbe0f8..b3cb177128 100644 --- a/plugins/scaffolder-react/src/api/types.ts +++ b/plugins/scaffolder-react/src/api/types.ts @@ -13,47 +13,54 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TaskSpec, TaskStep } from '@backstage/plugin-scaffolder-common'; -import { JsonObject, JsonValue, Observable } from '@backstage/types'; -import { JSONSchema7 } from 'json-schema'; -import { TemplateParameterSchema } from '../types'; + +import { + type ScaffolderApi as _ScaffolderApi, + type ScaffolderDryRunOptions as _ScaffolderDryRunOptions, + type ScaffolderDryRunResponse as _ScaffolderDryRunResponse, + type ScaffolderGetIntegrationsListOptions as _ScaffolderGetIntegrationsListOptions, + type ScaffolderGetIntegrationsListResponse as _ScaffolderGetIntegrationsListResponse, + type ScaffolderScaffoldOptions as _ScaffolderScaffoldOptions, + type ScaffolderScaffoldResponse as _ScaffolderScaffoldResponse, + type ScaffolderStreamLogsOptions as _ScaffolderStreamLogsOptions, + type ScaffolderUsageExample as _ScaffolderUsageExample, + type Action as _Action, + type ListActionsResponse as _ListActionsResponse, + type ListTemplatingExtensionsResponse as _ListTemplatingExtensionsResponse, + type LogEvent as _LogEvent, + type ScaffolderOutputLink as _ScaffolderOutputLink, + type ScaffolderOutputText as _ScaffolderOutputText, + type ScaffolderTask as _ScaffolderTask, + type ScaffolderTaskOutput as _ScaffolderTaskOutput, + type ScaffolderTaskStatus as _ScaffolderTaskStatus, + type TemplateFilter as _TemplateFilter, + type TemplateGlobalFunction as _TemplateGlobalFunction, + type TemplateGlobalValue as _TemplateGlobalValue, +} from '@backstage/plugin-scaffolder-common'; /** * The status of each task in a Scaffolder Job * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderTaskStatus} instead as this has now been moved. */ -export type ScaffolderTaskStatus = - | 'cancelled' - | 'completed' - | 'failed' - | 'open' - | 'processing' - | 'skipped'; +export type ScaffolderTaskStatus = _ScaffolderTaskStatus; /** * The shape of each task returned from the `scaffolder-backend` * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderTask} instead as this has now been moved. */ -export type ScaffolderTask = { - id: string; - spec: TaskSpec; - status: 'failed' | 'completed' | 'processing' | 'open' | 'cancelled'; - lastHeartbeatAt: string; - createdAt: string; -}; +export type ScaffolderTask = _ScaffolderTask; /** * A single scaffolder usage example * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderUsageExample} instead as this has now been moved. */ -export type ScaffolderUsageExample = { - description?: string; - example: string; - notes?: string; -}; +export type ScaffolderUsageExample = _ScaffolderUsageExample; /** * A single action example @@ -67,249 +74,135 @@ export type ActionExample = ScaffolderUsageExample; * The response shape for a single action in the `listActions` call to the `scaffolder-backend` * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#Action} instead as this has now been moved. */ -export type Action = { - id: string; - description?: string; - schema?: { - input?: JSONSchema7; - output?: JSONSchema7; - }; - examples?: ActionExample[]; -}; +export type Action = _Action; /** * The response shape for the `listActions` call to the `scaffolder-backend` * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ListActionsResponse} instead as this has now been moved. */ -export type ListActionsResponse = Array<Action>; +export type ListActionsResponse = _ListActionsResponse; /** * The response shape for a single filter in the `listTemplatingExtensions` call to the `scaffolder-backend` * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#TemplateFilter} instead as this has now been moved. */ -export type TemplateFilter = { - description?: string; - schema?: { - input?: JSONSchema7; - arguments?: JSONSchema7[]; - output?: JSONSchema7; - }; - examples?: ScaffolderUsageExample[]; -}; +export type TemplateFilter = _TemplateFilter; /** * The response shape for a single global function in the `listTemplatingExtensions` call to the `scaffolder-backend` * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#TemplateGlobalFunction} instead as this has now been moved. */ -export type TemplateGlobalFunction = { - description?: string; - schema?: { - arguments?: JSONSchema7[]; - output?: JSONSchema7; - }; - examples?: ScaffolderUsageExample[]; -}; +export type TemplateGlobalFunction = _TemplateGlobalFunction; /** * The response shape for a single global value in the `listTemplatingExtensions` call to the `scaffolder-backend` * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#TemplateGlobalValue} instead as this has now been moved. */ -export type TemplateGlobalValue = { - description?: string; - value: JsonValue; -}; +export type TemplateGlobalValue = _TemplateGlobalValue; /** * The response shape for the `listTemplatingExtensions` call to the `scaffolder-backend` * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ListTemplatingExtensionsResponse} instead as this has now been moved. */ -export type ListTemplatingExtensionsResponse = { - filters: Record<string, TemplateFilter>; - globals: { - functions: Record<string, TemplateGlobalFunction>; - values: Record<string, TemplateGlobalValue>; - }; -}; +export type ListTemplatingExtensionsResponse = + _ListTemplatingExtensionsResponse; -/** @public */ -export type ScaffolderOutputLink = { - title?: string; - icon?: string; - url?: string; - entityRef?: string; -}; +/** + * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderOutputLink} instead as this has now been moved. + */ +export type ScaffolderOutputLink = _ScaffolderOutputLink; -/** @public */ -export type ScaffolderOutputText = { - title?: string; - icon?: string; - content?: string; - default?: boolean; -}; +/** + * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderOutputText} instead as this has now been moved. + */ +export type ScaffolderOutputText = _ScaffolderOutputText; -/** @public */ -export type ScaffolderTaskOutput = { - links?: ScaffolderOutputLink[]; - text?: ScaffolderOutputText[]; -} & { - [key: string]: unknown; -}; +/** + * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderTaskOutput} instead as this has now been moved. + */ +export type ScaffolderTaskOutput = _ScaffolderTaskOutput; /** * The shape of a `LogEvent` message from the `scaffolder-backend` * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#LogEvent} instead as this has now been moved. */ -export type LogEvent = { - type: 'log' | 'completion' | 'cancelled' | 'recovered'; - body: { - message: string; - stepId?: string; - status?: ScaffolderTaskStatus; - }; - createdAt: string; - id: string; - taskId: string; -}; +export type LogEvent = _LogEvent; /** * The input options to the `scaffold` method of the `ScaffolderClient`. * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderScaffoldOptions} instead as this has now been moved. */ -export interface ScaffolderScaffoldOptions { - templateRef: string; - values: Record<string, JsonValue>; - secrets?: Record<string, string>; -} +export type ScaffolderScaffoldOptions = _ScaffolderScaffoldOptions; /** * The response shape of the `scaffold` method of the `ScaffolderClient`. * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderScaffoldResponse} instead as this has now been moved. */ -export interface ScaffolderScaffoldResponse { - taskId: string; -} +export type ScaffolderScaffoldResponse = _ScaffolderScaffoldResponse; /** * The arguments for `getIntegrationsList`. * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderGetIntegrationsListOptions} instead as this has now been moved. */ -export interface ScaffolderGetIntegrationsListOptions { - allowedHosts: string[]; -} +export type ScaffolderGetIntegrationsListOptions = + _ScaffolderGetIntegrationsListOptions; /** * The response shape for `getIntegrationsList`. * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderGetIntegrationsListResponse} instead as this has now been moved. */ -export interface ScaffolderGetIntegrationsListResponse { - integrations: { type: string; title: string; host: string }[]; -} +export type ScaffolderGetIntegrationsListResponse = + _ScaffolderGetIntegrationsListResponse; /** * The input options to the `streamLogs` method of the `ScaffolderClient`. * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderStreamLogsOptions} instead as this has now been moved. */ -export interface ScaffolderStreamLogsOptions { - isTaskRecoverable?: boolean; - taskId: string; - after?: number; -} +export type ScaffolderStreamLogsOptions = _ScaffolderStreamLogsOptions; -/** @public */ -export interface ScaffolderDryRunOptions { - template: JsonValue; - values: JsonObject; - secrets?: Record<string, string>; - directoryContents: { path: string; base64Content: string }[]; -} +/** + * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderDryRunOptions} instead as this has now been moved. + */ +export type ScaffolderDryRunOptions = _ScaffolderDryRunOptions; + +/** + * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderDryRunResponse} instead as this has now been moved. + */ +export type ScaffolderDryRunResponse = _ScaffolderDryRunResponse; -/** @public */ -export interface ScaffolderDryRunResponse { - directoryContents: Array<{ - path: string; - base64Content: string; - executable: boolean; - }>; - log: Array<Pick<LogEvent, 'body'>>; - steps: TaskStep[]; - output: ScaffolderTaskOutput; -} /** * An API to interact with the scaffolder backend. * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderApi} instead as this has now been moved. */ -export interface ScaffolderApi { - getTemplateParameterSchema( - templateRef: string, - ): Promise<TemplateParameterSchema>; - - /** - * Executes the scaffolding of a component, given a template and its - * parameter values. - * - * @param options - The {@link ScaffolderScaffoldOptions} the scaffolding. - */ - scaffold( - options: ScaffolderScaffoldOptions, - ): Promise<ScaffolderScaffoldResponse>; - - getTask(taskId: string): Promise<ScaffolderTask>; - - /** - * Sends a signal to a task broker to cancel the running task by taskId. - * - * @param taskId - the id of the task - */ - cancelTask(taskId: string): Promise<void>; - - /** - * Starts the task again from the point where it failed. - * - * @param taskId - the id of the task - */ - retry?(taskId: string): Promise<void>; - - listTasks?(options: { - filterByOwnership: 'owned' | 'all'; - limit?: number; - offset?: number; - }): Promise<{ tasks: ScaffolderTask[]; totalTasks?: number }>; - - getIntegrationsList( - options: ScaffolderGetIntegrationsListOptions, - ): Promise<ScaffolderGetIntegrationsListResponse>; - - /** - * Returns a list of all installed actions. - */ - listActions(): Promise<ListActionsResponse>; - - /** - * Returns a structure describing the available templating extensions. - */ - listTemplatingExtensions?(): Promise<ListTemplatingExtensionsResponse>; - - streamLogs(options: ScaffolderStreamLogsOptions): Observable<LogEvent>; - - dryRun?(options: ScaffolderDryRunOptions): Promise<ScaffolderDryRunResponse>; - - autocomplete?(options: { - token: string; - provider: string; - resource: string; - context?: Record<string, string>; - }): Promise<{ results: { title?: string; id: string }[] }>; -} +export type ScaffolderApi = _ScaffolderApi; diff --git a/plugins/scaffolder-react/src/next/api/FormFieldsApi.ts b/plugins/scaffolder-react/src/next/api/FormFieldsApi.ts index 7712b91a18..d01b4e37b9 100644 --- a/plugins/scaffolder-react/src/next/api/FormFieldsApi.ts +++ b/plugins/scaffolder-react/src/next/api/FormFieldsApi.ts @@ -16,13 +16,12 @@ import { ApiBlueprint, - createApiFactory, createExtensionInput, } from '@backstage/frontend-plugin-api'; import { formFieldsApiRef } from './ref'; -import { ScaffolderFormFieldsApi } from './types'; +import { FormField, ScaffolderFormFieldsApi } from './types'; import { FormFieldBlueprint } from '../blueprints'; -import { FormField, OpaqueFormField } from '@internal/scaffolder'; +import { OpaqueFormField } from '@internal/scaffolder'; class DefaultScaffolderFormFieldsApi implements ScaffolderFormFieldsApi { constructor( @@ -53,12 +52,12 @@ export const formFieldsApi = ApiBlueprint.makeWithOverrides({ e.get(FormFieldBlueprint.dataRefs.formFieldLoader), ); - return originalFactory({ - factory: createApiFactory({ + return originalFactory(defineParams => + defineParams({ api: formFieldsApiRef, deps: {}, factory: () => new DefaultScaffolderFormFieldsApi(formFieldLoaders), }), - }); + ); }, }); diff --git a/plugins/scaffolder-react/src/next/api/index.ts b/plugins/scaffolder-react/src/next/api/index.ts index ecc62e5acf..52c40ad99a 100644 --- a/plugins/scaffolder-react/src/next/api/index.ts +++ b/plugins/scaffolder-react/src/next/api/index.ts @@ -16,4 +16,4 @@ export { formFieldsApi } from './FormFieldsApi'; export { formFieldsApiRef } from './ref'; -export type { ScaffolderFormFieldsApi } from './types'; +export type { ScaffolderFormFieldsApi, FormField } from './types'; diff --git a/plugins/scaffolder-react/src/next/api/types.ts b/plugins/scaffolder-react/src/next/api/types.ts index ea93ba86b2..9e028567f6 100644 --- a/plugins/scaffolder-react/src/next/api/types.ts +++ b/plugins/scaffolder-react/src/next/api/types.ts @@ -23,3 +23,8 @@ import { FormFieldExtensionData } from '../blueprints'; export interface ScaffolderFormFieldsApi { getFormFields(): Promise<FormFieldExtensionData[]>; } + +/** @alpha */ +export interface FormField { + readonly $$type: '@backstage/scaffolder/FormField'; +} diff --git a/plugins/scaffolder-react/src/next/blueprints/FormFieldBlueprint.tsx b/plugins/scaffolder-react/src/next/blueprints/FormFieldBlueprint.tsx index 4324a080f6..afbf2b501d 100644 --- a/plugins/scaffolder-react/src/next/blueprints/FormFieldBlueprint.tsx +++ b/plugins/scaffolder-react/src/next/blueprints/FormFieldBlueprint.tsx @@ -19,8 +19,9 @@ import { } from '@backstage/frontend-plugin-api'; import { z } from 'zod'; -import { OpaqueFormField, FormField } from '@internal/scaffolder'; +import { OpaqueFormField } from '@internal/scaffolder'; import { FormFieldExtensionData } from './types'; +import { FormField } from '../api'; const formFieldExtensionDataRef = createExtensionDataRef< () => Promise<FormField> diff --git a/plugins/scaffolder-react/src/next/components/ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx b/plugins/scaffolder-react/src/next/components/ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx index 9546792983..2df4ff3b71 100644 --- a/plugins/scaffolder-react/src/next/components/ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx +++ b/plugins/scaffolder-react/src/next/components/ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx @@ -30,7 +30,6 @@ import Functions from '@material-ui/icons/Functions'; import MoreVert from '@material-ui/icons/MoreVert'; import { SyntheticEvent, useState } from 'react'; import { usePermission } from '@backstage/plugin-permission-react'; -import { taskReadPermission } from '@backstage/plugin-scaffolder-common/alpha'; import { templateManagementPermission } from '@backstage/plugin-scaffolder-common/alpha'; import { scaffolderReactTranslationRef } from '../../../translation'; @@ -69,10 +68,6 @@ export function ScaffolderPageContextMenu( const classes = useStyles(); const [anchorEl, setAnchorEl] = useState<HTMLButtonElement>(); - const { allowed: canReadTasks } = usePermission({ - permission: taskReadPermission, - }); - const { allowed: canManageTemplates } = usePermission({ permission: templateManagementPermission, }); @@ -164,7 +159,7 @@ export function ScaffolderPageContextMenu( /> </MenuItem> )} - {onTasksClicked && canReadTasks && ( + {onTasksClicked && ( <MenuItem onClick={onTasksClicked}> <ListItemIcon> <List fontSize="small" /> diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx index af9117ccf5..b0746e0672 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx @@ -672,6 +672,40 @@ describe('Stepper', () => { ); }); + it('should scroll the first main element to top when activeStep changes', async () => { + const manifest: TemplateParameterSchema = { + steps: [ + { title: 'Step 1', schema: { properties: {} } }, + { title: 'Step 2', schema: { properties: {} } }, + ], + title: 'Scroll Test', + }; + + // Render a main element in the document for the Stepper to find + const main = document.createElement('main'); + document.body.appendChild(main); + const scrollToMock = jest.fn(); + main.scrollTo = scrollToMock; + + // Render Stepper as usual (do not pass container) + const { getByRole, unmount } = await renderInTestApp( + <SecretsContextProvider> + <Stepper manifest={manifest} extensions={[]} onCreate={jest.fn()} /> + </SecretsContextProvider>, + ); + + // Click next to change the activeStep + await act(async () => { + fireEvent.click(getByRole('button', { name: 'Next' })); + }); + + expect(scrollToMock).toHaveBeenCalledWith({ top: 0, behavior: 'auto' }); + + // Clean up + document.body.removeChild(main); + unmount(); + }); + describe('Scaffolder Layouts', () => { it('should render the step in the scaffolder layout', async () => { const ScaffolderLayout: LayoutTemplate = ({ properties }) => ( diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index 6715eb0e1e..1f36eb58d3 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -37,6 +37,7 @@ import { merge } from 'lodash'; import { ComponentType, useCallback, + useEffect, useMemo, useState, type ReactNode, @@ -218,6 +219,13 @@ export const Stepper = (stepperProps: StepperProps) => { [validation, analytics], ); + useEffect(() => { + const main = document.querySelector('main'); + if (main && typeof main.scrollTo === 'function') { + main.scrollTo({ top: 0, behavior: 'auto' }); + } + }, [activeStep]); + const mergedUiSchema = merge({}, propUiSchema, currentStep?.uiSchema); const [isCreating, setIsCreating] = useState(false); diff --git a/plugins/scaffolder-react/src/next/components/TemplateGroup/TemplateGroup.tsx b/plugins/scaffolder-react/src/next/components/TemplateGroup/TemplateGroup.tsx index a70b822d13..74ade33344 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateGroup/TemplateGroup.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateGroup/TemplateGroup.tsx @@ -22,7 +22,7 @@ import { } from '@backstage/core-components'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { TemplateCardProps, TemplateCard } from '../TemplateCard'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { AnalyticsContext, IconComponent } from '@backstage/core-plugin-api'; /** * The props for the {@link TemplateGroup} component. @@ -69,12 +69,18 @@ export const TemplateGroup = (props: TemplateGroupProps) => { {titleComponent} <ItemCardGrid> {templates.map(({ template, additionalLinks }) => ( - <Card + <AnalyticsContext + attributes={{ + entityRef: stringifyEntityRef(template), + }} key={stringifyEntityRef(template)} - additionalLinks={additionalLinks} - template={template} - onSelected={onSelected} - /> + > + <Card + additionalLinks={additionalLinks} + template={template} + onSelected={onSelected} + /> + </AnalyticsContext> ))} </ItemCardGrid> </Content> diff --git a/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx b/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx index a0070765fe..58b74dff9d 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx @@ -22,7 +22,7 @@ import { } from '@backstage/plugin-scaffolder-common'; import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react'; import Typography from '@material-ui/core/Typography'; -import { ComponentType, useCallback } from 'react'; +import { ComponentType, useCallback, useEffect } from 'react'; import { TemplateGroup } from '../TemplateGroup/TemplateGroup'; @@ -58,12 +58,17 @@ export const TemplateGroups = (props: TemplateGroupsProps) => { [onTemplateSelected], ); + useEffect(() => { + if (error) { + errorApi.post(error); + } + }, [error, errorApi]); + if (loading) { return <Progress />; } if (error) { - errorApi.post(error); return null; } diff --git a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx index 2aa4ee9b2d..527009037d 100644 --- a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx +++ b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx @@ -97,13 +97,14 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => { async (formState: Record<string, JsonValue>) => { await onCreate(formState); - const name = - typeof formState.name === 'string' ? formState.name : undefined; - analytics.captureEvent('create', name ?? templateName ?? 'unknown', { + analytics.captureEvent('create', 'Task has been created', { value: minutesSaved, + attributes: { + templateSteps: sortedManifest?.steps?.length ?? 0, + }, }); }, - [onCreate, analytics, templateName, minutesSaved], + [onCreate, analytics, minutesSaved, sortedManifest], ); useEffect(() => { diff --git a/plugins/scaffolder-react/src/next/hooks/useFilteredSchemaProperties.ts b/plugins/scaffolder-react/src/next/hooks/useFilteredSchemaProperties.ts index be6f581f87..cac696a745 100644 --- a/plugins/scaffolder-react/src/next/hooks/useFilteredSchemaProperties.ts +++ b/plugins/scaffolder-react/src/next/hooks/useFilteredSchemaProperties.ts @@ -16,6 +16,7 @@ import cloneDeep from 'lodash/cloneDeep'; import { useApi, featureFlagsApiRef } from '@backstage/core-plugin-api'; import { TemplateParameterSchema } from '@backstage/plugin-scaffolder-react'; +import { useMemo } from 'react'; /** * Returns manifest of software templates with steps without a featureFlag tag. @@ -28,49 +29,51 @@ export const useFilteredSchemaProperties = ( const featureFlagKey = 'backstage:featureFlag'; const featureFlagApi = useApi(featureFlagsApiRef); - if (!manifest) { - return undefined; - } - - const filteredSteps = manifest?.steps - .filter(step => { - const featureFlag = step.schema[featureFlagKey]; - return ( - typeof featureFlag !== 'string' || featureFlagApi.isActive(featureFlag) - ); - }) - .map(step => { - const filteredStep = cloneDeep(step); - const removedPropertyKeys: Array<string> = []; - if (filteredStep.schema.properties) { - filteredStep.schema.properties = Object.fromEntries( - Object.entries(filteredStep.schema.properties).filter( - ([key, value]) => { - if (value[featureFlagKey]) { - if (featureFlagApi.isActive(value[featureFlagKey])) { - return true; - } - - removedPropertyKeys.push(key); - return false; - } - return true; - }, - ), + return useMemo(() => { + if (!manifest) { + return undefined; + } + const filteredSteps = manifest?.steps + .filter(step => { + const featureFlag = step.schema[featureFlagKey]; + return ( + typeof featureFlag !== 'string' || + featureFlagApi.isActive(featureFlag) ); + }) + .map(step => { + const filteredStep = cloneDeep(step); + const removedPropertyKeys: Array<string> = []; + if (filteredStep.schema.properties) { + filteredStep.schema.properties = Object.fromEntries( + Object.entries(filteredStep.schema.properties).filter( + ([key, value]) => { + if (value[featureFlagKey]) { + if (featureFlagApi.isActive(value[featureFlagKey])) { + return true; + } - // remove the feature flag property key from required if they are not active - filteredStep.schema.required = Array.isArray( - filteredStep.schema.required, - ) - ? filteredStep.schema.required?.filter( - r => !removedPropertyKeys.includes(r as string), - ) - : filteredStep.schema.required; - } + removedPropertyKeys.push(key); + return false; + } + return true; + }, + ), + ); - return filteredStep; - }); + // remove the feature flag property key from required if they are not active + filteredStep.schema.required = Array.isArray( + filteredStep.schema.required, + ) + ? filteredStep.schema.required?.filter( + r => !removedPropertyKeys.includes(r as string), + ) + : filteredStep.schema.required; + } - return { ...manifest, steps: filteredSteps }; + return filteredStep; + }); + + return { ...manifest, steps: filteredSteps }; + }, [manifest, featureFlagApi]); }; diff --git a/plugins/scaffolder-react/src/types.ts b/plugins/scaffolder-react/src/types.ts index 5f557198c6..5b724c5871 100644 --- a/plugins/scaffolder-react/src/types.ts +++ b/plugins/scaffolder-react/src/types.ts @@ -14,24 +14,13 @@ * limitations under the License. */ -import { JsonObject } from '@backstage/types'; - -import { TemplatePresentationV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { TemplateParameterSchema as _TemplateParameterSchema } from '@backstage/plugin-scaffolder-common'; /** * The shape of each entry of parameters which gets rendered * as a separate step in the wizard input * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#TemplateParameterSchema} instead as this has now been moved. */ -export type TemplateParameterSchema = { - title: string; - description?: string; - presentation?: TemplatePresentationV1beta3; - steps: Array<{ - title: string; - description?: string; - schema: JsonObject; - }>; - EXPERIMENTAL_formDecorators?: { id: string; input?: JsonObject }[]; -}; +export type TemplateParameterSchema = _TemplateParameterSchema; diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index ba8d5db71b..4a7d5ad9b9 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,318 @@ # @backstage/plugin-scaffolder +## 1.34.0-next.3 + +### Minor Changes + +- b1c0696: Add resizable panels width for the editor and preview panels in the template editor and template form playground layouts. Users can now resize these panels by dragging the divider between the two areas. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.2 + - @backstage/core-compat-api@0.5.0-next.3 + - @backstage/core-components@0.17.5-next.2 + +## 1.34.0-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-scaffolder-common@1.7.0-next.0 + - @backstage/plugin-scaffolder-react@1.19.0-next.1 + +## 1.34.0-next.1 + +### Minor Changes + +- c08cbc4: Move Scaffolder API to OpenAPI + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- b0dc9b8: differentiate between entirely and partially composite schemas in schema rendering +- c4b7c50: Export `FormField` type from `/alpha` in `-react` package, and internal refactor. +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.19.0-next.1 + - @backstage/plugin-scaffolder-common@1.7.0-next.0 + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + +## 1.33.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/plugin-scaffolder-react@1.18.1-next.0 + - @backstage/integration-react@1.2.9 + - @backstage/frontend-plugin-api@0.10.4 + +## 1.33.0 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- 289e4a1: Filter MultiEntityPicker options based on rendered option value +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-scaffolder-react@1.18.0 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-scaffolder-common@1.6.0 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + +## 1.33.0-next.2 + +### Minor Changes + +- c1ce316: BREAKING `/alpha`: Converted `scaffolder.task.read` and `scaffolder.task.cancel` into Resource Permissions. + + BREAKING `/alpha`: Added a new scaffolder rule `isTaskOwner` for `scaffolder.task.read` and `scaffolder.task.cancel` to allow for conditional permission policies such as restricting access to tasks and task events based on task creators. + + BREAKING `/alpha`: Retrying a task now requires both `scaffolder.task.read` and `scaffolder.task.create` permissions, replacing the previous requirement of `scaffolder.task.read` and `scaffolder.task.cancel`. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.18.0-next.2 + - @backstage/core-components@0.17.4-next.2 + - @backstage/plugin-scaffolder-common@1.6.0-next.1 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + +## 1.32.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-permission-react@0.4.36-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-scaffolder-common@1.5.12-next.0 + - @backstage/plugin-scaffolder-react@1.17.1-next.1 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 1.32.1-next.0 + +### Patch Changes + +- 289e4a1: Filter MultiEntityPicker options based on rendered option value +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/plugin-scaffolder-react@1.17.1-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 1.32.0 + +### Minor Changes + +- 6c972fe: Added information about the `entityRef` and `taskId` to the analytics events whenever is possible. +- 3c59ece: **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. + +### Patch Changes + +- d781b33: render details for composite property schemas +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- b00c160: Remove React import form notification and scaffolder plugin +- 95a1d72: Added appropriate message when global templating function metadata is absent. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-scaffolder-react@1.17.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/integration-react@1.2.8 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.35 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 1.32.0-next.2 + +### Minor Changes + +- 3c59ece: **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. + +### Patch Changes + +- b00c160: Remove React import form notification and scaffolder plugin +- 95a1d72: Added appropriate message when global templating function metadata is absent. +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-scaffolder-react@1.16.1-next.2 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/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.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 + +- 4235e87: add templating extensions page + +### Patch Changes + +- 92c3658: Full support in EntityPicker (and derivatives) for default EntityPresentationApi +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- d7da01d: Fix EntityPicker field to render description as markdown, matching other form components in the system. +- 36ae651: Fixing a bug where the name for `templatingExtensions` was incorrectly set to `templateExtensions` +- 72d019d: Removed various typos +- a274e0a: Migrate custom fields to new schema factory function; + standardize field descriptions to prefer ui:description and present consistently, + utilizing ScaffolderField component where possible. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/integration@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/plugin-scaffolder-react@1.16.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 1.31.0-next.3 + +### Patch Changes + +- a274e0a: Migrate custom fields to new schema factory function; + standardize field descriptions to prefer ui:description and present consistently, + utilizing ScaffolderField component where possible. +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/plugin-scaffolder-react@1.16.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + ## 1.31.0-next.2 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 9c71f584ed..335fbcbf00 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.31.0-next.2", + "version": "1.34.0-next.3", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin", @@ -79,7 +79,6 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", - "@microsoft/fetch-event-source": "^2.0.1", "@react-hookz/web": "^24.0.0", "@rjsf/core": "5.23.2", "@rjsf/material-ui": "5.23.2", @@ -97,10 +96,10 @@ "luxon": "^3.0.0", "qs": "^6.9.4", "react-resizable": "^3.0.5", + "react-resizable-panels": "^3.0.4", "react-use": "^17.2.4", "react-window": "^1.8.10", "yaml": "^2.0.0", - "zen-observable": "^0.10.0", "zod": "^3.22.4", "zod-to-json-schema": "^3.20.4" }, @@ -120,7 +119,6 @@ "@types/react": "^18.0.0", "@types/react-resizable": "^3.0.8", "@types/react-window": "^1.8.8", - "msw": "^1.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", "react-router-dom": "^6.3.0", diff --git a/plugins/scaffolder/report-alpha.api.md b/plugins/scaffolder/report-alpha.api.md index de088224b8..de1ddba005 100644 --- a/plugins/scaffolder/report-alpha.api.md +++ b/plugins/scaffolder/report-alpha.api.md @@ -5,19 +5,25 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } 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 { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; -import { FormField } from '@internal/scaffolder'; +import { FormField } from '@backstage/plugin-scaffolder-react/alpha'; import { formFieldsApiRef } from '@backstage/plugin-scaffolder-react/alpha'; import type { FormProps as FormProps_2 } from '@rjsf/core'; import { FormProps as FormProps_3 } from '@backstage/plugin-scaffolder-react'; import { 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'; @@ -57,24 +63,20 @@ const _default: FrontendPlugin< name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory<TApi, TImpl, TDeps>, + ) => ExtensionBlueprintParams<AnyApiFactory>; }>; 'api:scaffolder/form-decorators': ExtensionDefinition<{ config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>; inputs: { formDecorators: ExtensionInput< ConfigurableExtensionDataRef< @@ -90,18 +92,18 @@ const _default: FrontendPlugin< }; kind: 'api'; name: 'form-decorators'; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory<TApi, TImpl, TDeps>, + ) => ExtensionBlueprintParams<AnyApiFactory>; }>; 'api:scaffolder/form-fields': ExtensionDefinition<{ config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>; inputs: { formFields: ExtensionInput< ConfigurableExtensionDataRef< @@ -117,8 +119,51 @@ const _default: FrontendPlugin< }; kind: 'api'; name: 'form-fields'; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory<TApi, TImpl, TDeps>, + ) => ExtensionBlueprintParams<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: + | ExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > + | ExtensionDataRef< + () => IconLinkVerticalProps, + 'entity-icon-link-props', + {} + >; + inputs: {}; params: { - factory: AnyApiFactory; + useProps: () => Omit<IconLinkVerticalProps, 'color'>; + filter?: EntityPredicate | ((entity: Entity) => boolean); }; }>; 'nav-item:scaffolder': ExtensionDefinition<{ @@ -126,7 +171,7 @@ const _default: FrontendPlugin< name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { title: string; icon: IconComponent; @@ -150,9 +195,9 @@ const _default: FrontendPlugin< path?: string | undefined; }; output: - | ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}> - | ConfigurableExtensionDataRef<string, 'core.routing.path', {}> - | ConfigurableExtensionDataRef< + | ExtensionDataRef<string, 'core.routing.path', {}> + | ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}> + | ExtensionDataRef< RouteRef<AnyRouteRefParams>, 'core.routing.ref', { @@ -175,7 +220,8 @@ const _default: FrontendPlugin< kind: 'page'; name: undefined; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise<JSX.Element>; routeRef?: RouteRef; }; @@ -185,7 +231,7 @@ const _default: FrontendPlugin< name: 'repo-url-picker'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< () => Promise<FormField>, 'scaffolder.form-field-loader', {} @@ -215,7 +261,7 @@ export class DefaultScaffolderFormDecoratorsApi export const formDecoratorsApi: ExtensionDefinition<{ config: {}; configInput: {}; - output: ConfigurableExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>; + output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>; inputs: { formDecorators: ExtensionInput< ConfigurableExtensionDataRef< @@ -231,9 +277,13 @@ export const formDecoratorsApi: ExtensionDefinition<{ }; kind: 'api'; name: 'form-decorators'; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory<TApi, TImpl, TDeps>, + ) => ExtensionBlueprintParams<AnyApiFactory>; }>; // @alpha (undocumented) @@ -289,6 +339,8 @@ export const scaffolderTranslationRef: TranslationRef< readonly 'fields.entityPicker.description': 'An entity from the catalog'; readonly 'fields.entityTagsPicker.title': 'Tags'; readonly 'fields.entityTagsPicker.description': "Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters"; + readonly 'fields.multiEntityPicker.title': 'Entity'; + readonly 'fields.multiEntityPicker.description': 'An entity from the catalog'; readonly 'fields.myGroupsPicker.title': 'Entity'; readonly 'fields.myGroupsPicker.description': 'An entity from the catalog'; readonly 'fields.ownedEntityPicker.title': 'Entity'; @@ -323,6 +375,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'; @@ -385,6 +438,7 @@ export const scaffolderTranslationRef: TranslationRef< readonly 'templatingExtensions.content.functions.schema.arguments': 'Arguments'; readonly 'templatingExtensions.content.functions.examples': 'Examples'; readonly 'templatingExtensions.content.functions.notAvailable': 'There are no global template functions defined.'; + readonly 'templatingExtensions.content.functions.metadataAbsent': 'Function metadata unavailable'; readonly 'templatingExtensions.title': 'Templating Extensions'; readonly 'templatingExtensions.subtitle': 'This is the collection of available templating extensions'; readonly 'templatingExtensions.pageTitle': 'Templating Extensions'; diff --git a/plugins/scaffolder/report.api.md b/plugins/scaffolder/report.api.md index b929d7be5b..fbb73f6225 100644 --- a/plugins/scaffolder/report.api.md +++ b/plugins/scaffolder/report.api.md @@ -11,47 +11,43 @@ import { createScaffolderFieldExtension as createScaffolderFieldExtension_2 } fr import { createScaffolderLayout as createScaffolderLayout_2 } from '@backstage/plugin-scaffolder-react'; import { CustomFieldExtensionSchema as CustomFieldExtensionSchema_2 } from '@backstage/plugin-scaffolder-react'; import { CustomFieldValidator as CustomFieldValidator_2 } from '@backstage/plugin-scaffolder-react'; -import { DiscoveryApi } from '@backstage/core-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; -import { FetchApi } from '@backstage/core-plugin-api'; import { FieldExtensionComponent as FieldExtensionComponent_2 } from '@backstage/plugin-scaffolder-react'; import { FieldExtensionComponentProps as FieldExtensionComponentProps_2 } from '@backstage/plugin-scaffolder-react'; import { FieldExtensionOptions as FieldExtensionOptions_2 } from '@backstage/plugin-scaffolder-react'; import { FieldSchema as FieldSchema_2 } from '@backstage/plugin-scaffolder-react'; import { FieldValidation } from '@rjsf/utils'; import { FormProps } from '@backstage/plugin-scaffolder-react'; -import { IdentityApi } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { LayoutOptions as LayoutOptions_2 } from '@backstage/plugin-scaffolder-react'; import { LayoutTemplate as LayoutTemplate_2 } from '@backstage/plugin-scaffolder-react'; -import { ListActionsResponse as ListActionsResponse_2 } from '@backstage/plugin-scaffolder-react'; -import { ListTemplatingExtensionsResponse } from '@backstage/plugin-scaffolder-react'; -import { LogEvent as LogEvent_2 } from '@backstage/plugin-scaffolder-react'; -import { Observable } from '@backstage/types'; +import { ListActionsResponse as ListActionsResponse_2 } from '@backstage/plugin-scaffolder-common'; +import { LogEvent as LogEvent_2 } from '@backstage/plugin-scaffolder-common'; import { PathParams } from '@backstage/core-plugin-api'; import { PropsWithChildren } from 'react'; import { ReactNode } from 'react'; import { ReviewStepProps } from '@backstage/plugin-scaffolder-react'; import { RouteRef } from '@backstage/core-plugin-api'; -import { ScaffolderApi as ScaffolderApi_2 } from '@backstage/plugin-scaffolder-react'; -import { ScaffolderDryRunOptions as ScaffolderDryRunOptions_2 } from '@backstage/plugin-scaffolder-react'; -import { ScaffolderDryRunResponse as ScaffolderDryRunResponse_2 } from '@backstage/plugin-scaffolder-react'; -import { ScaffolderGetIntegrationsListOptions as ScaffolderGetIntegrationsListOptions_2 } from '@backstage/plugin-scaffolder-react'; -import { ScaffolderGetIntegrationsListResponse as ScaffolderGetIntegrationsListResponse_2 } from '@backstage/plugin-scaffolder-react'; -import { ScaffolderOutputLink } from '@backstage/plugin-scaffolder-react'; -import { ScaffolderScaffoldOptions as ScaffolderScaffoldOptions_2 } from '@backstage/plugin-scaffolder-react'; -import { ScaffolderScaffoldResponse as ScaffolderScaffoldResponse_2 } from '@backstage/plugin-scaffolder-react'; -import { ScaffolderStreamLogsOptions as ScaffolderStreamLogsOptions_2 } from '@backstage/plugin-scaffolder-react'; -import { ScaffolderTask as ScaffolderTask_2 } from '@backstage/plugin-scaffolder-react'; +import { ScaffolderApi as ScaffolderApi_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderClient as ScaffolderClient_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderDryRunOptions as ScaffolderDryRunOptions_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderDryRunResponse as ScaffolderDryRunResponse_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderGetIntegrationsListOptions as ScaffolderGetIntegrationsListOptions_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderGetIntegrationsListResponse as ScaffolderGetIntegrationsListResponse_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderOutputLink } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderScaffoldOptions as ScaffolderScaffoldOptions_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderScaffoldResponse as ScaffolderScaffoldResponse_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderStreamLogsOptions as ScaffolderStreamLogsOptions_2 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderTask as ScaffolderTask_2 } from '@backstage/plugin-scaffolder-common'; import { ScaffolderTaskOutput as ScaffolderTaskOutput_2 } from '@backstage/plugin-scaffolder-react'; -import { ScaffolderTaskStatus as ScaffolderTaskStatus_2 } from '@backstage/plugin-scaffolder-react'; +import { ScaffolderTaskOutput as ScaffolderTaskOutput_3 } from '@backstage/plugin-scaffolder-common'; +import { ScaffolderTaskStatus as ScaffolderTaskStatus_2 } from '@backstage/plugin-scaffolder-common'; import { ScaffolderUseTemplateSecrets as ScaffolderUseTemplateSecrets_2 } from '@backstage/plugin-scaffolder-react'; -import { ScmIntegrationRegistry } from '@backstage/integration'; import { SubRouteRef } from '@backstage/core-plugin-api'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react'; import { TemplateListPageProps } from '@backstage/plugin-scaffolder/alpha'; -import { TemplateParameterSchema as TemplateParameterSchema_2 } from '@backstage/plugin-scaffolder-react'; +import { TemplateParameterSchema as TemplateParameterSchema_2 } from '@backstage/plugin-scaffolder-common'; import { TemplateWizardPageProps } from '@backstage/plugin-scaffolder/alpha'; import { z } from 'zod'; @@ -71,7 +67,7 @@ export type CustomFieldValidator<TReturnFieldData> = // @public export const EntityNamePickerFieldExtension: FieldExtensionComponent_2< string, - {} + any >; // @public @@ -104,7 +100,7 @@ export const EntityPickerFieldExtension: FieldExtensionComponent_2< >; // @public (undocumented) -export const EntityPickerFieldSchema: FieldSchema< +export const EntityPickerFieldSchema: FieldSchema_2< string, { defaultKind?: string | undefined; @@ -133,8 +129,9 @@ export const EntityPickerFieldSchema: FieldSchema< >; // @public -export type EntityPickerUiOptions = - typeof EntityPickerFieldSchema.uiOptionsType; +export type EntityPickerUiOptions = NonNullable< + (typeof EntityPickerFieldSchema.TProps.uiSchema)['ui:options'] +>; // @public export const EntityTagsPickerFieldExtension: FieldExtensionComponent_2< @@ -147,7 +144,7 @@ export const EntityTagsPickerFieldExtension: FieldExtensionComponent_2< >; // @public (undocumented) -export const EntityTagsPickerFieldSchema: FieldSchema< +export const EntityTagsPickerFieldSchema: FieldSchema_2< string[], { helperText?: string | undefined; @@ -157,8 +154,9 @@ export const EntityTagsPickerFieldSchema: FieldSchema< >; // @public -export type EntityTagsPickerUiOptions = - typeof EntityTagsPickerFieldSchema.uiOptionsType; +export type EntityTagsPickerUiOptions = NonNullable< + (typeof EntityTagsPickerFieldSchema.TProps.uiSchema)['ui:options'] +>; // @public @deprecated (undocumented) export type FieldExtensionComponent<_TReturnValue, _TInputProps> = @@ -233,27 +231,19 @@ export const MultiEntityPickerFieldExtension: FieldExtensionComponent_2< // @public export const MyGroupsPickerFieldExtension: FieldExtensionComponent_2< string, - { - title?: string | undefined; - description?: string | undefined; - } + any >; // @public -export const MyGroupsPickerFieldSchema: FieldSchema< - string, - { - title?: string | undefined; - description?: string | undefined; - } ->; +export const MyGroupsPickerFieldSchema: FieldSchema_2<string, any>; // @public export const MyGroupsPickerSchema: CustomFieldExtensionSchema_2; // @public -export type MyGroupsPickerUiOptions = - typeof MyGroupsPickerFieldSchema.uiOptionsType; +export type MyGroupsPickerUiOptions = NonNullable< + (typeof MyGroupsPickerFieldSchema.TProps.uiSchema)['ui:options'] +>; // @public export const OwnedEntityPickerFieldExtension: FieldExtensionComponent_2< @@ -285,7 +275,7 @@ export const OwnedEntityPickerFieldExtension: FieldExtensionComponent_2< >; // @public (undocumented) -export const OwnedEntityPickerFieldSchema: FieldSchema< +export const OwnedEntityPickerFieldSchema: FieldSchema_2< string, { defaultKind?: string | undefined; @@ -314,8 +304,9 @@ export const OwnedEntityPickerFieldSchema: FieldSchema< >; // @public -export type OwnedEntityPickerUiOptions = - typeof OwnedEntityPickerFieldSchema.uiOptionsType; +export type OwnedEntityPickerUiOptions = NonNullable< + (typeof OwnedEntityPickerFieldSchema.TProps.uiSchema)['ui:options'] +>; // @public export const OwnerPickerFieldExtension: FieldExtensionComponent_2< @@ -346,7 +337,7 @@ export const OwnerPickerFieldExtension: FieldExtensionComponent_2< >; // @public (undocumented) -export const OwnerPickerFieldSchema: FieldSchema< +export const OwnerPickerFieldSchema: FieldSchema_2< string, { defaultNamespace?: string | false | undefined; @@ -374,7 +365,9 @@ export const OwnerPickerFieldSchema: FieldSchema< >; // @public -export type OwnerPickerUiOptions = typeof OwnerPickerFieldSchema.uiOptionsType; +export type OwnerPickerUiOptions = NonNullable< + (typeof OwnerPickerFieldSchema.TProps.uiSchema)['ui:options'] +>; // @public export const RepoBranchPickerFieldExtension: FieldExtensionComponent_2< @@ -462,8 +455,9 @@ export const RepoUrlPickerFieldSchema: FieldSchema_2< >; // @public @deprecated -export type RepoUrlPickerUiOptions = - typeof RepoUrlPickerFieldSchema.uiOptionsType; +export type RepoUrlPickerUiOptions = NonNullable< + (typeof RepoUrlPickerFieldSchema.TProps.uiSchema)['ui:options'] +>; export { ReviewStepProps }; @@ -507,70 +501,8 @@ export type ScaffolderApi = ScaffolderApi_2; // @public @deprecated (undocumented) export const scaffolderApiRef: ApiRef<ScaffolderApi_2>; -// @public -export class ScaffolderClient implements ScaffolderApi_2 { - constructor(options: { - discoveryApi: DiscoveryApi; - fetchApi: FetchApi; - identityApi?: IdentityApi; - scmIntegrationsApi: ScmIntegrationRegistry; - useLongPollingLogs?: boolean; - }); - // (undocumented) - autocomplete({ - token, - resource, - provider, - context, - }: { - token: string; - provider: string; - resource: string; - context?: Record<string, string>; - }): Promise<{ - results: { - title?: string; - id: string; - }[]; - }>; - // (undocumented) - cancelTask(taskId: string): Promise<void>; - // (undocumented) - dryRun( - options: ScaffolderDryRunOptions_2, - ): Promise<ScaffolderDryRunResponse_2>; - // (undocumented) - getIntegrationsList( - options: ScaffolderGetIntegrationsListOptions_2, - ): Promise<ScaffolderGetIntegrationsListResponse_2>; - // (undocumented) - getTask(taskId: string): Promise<ScaffolderTask_2>; - // (undocumented) - getTemplateParameterSchema( - templateRef: string, - ): Promise<TemplateParameterSchema_2>; - // (undocumented) - listActions(): Promise<ListActionsResponse_2>; - // (undocumented) - listTasks(options: { - filterByOwnership: 'owned' | 'all'; - limit?: number; - offset?: number; - }): Promise<{ - tasks: ScaffolderTask_2[]; - totalTasks?: number; - }>; - // (undocumented) - listTemplatingExtensions(): Promise<ListTemplatingExtensionsResponse>; - // (undocumented) - retry?(taskId: string): Promise<void>; - // (undocumented) - scaffold( - options: ScaffolderScaffoldOptions_2, - ): Promise<ScaffolderScaffoldResponse_2>; - // (undocumented) - streamLogs(options: ScaffolderStreamLogsOptions_2): Observable<LogEvent_2>; -} +// @public @deprecated +export class ScaffolderClient extends ScaffolderClient_2 {} // @public @deprecated (undocumented) export type ScaffolderDryRunOptions = ScaffolderDryRunOptions_2; @@ -646,7 +578,7 @@ export type ScaffolderStreamLogsOptions = ScaffolderStreamLogsOptions_2; export type ScaffolderTask = ScaffolderTask_2; // @public @deprecated (undocumented) -export type ScaffolderTaskOutput = ScaffolderTaskOutput_2; +export type ScaffolderTaskOutput = ScaffolderTaskOutput_3; // @public @deprecated (undocumented) export type ScaffolderTaskStatus = ScaffolderTaskStatus_2; diff --git a/plugins/scaffolder/src/alpha/api/FormDecoratorsApi.ts b/plugins/scaffolder/src/alpha/api/FormDecoratorsApi.ts index 41bf91dba0..63807f54ef 100644 --- a/plugins/scaffolder/src/alpha/api/FormDecoratorsApi.ts +++ b/plugins/scaffolder/src/alpha/api/FormDecoratorsApi.ts @@ -16,7 +16,6 @@ import { ApiBlueprint, - createApiFactory, createExtensionInput, } from '@backstage/frontend-plugin-api'; import { ScaffolderFormDecoratorsApi } from './types'; @@ -58,8 +57,8 @@ export const formDecoratorsApi = ApiBlueprint.makeWithOverrides({ e.get(FormDecoratorBlueprint.dataRefs.formDecoratorLoader), ); - return originalFactory({ - factory: createApiFactory({ + return originalFactory(defineParams => + defineParams({ api: formDecoratorsApiRef, deps: {}, factory: () => @@ -67,6 +66,6 @@ export const formDecoratorsApi = ApiBlueprint.makeWithOverrides({ decorators: formDecorators, }), }), - }); + ); }, }); diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx index 2657565a38..5cbe524d3e 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx @@ -118,7 +118,7 @@ async function downloadDirectoryContents( directoryContents: { path: string; base64Content: string; - executable: boolean; + executable?: boolean; }[], name: string, ) { diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditor.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditor.tsx index b339be18e3..f845f1ca8d 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditor.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditor.tsx @@ -34,6 +34,7 @@ import { TemplateEditorLayoutFiles, TemplateEditorLayoutPreview, TemplateEditorLayoutConsole, + TemplateEditorPanels, } from './TemplateEditorLayout'; import { TemplateEditorToolbar } from './TemplateEditorToolbar'; import { TemplateEditorToolbarFileMenu } from './TemplateEditorToolbarFileMenu'; @@ -88,17 +89,24 @@ export const TemplateEditor = (props: { <TemplateEditorLayoutBrowser> <TemplateEditorBrowser onClose={closeDirectory} /> </TemplateEditorLayoutBrowser> - <TemplateEditorLayoutFiles> - <TemplateEditorTextArea.DirectoryEditor errorText={errorText} /> - </TemplateEditorLayoutFiles> - <TemplateEditorLayoutPreview> - <TemplateEditorForm.DirectoryEditorDryRun - setErrorText={setErrorText} - fieldExtensions={fieldExtensions} - layouts={layouts} - formProps={formProps} - /> - </TemplateEditorLayoutPreview> + <TemplateEditorPanels + autoSaveId="template-editor" + files={ + <TemplateEditorLayoutFiles> + <TemplateEditorTextArea.DirectoryEditor errorText={errorText} /> + </TemplateEditorLayoutFiles> + } + preview={ + <TemplateEditorLayoutPreview> + <TemplateEditorForm.DirectoryEditorDryRun + setErrorText={setErrorText} + fieldExtensions={fieldExtensions} + layouts={layouts} + formProps={formProps} + /> + </TemplateEditorLayoutPreview> + } + /> <TemplateEditorLayoutConsole> <DryRunResults /> </TemplateEditorLayoutConsole> diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorLayout.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorLayout.tsx index 242befe2b4..8b72b96b20 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorLayout.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorLayout.tsx @@ -14,8 +14,11 @@ * limitations under the License. */ -import { PropsWithChildren } from 'react'; +import { PropsWithChildren, ReactNode } from 'react'; import { WithStyles, withStyles } from '@material-ui/core/styles'; +import { PanelGroup, Panel, PanelResizeHandle } from 'react-resizable-panels'; +import { useTheme } from '@material-ui/core/styles'; +import useMediaQuery from '@material-ui/core/useMediaQuery'; export const TemplateEditorLayout = withStyles( theme => ({ @@ -36,7 +39,7 @@ export const TemplateEditorLayout = withStyles( "browser editor preview" "results results results" `, - gridTemplateColumns: '1fr 3fr 2fr', + gridTemplateColumns: '1fr 5fr', gridTemplateRows: 'auto 1fr auto', }, }, @@ -73,12 +76,15 @@ export const TemplateEditorLayoutBrowser = withStyles( )); export const TemplateEditorLayoutFiles = withStyles( - { + theme => ({ root: { gridArea: 'editor', overflow: 'auto', + [theme.breakpoints.up('md')]: { + height: '100%', + }, }, - }, + }), { name: 'ScaffolderTemplateEditorLayoutFiles' }, )(({ children, classes }: PropsWithChildren<WithStyles>) => ( <section className={classes.root}>{children}</section> @@ -91,7 +97,7 @@ export const TemplateEditorLayoutPreview = withStyles( position: 'relative', backgroundColor: theme.palette.background.default, [theme.breakpoints.up('md')]: { - borderLeft: `1px solid ${theme.palette.divider}`, + height: '100%', }, }, scroll: { @@ -124,3 +130,50 @@ export const TemplateEditorLayoutConsole = withStyles( )(({ children, classes }: PropsWithChildren<WithStyles>) => ( <section className={classes.root}>{children}</section> )); + +export const TemplateEditorPanelResizeHandle = withStyles( + { + root: { + width: 8, + cursor: 'col-resize', + background: 'rgba(0,0,0,0.04)', + }, + }, + { name: 'ScaffolderTemplateEditorPanelResizeHandle' }, +)(({ classes }: { classes: WithStyles['classes'] }) => ( + <PanelResizeHandle className={classes.root} /> +)); + +export function TemplateEditorPanels({ + files, + preview, + autoSaveId = 'template-editor-panels', +}: { + files: ReactNode; + preview: ReactNode; + autoSaveId?: string; +}) { + const theme = useTheme(); + const isMdUp = useMediaQuery(theme.breakpoints.up('md')); + + if (isMdUp) { + return ( + <PanelGroup direction="horizontal" autoSaveId={autoSaveId}> + <Panel minSize={15} defaultSize={50}> + {files} + </Panel> + <TemplateEditorPanelResizeHandle /> + <Panel minSize={15} defaultSize={50}> + {preview} + </Panel> + </PanelGroup> + ); + } + // Stack as rows for small screens, just render children in a plain block + return ( + <> + {files} + {preview} + </> + ); +} diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorToolbarTemplatesMenu.test.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorToolbarTemplatesMenu.test.tsx index 879f225b4e..0f3237547a 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorToolbarTemplatesMenu.test.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorToolbarTemplatesMenu.test.tsx @@ -99,7 +99,6 @@ describe('TemplateEditorToolbarTemplatesMenu', () => { name: 'Publish', action: 'publish:github', input: { - allowedHosts: ['github.com'], description: 'This is ${{ parameters.component_id }}', repoUrl: '${{ parameters.repoUrl }}', }, @@ -205,7 +204,6 @@ describe('TemplateEditorToolbarTemplatesMenu', () => { name: 'Publish', action: 'publish:github', input: { - allowedHosts: ['github.com'], description: 'This is ${{ parameters.component_id }}', repoUrl: '${{ parameters.repoUrl }}', }, diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx index 0d31f2ebe2..cb40ff2766 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx @@ -39,6 +39,7 @@ import { TemplateEditorLayoutToolbar, TemplateEditorLayoutFiles, TemplateEditorLayoutPreview, + TemplateEditorPanels, } from './TemplateEditorLayout'; import { TemplateEditorToolbar } from './TemplateEditorToolbar'; import { TemplateEditorToolbarFileMenu } from './TemplateEditorToolbarFileMenu'; @@ -113,7 +114,7 @@ const useStyles = makeStyles( "textArea preview" `, gridTemplateRows: 'auto 1fr', - gridTemplateColumns: '1fr 1fr', + gridTemplateColumns: '1fr', }, }, files: { @@ -207,23 +208,30 @@ export const TemplateFormPreviewer = ({ /> </TemplateEditorToolbar> </TemplateEditorLayoutToolbar> - <TemplateEditorLayoutFiles classes={{ root: classes.files }}> - <TemplateEditorTextArea - content={templateYaml} - onUpdate={setTemplateYaml} - errorText={errorText} - /> - </TemplateEditorLayoutFiles> - <TemplateEditorLayoutPreview> - <TemplateEditorForm - content={templateYaml} - contentIsSpec - fieldExtensions={customFieldExtensions} - setErrorText={setErrorText} - layouts={layouts} - formProps={formProps} - /> - </TemplateEditorLayoutPreview> + <TemplateEditorPanels + autoSaveId="template-form-previewer" + files={ + <TemplateEditorLayoutFiles classes={{ root: classes.files }}> + <TemplateEditorTextArea + content={templateYaml} + onUpdate={setTemplateYaml} + errorText={errorText} + /> + </TemplateEditorLayoutFiles> + } + preview={ + <TemplateEditorLayoutPreview> + <TemplateEditorForm + content={templateYaml} + contentIsSpec + fieldExtensions={customFieldExtensions} + setErrorText={setErrorText} + layouts={layouts} + formProps={formProps} + /> + </TemplateEditorLayoutPreview> + } + /> </TemplateEditorLayout> ); }; diff --git a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx index d3a02931de..6836259dd8 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx @@ -21,7 +21,7 @@ import { renderInTestApp, TestApiRegistry, } from '@backstage/test-utils'; -import { act, fireEvent } from '@testing-library/react'; +import { fireEvent, waitFor } from '@testing-library/react'; import { ScaffolderApi, scaffolderApiRef, @@ -127,14 +127,10 @@ describe('TemplateWizardPage', () => { }); // Go to the final page - await act(async () => { - fireEvent.click(await findByRole('button', { name: 'Review' })); - }); + fireEvent.click(await findByRole('button', { name: 'Review' })); // Create the software - await act(async () => { - fireEvent.click(await findByRole('button', { name: 'Create' })); - }); + fireEvent.click(await findByRole('button', { name: 'Create' })); // The "Next Step" button should have fired an event expect(analyticsApi.captureEvent).toHaveBeenCalledWith( @@ -148,15 +144,20 @@ describe('TemplateWizardPage', () => { ); // And the "Create" button should have fired an event - expect(analyticsApi.captureEvent).toHaveBeenCalledWith( - expect.objectContaining({ - action: 'create', - subject: 'expected-name', - context: expect.objectContaining({ - entityRef: 'template:default/test', + await waitFor(() => + expect(analyticsApi.captureEvent).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'create', + subject: 'Task has been created', + attributes: { + templateSteps: 1, + }, + context: expect.objectContaining({ + entityRef: 'template:default/test', + }), + value: 120, }), - value: 120, - }), + ), ); }); diff --git a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPageContextMenu.tsx b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPageContextMenu.tsx index 9a912f265f..c15795de2c 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPageContextMenu.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPageContextMenu.tsx @@ -26,6 +26,7 @@ import MoreVert from '@material-ui/icons/MoreVert'; import { SyntheticEvent, useState } from 'react'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { scaffolderTranslationRef } from '../../../translation'; +import { Link } from '@backstage/core-components'; const useStyles = makeStyles(theme => ({ button: { @@ -82,7 +83,7 @@ export function TemplateWizardPageContextMenu( transformOrigin={{ vertical: 'top', horizontal: 'right' }} > <MenuList> - <MenuItem onClick={() => window.open(editUrl, '_blank')}> + <MenuItem component={Link} to={editUrl}> <ListItemIcon> <Edit fontSize="small" /> </ListItemIcon> diff --git a/plugins/scaffolder/src/alpha/extensions.tsx b/plugins/scaffolder/src/alpha/extensions.tsx index 7c82edc97f..803644d95a 100644 --- a/plugins/scaffolder/src/alpha/extensions.tsx +++ b/plugins/scaffolder/src/alpha/extensions.tsx @@ -22,7 +22,6 @@ import { NavItemBlueprint, PageBlueprint, ApiBlueprint, - createApiFactory, discoveryApiRef, fetchApiRef, identityApiRef, @@ -47,7 +46,7 @@ export const scaffolderPage = PageBlueprint.makeWithOverrides({ ); return originalFactory({ routeRef: convertLegacyRouteRef(rootRouteRef), - defaultPath: '/create', + path: '/create', loader: () => import('../components/Router/Router').then(m => compatWrapper( @@ -74,8 +73,8 @@ export const repoUrlPickerFormField = FormFieldBlueprint.make({ }); export const scaffolderApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: scaffolderApiRef, deps: { discoveryApi: discoveryApiRef, @@ -91,5 +90,4 @@ export const scaffolderApi = ApiBlueprint.make({ identityApi, }), }), - }, }); 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: <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/api.ts b/plugins/scaffolder/src/api.ts index 4fb3627917..d880521cf2 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -14,389 +14,12 @@ * limitations under the License. */ -import { parseEntityRef } from '@backstage/catalog-model'; -import { - DiscoveryApi, - FetchApi, - IdentityApi, -} from '@backstage/core-plugin-api'; -import { ResponseError } from '@backstage/errors'; -import { ScmIntegrationRegistry } from '@backstage/integration'; -import { - ListActionsResponse, - ListTemplatingExtensionsResponse, - LogEvent, - ScaffolderApi, - ScaffolderDryRunOptions, - ScaffolderDryRunResponse, - ScaffolderGetIntegrationsListOptions, - ScaffolderGetIntegrationsListResponse, - ScaffolderScaffoldOptions, - ScaffolderScaffoldResponse, - ScaffolderStreamLogsOptions, - ScaffolderTask, - TemplateParameterSchema, -} from '@backstage/plugin-scaffolder-react'; -import { Observable } from '@backstage/types'; -import { - EventSourceMessage, - fetchEventSource, -} from '@microsoft/fetch-event-source'; -import { default as qs, default as queryString } from 'qs'; -import ObservableImpl from 'zen-observable'; +import { ScaffolderClient as _ScaffolderClient } from '@backstage/plugin-scaffolder-common'; /** * An API to interact with the scaffolder backend. * * @public + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderClient} instead as this has now been moved. */ -export class ScaffolderClient implements ScaffolderApi { - private readonly discoveryApi: DiscoveryApi; - private readonly scmIntegrationsApi: ScmIntegrationRegistry; - private readonly fetchApi: FetchApi; - private readonly identityApi?: IdentityApi; - private readonly useLongPollingLogs: boolean; - - constructor(options: { - discoveryApi: DiscoveryApi; - fetchApi: FetchApi; - identityApi?: IdentityApi; - scmIntegrationsApi: ScmIntegrationRegistry; - useLongPollingLogs?: boolean; - }) { - this.discoveryApi = options.discoveryApi; - this.fetchApi = options.fetchApi ?? { fetch }; - this.scmIntegrationsApi = options.scmIntegrationsApi; - this.useLongPollingLogs = options.useLongPollingLogs ?? false; - this.identityApi = options.identityApi; - } - - async listTasks(options: { - filterByOwnership: 'owned' | 'all'; - limit?: number; - offset?: number; - }): Promise<{ tasks: ScaffolderTask[]; totalTasks?: number }> { - if (!this.identityApi) { - throw new Error( - 'IdentityApi is not available in the ScaffolderClient, please pass through the IdentityApi to the ScaffolderClient constructor in order to use the listTasks method', - ); - } - const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); - const { userEntityRef } = await this.identityApi.getBackstageIdentity(); - - const query = queryString.stringify({ - createdBy: - options.filterByOwnership === 'owned' ? userEntityRef : undefined, - limit: options.limit, - offset: options.offset, - }); - - const response = await this.fetchApi.fetch(`${baseUrl}/v2/tasks?${query}`); - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - - return await response.json(); - } - - async getIntegrationsList( - options: ScaffolderGetIntegrationsListOptions, - ): Promise<ScaffolderGetIntegrationsListResponse> { - const integrations = [ - ...this.scmIntegrationsApi.azure.list(), - ...this.scmIntegrationsApi.bitbucket - .list() - .filter( - item => - !this.scmIntegrationsApi.bitbucketCloud.byHost(item.config.host) && - !this.scmIntegrationsApi.bitbucketServer.byHost(item.config.host), - ), - ...this.scmIntegrationsApi.bitbucketCloud.list(), - ...this.scmIntegrationsApi.bitbucketServer.list(), - ...this.scmIntegrationsApi.gerrit.list(), - ...this.scmIntegrationsApi.gitea.list(), - ...this.scmIntegrationsApi.github.list(), - ...this.scmIntegrationsApi.gitlab.list(), - ] - .map(c => ({ type: c.type, title: c.title, host: c.config.host })) - .filter(c => options.allowedHosts.includes(c.host)); - - return { - integrations, - }; - } - - async getTemplateParameterSchema( - templateRef: string, - ): Promise<TemplateParameterSchema> { - const { namespace, kind, name } = parseEntityRef(templateRef, { - defaultKind: 'template', - }); - - const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); - const templatePath = [namespace, kind, name] - .map(s => encodeURIComponent(s)) - .join('/'); - - const url = `${baseUrl}/v2/templates/${templatePath}/parameter-schema`; - - const response = await this.fetchApi.fetch(url); - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - - const schema: TemplateParameterSchema = await response.json(); - return schema; - } - - async scaffold( - options: ScaffolderScaffoldOptions, - ): Promise<ScaffolderScaffoldResponse> { - const { templateRef, values, secrets = {} } = options; - const url = `${await this.discoveryApi.getBaseUrl('scaffolder')}/v2/tasks`; - const response = await this.fetchApi.fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - templateRef, - values: { ...values }, - secrets, - }), - }); - - if (response.status !== 201) { - const status = `${response.status} ${response.statusText}`; - const body = await response.text(); - throw new Error(`Backend request failed, ${status} ${body.trim()}`); - } - - const { id } = (await response.json()) as { id: string }; - return { taskId: id }; - } - - async getTask(taskId: string): Promise<ScaffolderTask> { - const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); - const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}`; - - const response = await this.fetchApi.fetch(url); - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - - return await response.json(); - } - - streamLogs(options: ScaffolderStreamLogsOptions): Observable<LogEvent> { - if (this.useLongPollingLogs) { - return this.streamLogsPolling(options); - } - - return this.streamLogsEventStream(options); - } - - async dryRun( - options: ScaffolderDryRunOptions, - ): Promise<ScaffolderDryRunResponse> { - const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); - const response = await this.fetchApi.fetch(`${baseUrl}/v2/dry-run`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - template: options.template, - values: options.values, - secrets: options.secrets, - directoryContents: options.directoryContents, - }), - }); - - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - - return response.json(); - } - - private streamLogsEventStream({ - isTaskRecoverable, - taskId, - after, - }: ScaffolderStreamLogsOptions): Observable<LogEvent> { - return new ObservableImpl(subscriber => { - const params = new URLSearchParams(); - if (after !== undefined) { - params.set('after', String(Number(after))); - } - - this.discoveryApi.getBaseUrl('scaffolder').then( - baseUrl => { - const url = `${baseUrl}/v2/tasks/${encodeURIComponent( - taskId, - )}/eventstream`; - - const processEvent = (event: any) => { - if (event.data) { - try { - subscriber.next(JSON.parse(event.data)); - } catch (ex) { - subscriber.error(ex); - } - } - }; - - const ctrl = new AbortController(); - void fetchEventSource(url, { - fetch: this.fetchApi.fetch, - signal: ctrl.signal, - onmessage(e: EventSourceMessage) { - if (e.event === 'log') { - processEvent(e); - return; - } else if (e.event === 'completion' && !isTaskRecoverable) { - processEvent(e); - subscriber.complete(); - ctrl.abort(); - return; - } - processEvent(e); - }, - onerror(err) { - subscriber.error(err); - }, - }); - }, - error => { - subscriber.error(error); - }, - ); - }); - } - - private streamLogsPolling({ - taskId, - after: inputAfter, - }: { - taskId: string; - after?: number; - }): Observable<LogEvent> { - let after = inputAfter; - - return new ObservableImpl(subscriber => { - this.discoveryApi.getBaseUrl('scaffolder').then(async baseUrl => { - while (!subscriber.closed) { - const url = `${baseUrl}/v2/tasks/${encodeURIComponent( - taskId, - )}/events?${qs.stringify({ after })}`; - const response = await this.fetchApi.fetch(url); - - if (!response.ok) { - // wait for one second to not run into an - await new Promise(resolve => setTimeout(resolve, 1000)); - continue; - } - - const logs = (await response.json()) as LogEvent[]; - - for (const event of logs) { - after = Number(event.id); - - subscriber.next(event); - - if (event.type === 'completion') { - subscriber.complete(); - return; - } - } - } - }); - }); - } - - async listActions(): Promise<ListActionsResponse> { - const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); - const response = await this.fetchApi.fetch(`${baseUrl}/v2/actions`); - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - - return await response.json(); - } - - async listTemplatingExtensions(): Promise<ListTemplatingExtensionsResponse> { - const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); - const response = await this.fetchApi.fetch( - `${baseUrl}/v2/templating-extensions`, - ); - if (!response.ok) { - throw ResponseError.fromResponse(response); - } - return response.json(); - } - - async cancelTask(taskId: string): Promise<void> { - const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); - const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}/cancel`; - - const response = await this.fetchApi.fetch(url, { - method: 'POST', - }); - - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - - return await response.json(); - } - - async retry?(taskId: string): Promise<void> { - const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); - const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}/retry`; - - const response = await this.fetchApi.fetch(url, { - method: 'POST', - }); - - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - - return await response.json(); - } - - async autocomplete({ - token, - resource, - provider, - context, - }: { - token: string; - provider: string; - resource: string; - context?: Record<string, string>; - }): Promise<{ results: { title?: string; id: string }[] }> { - const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); - - const url = `${baseUrl}/v2/autocomplete/${provider}/${resource}`; - - const response = await this.fetchApi.fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - token, - context: context ?? {}, - }), - }); - - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - - const { results } = await response.json(); - return { results }; - } -} +export class ScaffolderClient extends _ScaffolderClient {} diff --git a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx index 6d48cec125..d6b5b7116e 100644 --- a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx +++ b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx @@ -91,14 +91,22 @@ const ListTaskPageContent = (props: MyTaskPageProps) => { if (error) { return ( - <> - <ErrorPanel error={error} /> - <EmptyState - missing="info" - title={t('listTaskPage.content.emptyState.title')} - description={t('listTaskPage.content.emptyState.description')} - /> - </> + <CatalogFilterLayout> + <CatalogFilterLayout.Filters> + <OwnerListPicker + filter={ownerFilter} + onSelectOwner={id => setOwnerFilter(id)} + /> + </CatalogFilterLayout.Filters> + <CatalogFilterLayout.Content> + <ErrorPanel error={error} /> + <EmptyState + missing="info" + title={t('listTaskPage.content.emptyState.title')} + description={t('listTaskPage.content.emptyState.description')} + /> + </CatalogFilterLayout.Content> + </CatalogFilterLayout> ); } diff --git a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx index 704d39a8fd..2df1fc4ddb 100644 --- a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx @@ -21,7 +21,6 @@ import MenuItem from '@material-ui/core/MenuItem'; import MenuList from '@material-ui/core/MenuList'; import Popover from '@material-ui/core/Popover'; import { makeStyles, Theme, useTheme } from '@material-ui/core/styles'; -import { useAsync } from '@react-hookz/web'; import Cancel from '@material-ui/icons/Cancel'; import Repeat from '@material-ui/icons/Repeat'; import Replay from '@material-ui/icons/Replay'; @@ -29,11 +28,8 @@ import Toc from '@material-ui/icons/Toc'; import ControlPointIcon from '@material-ui/icons/ControlPoint'; import MoreVert from '@material-ui/icons/MoreVert'; import { SyntheticEvent, useState } from 'react'; -import { useAnalytics, useApi } from '@backstage/core-plugin-api'; -import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; import { usePermission } from '@backstage/plugin-permission-react'; import { - taskCancelPermission, taskReadPermission, taskCreatePermission, } from '@backstage/plugin-scaffolder-common/alpha'; @@ -51,6 +47,8 @@ type ContextMenuProps = { onToggleLogs?: (state: boolean) => void; onToggleButtonBar?: (state: boolean) => void; taskId?: string; + isCancelButtonDisabled: boolean; + onCancel: () => void; }; const useStyles = makeStyles<Theme, { fontColor: string }>(() => ({ @@ -75,24 +73,12 @@ export const ContextMenu = (props: ContextMenuProps) => { const { getPageTheme } = useTheme(); const pageTheme = getPageTheme({ themeId: 'website' }); const classes = useStyles({ fontColor: pageTheme.fontColor }); - const scaffolderApi = useApi(scaffolderApiRef); - const analytics = useAnalytics(); const [anchorEl, setAnchorEl] = useState<HTMLButtonElement>(); const { t } = useTranslationRef(scaffolderTranslationRef); - const [{ status: cancelStatus }, { execute: cancel }] = useAsync(async () => { - if (taskId) { - analytics.captureEvent('cancelled', 'Template has been cancelled'); - await scaffolderApi.cancelTask(taskId); - } - }); - - const { allowed: canCancelTask } = usePermission({ - permission: taskCancelPermission, - }); - const { allowed: canReadTask } = usePermission({ permission: taskReadPermission, + resourceRef: taskId, }); const { allowed: canCreateTask } = usePermission({ @@ -171,12 +157,8 @@ export const ContextMenu = (props: ContextMenuProps) => { </MenuItem> )} <MenuItem - onClick={cancel} - disabled={ - !cancelEnabled || - cancelStatus !== 'not-executed' || - !canCancelTask - } + onClick={props.onCancel} + disabled={props.isCancelButtonDisabled} data-testid="cancel-task" > <ListItemIcon> diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx index a576b1a9bc..5e44d5cf43 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx @@ -161,20 +161,43 @@ describe('OngoingTask', () => { await expect(rendered.findByText('Hide Logs')).resolves.toBeInTheDocument(); }); - it('should have cancel and start over buttons be disabled without the proper permissions', async () => { + it('should have cancel button be disabled when user has read permission but lacks cancel permission', async () => { const permissionApi = mockApis.permission({ - authorize: AuthorizeResult.DENY, + authorize: request => { + if (request.permission.name === 'scaffolder.task.cancel') { + return AuthorizeResult.DENY; + } + return AuthorizeResult.ALLOW; + }, }); const rendered = await render(permissionApi); const { getByTestId } = rendered; expect(getByTestId('cancel-button')).toHaveClass('Mui-disabled'); - expect(getByTestId('start-over-button')).toHaveClass('Mui-disabled'); await act(async () => { fireEvent.click(getByTestId('menu-button')); }); expect(getByTestId('cancel-task')).toHaveClass('Mui-disabled'); - expect(getByTestId('start-over-task')).toHaveClass('Mui-disabled'); + }); + + it('should have start over button be disabled when user has read permission but lacks create permission', async () => { + const permissionApi = mockApis.permission({ + authorize: request => { + if (request.permission.name === 'scaffolder.task.create') { + return AuthorizeResult.DENY; + } + return AuthorizeResult.ALLOW; + }, + }); + const rendered = await render(permissionApi); + + const { getByTestId } = rendered; + expect(getByTestId('start-over-button')).toHaveClass('Mui-disabled'); + + await act(async () => { + fireEvent.click(getByTestId('menu-button')); + }); + expect(getByTestId('start-over-button')).toHaveClass('Mui-disabled'); }); }); diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx index b718a257b6..bc0d5fe1aa 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx @@ -32,7 +32,12 @@ import { useTaskEventStream, } from '@backstage/plugin-scaffolder-react'; import { selectedTemplateRouteRef } from '../../routes'; -import { useAnalytics, useApi, useRouteRef } from '@backstage/core-plugin-api'; +import { + AnalyticsContext, + useAnalytics, + useApi, + useRouteRef, +} from '@backstage/core-plugin-api'; import qs from 'qs'; import { ContextMenu } from './ContextMenu'; import { @@ -51,6 +56,7 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { scaffolderTranslationRef } from '../../translation'; import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; import { default as reactUseAsync } from 'react-use/esm/useAsync'; +import { stringifyEntityRef } from '@backstage/catalog-model'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -82,6 +88,36 @@ export const OngoingTask = (props: { }>; }) => { // todo(blam): check that task Id actually exists, and that it's valid. otherwise redirect to something more useful. + const { taskId } = useParams(); + const taskStream = useTaskEventStream(taskId!); + const { namespace, name } = + taskStream.task?.spec.templateInfo?.entity?.metadata ?? {}; + + return ( + <AnalyticsContext + attributes={{ + entityRef: + name && + stringifyEntityRef({ + kind: 'template', + namespace, + name, + }), + taskId, + }} + > + <Page themeId="website"> + <OngoingTaskContent {...props} /> + </Page> + </AnalyticsContext> + ); +}; + +function OngoingTaskContent(props: { + TemplateOutputsComponent?: ComponentType<{ + output?: ScaffolderTaskOutput; + }>; +}) { const { taskId } = useParams(); const templateRouteRef = useRouteRef(selectedTemplateRouteRef); const navigate = useNavigate(); @@ -103,13 +139,14 @@ export const OngoingTask = (props: { const [logsVisible, setLogVisibleState] = useState(false); const [buttonBarVisible, setButtonBarVisibleState] = useState(true); - // Used dummy string value for `resourceRef` since `allowed` field will always return `false` if `resourceRef` is `undefined` const { allowed: canCancelTask } = usePermission({ permission: taskCancelPermission, + resourceRef: taskId, }); const { allowed: canReadTask } = usePermission({ permission: taskReadPermission, + resourceRef: taskId, }); const { allowed: canCreateTask } = usePermission({ @@ -183,7 +220,7 @@ export const OngoingTask = (props: { templateRouteRef, ]); - const [{ status: _ }, { execute: triggerRetry }] = useAsync(async () => { + const [, { execute: triggerRetry }] = useAsync(async () => { if (taskId) { analytics.captureEvent('retried', 'Template has been retried'); await scaffolderApi.retry?.(taskId); @@ -202,9 +239,11 @@ export const OngoingTask = (props: { const Outputs = props.TemplateOutputsComponent ?? DefaultTemplateOutputs; const cancelEnabled = !(taskStream.cancelled || taskStream.completed); + const isCancelButtonDisabled = + !cancelEnabled || cancelStatus !== 'not-executed' || !canCancelTask; return ( - <Page themeId="website"> + <> <Header pageTitleOverride={ presentation @@ -232,6 +271,8 @@ export const OngoingTask = (props: { onToggleLogs={setLogVisibleState} onToggleButtonBar={setButtonBarVisibleState} taskId={taskId} + onCancel={triggerCancel} + isCancelButtonDisabled={isCancelButtonDisabled} /> </Header> <Content className={classes.contentWrapper}> @@ -316,6 +357,6 @@ export const OngoingTask = (props: { </Paper> ) : null} </Content> - </Page> + </> ); -}; +} 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( + <LocalRenderSchema strategy="properties" {...{ schema }} />, + ); + 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..2d4ebd6fca 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<string, JSONSchema7Definition[]> => { +const getSubschemas = (schema: JSONSchema7Definition): subSchemasType => { if (typeof schema === 'boolean') { return {}; } const base: Omit<JSONSchema7, keyof subSchemasType> = {}; - 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 [ @@ -95,6 +102,9 @@ const getSubschemas = ( base[key as Exclude<keyof JSONSchema7, keyof subSchemasType>] = value; } } + if (!(base?.type === 'object' || 'properties' in base)) { + return subschemas; + } return Object.fromEntries( Object.entries(subschemas).map(([key, sub]) => { const mergedSubschema = sub.map(alt => { @@ -224,7 +234,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 +255,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) ? ( <Chip data-testid={`expand_${id}`} label={type} @@ -344,18 +357,19 @@ export const RenderSchema = ({ const columnStyles = useColumnStyles(); const result = (() => { 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') { - elements = [{ schema }]; - columns = [typeColumn]; - if (schema.description) { - columns.unshift(descriptionColumn); - } - if (schema.title) { - columns.unshift(titleColumn); + if ('type' in schema || !Object.keys(subschemas).length) { + elements = [{ schema }]; + columns = [typeColumn]; + if (schema.description) { + columns.unshift(descriptionColumn); + } + if (schema.title) { + columns.unshift(titleColumn); + } } } else if (schema.properties) { columns = [nameColumn, titleColumn, descriptionColumn, typeColumn]; @@ -393,90 +407,100 @@ export const RenderSchema = ({ {elements.map(el => { const id = generateId(el, context); const info = inspectSchema(el.schema); - return ( - <Fragment key={id}> - <TableRow data-testid={`${strategy}-row_${id}`}> - {columns!.map(col => ( - <TableCell - key={col.key} - className={ - columnStyles[col.className ?? 'standard'] + const rows = [ + <TableRow data-testid={`${strategy}-row_${id}`}> + {columns!.map(col => ( + <TableCell + key={col.key} + className={ + columnStyles[col.className ?? 'standard'] + } + > + {col.render(el, context)} + </TableCell> + ))} + </TableRow>, + ]; + if ( + typeof el.schema !== 'boolean' && + (info.canSubschema || info.hasEnum) + ) { + let details: ReactElement = ( + <Box data-testid={`expansion_${id}`} sx={{ margin: 1 }}> + {info.canSubschema && ( + <RenderSchema + strategy="properties" + context={{ + ...context, + parentId: id, + parent: context, + }} + schema={ + el.schema.type === 'array' + ? (el.schema.items as JSONSchema7 | undefined) + : el.schema } - > - {col.render(el, context)} - </TableCell> - ))} - </TableRow> - {typeof el.schema !== 'boolean' && - (info.canSubschema || info.hasEnum) && ( - <TableRow> - <TableCell - style={{ paddingBottom: 0, paddingTop: 0 }} - colSpan={columns!.length} - > - <Collapse - in={isExpanded[id]} - timeout="auto" - unmountOnExit - > - <Box - data-testid={`expansion_${id}`} - sx={{ margin: 1 }} - > - {info.canSubschema && ( - <RenderSchema - strategy="properties" - context={{ - ...context, - parentId: id, - parent: context, - }} - schema={ - el.schema.type === 'array' - ? (el.schema.items as - | JSONSchema7 - | undefined) - : el.schema - } - /> - )} - {info.hasEnum && ( - <> - {cloneElement( - context.headings[0], - {}, - 'Valid values:', - )} - <RenderEnum - data-testid={`enum_${id}`} - e={enumFrom(el.schema)!} - classes={context.classes} - /> - </> - )} - </Box> - </Collapse> - </TableCell> - </TableRow> + /> )} - </Fragment> - ); + {info.hasEnum && ( + <> + {cloneElement( + context.headings[0], + {}, + 'Valid values:', + )} + <RenderEnum + data-testid={`enum_${id}`} + e={enumFrom(el.schema)!} + classes={context.classes} + /> + </> + )} + </Box> + ); + if (getTypes(el.schema)) { + details = ( + <Collapse + in={isExpanded[id]} + timeout="auto" + unmountOnExit + > + {details} + </Collapse> + ); + } + rows.push( + <TableRow> + <TableCell + style={{ paddingBottom: 0, paddingTop: 0 }} + colSpan={columns!.length} + > + {details} + </TableCell> + </TableRow>, + ); + } + return <Fragment key={id}>{rows}</Fragment>; })} </TableBody> </Table> </TableContainer> )} - {Object.keys(subschemas).map(sk => ( + {(Object.keys(subschemas) as Array<keyof subSchemasType>).map(sk => ( <Fragment key={sk}> {cloneElement(context.headings[0], {}, sk)} - {subschemas[sk].map((sub, index) => ( + {subschemas[sk]!.map((sub, index) => ( <RenderSchema key={index} + strategy={ + typeof sub !== 'boolean' && 'properties' in sub + ? strategy + : 'root' + } {...{ - strategy, context: { ...context, - parentId: `${context.parentId}_sub${index}`, + parentId: `${context.parentId}_${sk}${index}`, }, schema: sub, }} diff --git a/plugins/scaffolder/src/components/Router/Router.tsx b/plugins/scaffolder/src/components/Router/Router.tsx index 91567c1ddd..a57811a520 100644 --- a/plugins/scaffolder/src/components/Router/Router.tsx +++ b/plugins/scaffolder/src/components/Router/Router.tsx @@ -60,14 +60,12 @@ import { CustomFieldsPage, } from '../../alpha/components/TemplateEditorPage'; import { RequirePermission } from '@backstage/plugin-permission-react'; -import { - taskReadPermission, - templateManagementPermission, -} from '@backstage/plugin-scaffolder-common/alpha'; +import { templateManagementPermission } from '@backstage/plugin-scaffolder-common/alpha'; import { useApp } from '@backstage/core-plugin-api'; -import { FormField, OpaqueFormField } from '@internal/scaffolder'; +import { OpaqueFormField } from '@internal/scaffolder'; import { useAsync, useMountEffect } from '@react-hookz/web'; import { TemplatingExtensionsPage } from '../TemplatingExtensionsPage'; +import { FormField } from '@backstage/plugin-scaffolder-react/alpha'; /** * The Props for the Scaffolder Router @@ -182,11 +180,9 @@ export const InternalRouter = ( <Route path={scaffolderTaskRouteRef.path} element={ - <RequirePermission permission={taskReadPermission}> - <TaskPageComponent - TemplateOutputsComponent={TemplateOutputsComponent} - /> - </RequirePermission> + <TaskPageComponent + TemplateOutputsComponent={TemplateOutputsComponent} + /> } /> <Route @@ -230,11 +226,7 @@ export const InternalRouter = ( /> <Route path={scaffolderListTaskRouteRef.path} - element={ - <RequirePermission permission={taskReadPermission}> - <ListTasksPage contextMenu={props.contextMenu} /> - </RequirePermission> - } + element={<ListTasksPage contextMenu={props.contextMenu} />} /> <Route path={editorRouteRef.path} diff --git a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateFilters.tsx b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateFilters.tsx index e0c80c27cf..05f3043dd1 100644 --- a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateFilters.tsx +++ b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateFilters.tsx @@ -26,7 +26,7 @@ import { ClassNameMap } from '@material-ui/core/styles/withStyles'; import Typography from '@material-ui/core/Typography'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import classNames from 'classnames'; -import React, { ReactElement, useState } from 'react'; +import { cloneElement, Fragment, ReactElement, useState } from 'react'; import { scaffolderTranslationRef } from '../../translation'; import { Expanded, RenderSchema, SchemaRenderContext } from '../RenderSchema'; import { ScaffolderUsageExamplesTable } from '../ScaffolderUsageExamplesTable'; @@ -60,7 +60,7 @@ const FilterDetailContent = ({ headings: [<Typography variant="h6" component="h4" />], }; return ( - <React.Fragment key={`${name}.detail`}> + <Fragment key={`${name}.detail`}> {filter.description && <MarkdownContent content={filter.description} />} <Box pb={2}> <Typography variant="h5" component="h3"> @@ -84,7 +84,7 @@ const FilterDetailContent = ({ const [argSchema, required] = inspectFunctionArgSchema(arg); return ( - <React.Fragment key={i}> + <Fragment key={i}> <div className={classNames({ [classes.argRequired]: required })} > @@ -101,7 +101,7 @@ const FilterDetailContent = ({ }} schema={argSchema} /> - </React.Fragment> + </Fragment> ); })} </Box> @@ -133,7 +133,7 @@ const FilterDetailContent = ({ </AccordionDetails> </Accordion> )} - </React.Fragment> + </Fragment> ); }; @@ -178,7 +178,7 @@ export const TemplateFilters = ({ > {name} </Typography> - {React.cloneElement(baseLink, { + {cloneElement(baseLink, { to: `${baseLink.props.to}#${fragment}`, })} <FilterDetailContent {...{ t, classes, name, filter }} /> diff --git a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateGlobals.tsx b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateGlobals.tsx index 329f7b9742..249e2a581d 100644 --- a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateGlobals.tsx +++ b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateGlobals.tsx @@ -26,7 +26,7 @@ import { ClassNameMap } from '@material-ui/core/styles/withStyles'; import Typography from '@material-ui/core/Typography'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import classNames from 'classnames'; -import React, { ReactElement, useState } from 'react'; +import { cloneElement, Fragment, ReactElement, useState } from 'react'; import { scaffolderTranslationRef } from '../../translation'; import { Expanded, RenderSchema, SchemaRenderContext } from '../RenderSchema'; import { ScaffolderUsageExamplesTable } from '../ScaffolderUsageExamplesTable'; @@ -48,8 +48,11 @@ const FunctionDetailContent = ({ const expanded = useState<Expanded>({}); if (!Object.keys(fn).length) { return ( - <Typography style={{ fontStyle: 'italic' }}> - {t('templatingExtensions.content.functions.notAvailable')} + <Typography + style={{ fontStyle: 'italic' }} + data-testid={`${name}.metadataAbsent`} + > + {t('templatingExtensions.content.functions.metadataAbsent')} </Typography> ); } @@ -60,7 +63,7 @@ const FunctionDetailContent = ({ headings: [<Typography variant="h6" component="h4" />], }; return ( - <React.Fragment key={`${name}.detail`}> + <Fragment key={`${name}.detail`}> {fn.description && <MarkdownContent content={fn.description} />} {schema?.arguments?.length && ( <Box key={`${name}.args`} pb={2}> @@ -71,7 +74,7 @@ const FunctionDetailContent = ({ const [argSchema, required] = inspectFunctionArgSchema(arg); return ( - <React.Fragment key={i}> + <Fragment key={i}> <div className={classNames({ [classes.argRequired]: required })} > @@ -89,7 +92,7 @@ const FunctionDetailContent = ({ }} schema={argSchema} /> - </React.Fragment> + </Fragment> ); })} </Box> @@ -121,7 +124,7 @@ const FunctionDetailContent = ({ </AccordionDetails> </Accordion> )} - </React.Fragment> + </Fragment> ); }; @@ -166,7 +169,7 @@ export const TemplateGlobalFunctions = ({ > {name} </Typography> - {React.cloneElement(baseLink, { + {cloneElement(baseLink, { to: `${baseLink.props.to}#${fragment}`, })} <FunctionDetailContent {...{ classes, name, fn, t }} /> @@ -218,7 +221,7 @@ export const TemplateGlobalValues = ({ > {name} </Typography> - {React.cloneElement(baseLink, { + {cloneElement(baseLink, { to: `${baseLink.props.to}#${fragment}`, })} {gv.description && <MarkdownContent content={gv.description} />} diff --git a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplatingExtensionsPage.test.tsx b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplatingExtensionsPage.test.tsx index d3d0eb7370..2c0680b5f8 100644 --- a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplatingExtensionsPage.test.tsx +++ b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplatingExtensionsPage.test.tsx @@ -315,76 +315,112 @@ describe('TemplatingExtensionsPage', () => { }); }); describe('renders global', () => { - it('renders global functions', async () => { - listTemplatingExtensions.mockResolvedValue({ - ...emptyExtensions, - globals: { - ...emptyExtensions.globals, - functions: { - truthy: { - description: 'evaluate truthiness', - schema: { - arguments: [ - { - title: 'input', - }, - ], - output: { - type: 'boolean', - }, - }, - examples: [ - { - description: 'basic usage', - example: "truthy('foo')", - notes: 'yields `true`', - }, - ], + describe('renders global functions', () => { + it('without metadata', async () => { + listTemplatingExtensions.mockResolvedValue({ + ...emptyExtensions, + globals: { + ...emptyExtensions.globals, + functions: { + anything: {}, }, }, - }, + }); + const { findByTestId, getByRole } = await r(); + + fireEvent.click(within(getByRole('tablist')).getByText('Functions')); + + const functions = await findByTestId('functions'); + + const anything = within(functions).getByTestId('anything'); + const title = within(anything).getByText('anything'); + expect(title).toBeInTheDocument(); + expect(title.id).toBe('function_anything'); + + const link = within(anything).getByRole('link'); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute( + 'href', + expect.stringMatching(new RegExp(`#${title.id}$`)), + ); + expect( + within(anything).getByTestId('anything.metadataAbsent'), + ).toBeInTheDocument(); }); - const { findByTestId, getByRole } = await r(); + it('with metadata', async () => { + listTemplatingExtensions.mockResolvedValue({ + ...emptyExtensions, + globals: { + ...emptyExtensions.globals, + functions: { + truthy: { + description: 'evaluate truthiness', + schema: { + arguments: [ + { + title: 'input', + }, + ], + output: { + type: 'boolean', + }, + }, + examples: [ + { + description: 'basic usage', + example: "truthy('foo')", + notes: 'yields `true`', + }, + ], + }, + }, + }, + }); + const { findByTestId, getByRole } = await r(); - fireEvent.click(within(getByRole('tablist')).getByText('Functions')); + fireEvent.click(within(getByRole('tablist')).getByText('Functions')); - const functions = await findByTestId('functions'); + const functions = await findByTestId('functions'); - const truthy = within(functions).getByTestId('truthy'); - const title = within(truthy).getByText('truthy'); - expect(title).toBeInTheDocument(); - expect(title.id).toBe('function_truthy'); + const truthy = within(functions).getByTestId('truthy'); + const title = within(truthy).getByText('truthy'); + expect(title).toBeInTheDocument(); + expect(title.id).toBe('function_truthy'); - const link = within(truthy).getByRole('link'); - expect(link).toBeInTheDocument(); - expect(link).toHaveAttribute( - 'href', - expect.stringMatching(new RegExp(`#${title.id}$`)), - ); + const link = within(truthy).getByRole('link'); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute( + 'href', + expect.stringMatching(new RegExp(`#${title.id}$`)), + ); + expect( + within(truthy).queryByTestId('truthy.metadataAbsent'), + ).not.toBeInTheDocument(); - expect( - within(truthy).getByText('evaluate truthiness'), - ).toBeInTheDocument(); + expect( + within(truthy).getByText('evaluate truthiness'), + ).toBeInTheDocument(); - expect(within(truthy).getByText('[0]')).toBeInTheDocument(); - expect( - within(truthy).getByTestId('root_truthy.arg0'), - ).toBeInTheDocument(); - expect( - within(truthy).queryByTestId('root_truthy.arg1'), - ).not.toBeInTheDocument(); - expect( - within(truthy).getByTestId('root_truthy.output'), - ).toBeInTheDocument(); + expect(within(truthy).getByText('[0]')).toBeInTheDocument(); + expect( + within(truthy).getByTestId('root_truthy.arg0'), + ).toBeInTheDocument(); + expect( + within(truthy).queryByTestId('root_truthy.arg1'), + ).not.toBeInTheDocument(); + expect( + within(truthy).getByTestId('root_truthy.output'), + ).toBeInTheDocument(); - const x = within(truthy).getByTestId('examples'); - expect(x).toBeInTheDocument(); - const xd0 = within(x).getByTestId('example_desc0'); - expect(xd0).toBeInTheDocument(); - expect(xd0).toHaveTextContent(/basic usage\s*yields\s*true/); + const x = within(truthy).getByTestId('examples'); + expect(x).toBeInTheDocument(); + const xd0 = within(x).getByTestId('example_desc0'); + expect(xd0).toBeInTheDocument(); + expect(xd0).toHaveTextContent(/basic usage\s*yields\s*true/); - const xc0 = within(x).getByTestId('example_code0'); - expect(within(xc0).getByText("truthy('foo')")).toBeInTheDocument(); + const xc0 = within(x).getByTestId('example_code0'); + expect(within(xc0).getByText("truthy('foo')")).toBeInTheDocument(); + }); }); it('renders global values', async () => { const msvValue = ['foo', 'bar', 'baz']; diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts index 27e33befb5..fe4765b8b8 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/schema.ts @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { z } from 'zod'; -import { makeFieldSchemaFromZod } from '../utils'; +import { makeFieldSchema } from '@backstage/plugin-scaffolder-react'; -const EntityNamePickerFieldSchema = makeFieldSchemaFromZod(z.string()); +const EntityNamePickerFieldSchema = makeFieldSchema({ + output: z => z.string(), +}); export const EntityNamePickerSchema = EntityNamePickerFieldSchema.schema; -export type EntityNamePickerProps = typeof EntityNamePickerFieldSchema.type; +export type EntityNamePickerProps = typeof EntityNamePickerFieldSchema.TProps; diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx index 2098e3e07d..97c90b2aa5 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx @@ -28,6 +28,8 @@ import { EntityPickerProps } from './schema'; import { ScaffolderRJSFFieldProps as FieldProps } from '@backstage/plugin-scaffolder-react'; import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { scaffolderTranslationRef } from '../../../translation'; const makeEntity = (kind: string, namespace: string, name: string): Entity => ({ apiVersion: 'scaffolder.backstage.io/v1beta3', @@ -891,4 +893,128 @@ describe('<EntityPicker />', () => { expect(onChange).toHaveBeenCalledWith(undefined); }); }); + + describe('EntityPicker description', () => { + const description = { + fromSchema: 'EntityPicker description from schema', + fromUiSchema: 'EntityPicker description from uiSchema', + } as { fromSchema: string; fromUiSchema: string; default?: string }; + + beforeEach(() => { + const RealWrapper = Wrapper; + Wrapper = ({ children }: { children?: ReactNode }) => { + const { t } = useTranslationRef(scaffolderTranslationRef); + description.default = t('fields.entityPicker.description'); + return <RealWrapper>{children}</RealWrapper>; + }; + }); + it('presents default description', async () => { + uiSchema = { + 'ui:options': { + catalogFilter: [ + { + kind: ['Group'], + 'metadata.name': 'test-entity', + }, + { + kind: ['User'], + 'metadata.name': 'test-entity', + }, + ], + }, + }; + props = { + onChange, + schema, + required: true, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps<any>; + + const { getByText, queryByText } = await renderInTestApp( + <Wrapper> + <EntityPicker {...props} /> + </Wrapper>, + ); + expect(getByText(description.default!)).toBeInTheDocument(); + expect(queryByText(description.fromSchema)).toBe(null); + expect(queryByText(description.fromUiSchema)).toBe(null); + }); + + it('presents schema description', async () => { + uiSchema = { + 'ui:options': { + catalogFilter: [ + { + kind: ['Group'], + 'metadata.name': 'test-entity', + }, + { + kind: ['User'], + 'metadata.name': 'test-entity', + }, + ], + }, + }; + props = { + onChange, + schema: { + ...schema, + description: description.fromSchema, + }, + required: true, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps<any>; + + const { getByText, queryByText } = await renderInTestApp( + <Wrapper> + <EntityPicker {...props} /> + </Wrapper>, + ); + expect(queryByText(description.default!)).toBe(null); + expect(getByText(description.fromSchema)).toBeInTheDocument(); + expect(queryByText(description.fromUiSchema)).toBe(null); + }); + + it('presents uiSchema description', async () => { + uiSchema = { + 'ui:options': { + catalogFilter: [ + { + kind: ['Group'], + 'metadata.name': 'test-entity', + }, + { + kind: ['User'], + 'metadata.name': 'test-entity', + }, + ], + }, + 'ui:description': description.fromUiSchema, + }; + props = { + onChange, + schema: { + ...schema, + description: description.fromSchema, + }, + required: true, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps<any>; + + const { getByText, queryByText } = await renderInTestApp( + <Wrapper> + <EntityPicker {...props} /> + </Wrapper>, + ); + expect(queryByText(description.default!)).toBe(null); + expect(queryByText(description.fromSchema)).toBe(null); + expect(getByText(description.fromUiSchema)).toBeInTheDocument(); + }); + }); }); diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index 26f88ea85d..d2197d4426 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -185,7 +185,7 @@ export const EntityPicker = (props: EntityPickerProps) => { return ( <ScaffolderField rawErrors={rawErrors} - rawDescription={description} + rawDescription={uiSchema['ui:description'] ?? description} required={required} disabled={isDisabled} errors={errors} diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts index eb1bba364b..5102d248b7 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts @@ -13,57 +13,56 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { z } from 'zod'; -import { makeFieldSchemaFromZod } from '../utils'; +import { z as zod } from 'zod'; +import { makeFieldSchema } from '@backstage/plugin-scaffolder-react'; -/** - * @public - */ -export const entityQueryFilterExpressionSchema = z.record( - z - .string() - .or(z.object({ exists: z.boolean().optional() })) - .or(z.array(z.string())), -); - -/** - * @public - */ -export const EntityPickerFieldSchema = makeFieldSchemaFromZod( - z.string(), - z.object({ - /** - * @deprecated Use `catalogFilter` instead. - */ - allowedKinds: z - .array(z.string()) - .optional() - .describe( - 'DEPRECATED: Use `catalogFilter` instead. List of kinds of entities to derive options from', - ), - defaultKind: z +export const createEntityQueryFilterExpressionSchema = (z: typeof zod) => + z.record( + z .string() - .optional() - .describe( - 'The default entity kind. Options of this kind will not be prefixed.', - ), - allowArbitraryValues: z - .boolean() - .optional() - .describe('Whether to allow arbitrary user input. Defaults to true'), - defaultNamespace: z - .union([z.string(), z.literal(false)]) - .optional() - .describe( - 'The default namespace. Options with this namespace will not be prefixed.', - ), - catalogFilter: z - .array(entityQueryFilterExpressionSchema) - .or(entityQueryFilterExpressionSchema) - .optional() - .describe('List of key-value filter expression for entities'), - }), -); + .or(z.object({ exists: z.boolean().optional() })) + .or(z.array(z.string())), + ); + +/** + * @public + */ +export const EntityPickerFieldSchema = makeFieldSchema({ + output: z => z.string(), + uiOptions: z => + z.object({ + /** + * @deprecated Use `catalogFilter` instead. + */ + allowedKinds: z + .array(z.string()) + .optional() + .describe( + 'DEPRECATED: Use `catalogFilter` instead. List of kinds of entities to derive options from', + ), + defaultKind: z + .string() + .optional() + .describe( + 'The default entity kind. Options of this kind will not be prefixed.', + ), + allowArbitraryValues: z + .boolean() + .optional() + .describe('Whether to allow arbitrary user input. Defaults to true'), + defaultNamespace: z + .union([z.string(), z.literal(false)]) + .optional() + .describe( + 'The default namespace. Options with this namespace will not be prefixed.', + ), + catalogFilter: (t => t.or(t.array()))( + createEntityQueryFilterExpressionSchema(z), + ) + .optional() + .describe('List of key-value filter expression for entities'), + }), +}); /** * The input props that can be specified under `ui:options` for the @@ -71,15 +70,16 @@ export const EntityPickerFieldSchema = makeFieldSchemaFromZod( * * @public */ -export type EntityPickerUiOptions = - typeof EntityPickerFieldSchema.uiOptionsType; +export type EntityPickerUiOptions = NonNullable< + (typeof EntityPickerFieldSchema.TProps.uiSchema)['ui:options'] +>; -export type EntityPickerProps = typeof EntityPickerFieldSchema.type; +export type EntityPickerProps = typeof EntityPickerFieldSchema.TProps; export const EntityPickerSchema = EntityPickerFieldSchema.schema; -export type EntityPickerFilterQuery = z.TypeOf< - typeof entityQueryFilterExpressionSchema +export type EntityPickerFilterQuery = zod.TypeOf< + ReturnType<typeof createEntityQueryFilterExpressionSchema> >; export type EntityPickerFilterQueryValue = diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.test.tsx new file mode 100644 index 0000000000..98091f5098 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.test.tsx @@ -0,0 +1,148 @@ +/* + * 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 { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; +import { ScaffolderRJSFFieldProps as FieldProps } from '@backstage/plugin-scaffolder-react'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { ComponentType, PropsWithChildren, ReactNode } from 'react'; +import { EntityTagsPicker } from './EntityTagsPicker'; +import { EntityTagsPickerProps } from './schema'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { scaffolderTranslationRef } from '../../../translation'; + +const makeEntity = (kind: string, namespace: string, name: string): Entity => ({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind, + metadata: { namespace, name }, +}); + +describe('<EntityTagsPicker />', () => { + const entities: Entity[] = [ + makeEntity('Group', 'default', 'team-a'), + makeEntity('Group', 'default', 'squad-b'), + ]; + const onChange = jest.fn(); + const schema = { type: 'array', items: { type: 'string' } }; + const uiSchema: EntityTagsPickerProps['uiSchema'] = { + 'ui:options': {}, + }; + const rawErrors: string[] = []; + const formData = undefined; + + let props: FieldProps<string[]>; + + const catalogApi = catalogApiMock.mock({ + getEntities: jest.fn(async () => ({ items: entities })), + }); + + let Wrapper: ComponentType<PropsWithChildren<{}>>; + + beforeEach(() => { + Wrapper = ({ children }: { children?: ReactNode }) => ( + <TestApiProvider apis={[[catalogApiRef, catalogApi]]}> + {children} + </TestApiProvider> + ); + }); + + afterEach(() => jest.resetAllMocks()); + + describe('EntityTagsPicker description', () => { + const description = { + fromSchema: 'EntityTagsPicker description from schema', + fromUiSchema: 'EntityTagsPicker description from uiSchema', + } as { fromSchema: string; fromUiSchema: string; default?: string }; + + beforeEach(() => { + const RealWrapper = Wrapper; + Wrapper = ({ children }: { children?: ReactNode }) => { + const { t } = useTranslationRef(scaffolderTranslationRef); + description.default = t('fields.entityTagsPicker.description'); + return <RealWrapper>{children}</RealWrapper>; + }; + }); + it('presents default description', async () => { + props = { + onChange, + schema, + required: true, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps<any>; + + const { getByText, queryByText } = await renderInTestApp( + <Wrapper> + <EntityTagsPicker {...props} /> + </Wrapper>, + ); + expect(getByText(description.default!)).toBeInTheDocument(); + expect(queryByText(description.fromSchema)).toBe(null); + expect(queryByText(description.fromUiSchema)).toBe(null); + }); + + it('presents schema description', async () => { + props = { + onChange, + schema: { + ...schema, + description: description.fromSchema, + }, + required: true, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps<any>; + + const { getByText, queryByText } = await renderInTestApp( + <Wrapper> + <EntityTagsPicker {...props} /> + </Wrapper>, + ); + expect(queryByText(description.default!)).toBe(null); + expect(getByText(description.fromSchema)).toBeInTheDocument(); + expect(queryByText(description.fromUiSchema)).toBe(null); + }); + + it('presents uiSchema description', async () => { + props = { + onChange, + schema: { + ...schema, + description: description.fromSchema, + }, + required: true, + uiSchema: { + ...uiSchema, + 'ui:description': description.fromUiSchema, + }, + rawErrors, + formData, + } as unknown as FieldProps<any>; + + const { getByText, queryByText } = await renderInTestApp( + <Wrapper> + <EntityTagsPicker {...props} /> + </Wrapper>, + ); + expect(queryByText(description.default!)).toBe(null); + expect(queryByText(description.fromSchema)).toBe(null); + expect(getByText(description.fromUiSchema)).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx index 3cfa16de23..7d63526fe4 100644 --- a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx @@ -20,12 +20,12 @@ import { GetEntityFacetsRequest } from '@backstage/catalog-client'; import { makeValidator } from '@backstage/catalog-model'; import { useApi } from '@backstage/core-plugin-api'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import FormControl from '@material-ui/core/FormControl'; import TextField from '@material-ui/core/TextField'; import Autocomplete from '@material-ui/lab/Autocomplete'; import { EntityTagsPickerProps } from './schema'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { scaffolderTranslationRef } from '../../../translation'; +import { ScaffolderField } from '@backstage/plugin-scaffolder-react/alpha'; export { EntityTagsPickerSchema } from './schema'; @@ -36,7 +36,19 @@ export { EntityTagsPickerSchema } from './schema'; * @public */ export const EntityTagsPicker = (props: EntityTagsPickerProps) => { - const { formData, onChange, uiSchema } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); + const { + formData, + onChange, + schema: { + title = t('fields.entityTagsPicker.title'), + description = t('fields.entityTagsPicker.description'), + }, + uiSchema, + rawErrors, + required, + errors, + } = props; const catalogApi = useApi(catalogApiRef); const [tagOptions, setTagOptions] = useState<string[]>([]); const [inputValue, setInputValue] = useState(''); @@ -47,8 +59,6 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => { const helperText = uiSchema['ui:options']?.helperText; const isDisabled = uiSchema?.['ui:disabled'] ?? false; - const { t } = useTranslationRef(scaffolderTranslationRef); - const { loading, value: existingTags } = useAsync(async () => { const facet = 'metadata.tags'; const tagsRequest: GetEntityFacetsRequest = { facets: [facet] }; @@ -97,7 +107,13 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => { useEffectOnce(() => onChange(formData || [])); return ( - <FormControl margin="normal"> + <ScaffolderField + rawErrors={rawErrors} + rawDescription={helperText ?? uiSchema['ui:description'] ?? description} + required={required} + disabled={isDisabled} + errors={errors} + > <Autocomplete multiple freeSolo @@ -115,15 +131,14 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => { renderInput={params => ( <TextField {...params} - label={t('fields.entityTagsPicker.title')} + label={title} disabled={isDisabled} onChange={e => setInputValue(e.target.value)} error={inputError} - helperText={helperText ?? t('fields.entityTagsPicker.description')} FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }} /> )} /> - </FormControl> + </ScaffolderField> ); }; diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts index 6677d16e98..d1a300f5c8 100644 --- a/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/schema.ts @@ -13,30 +13,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { z } from 'zod'; -import { makeFieldSchemaFromZod } from '../utils'; +import { makeFieldSchema } from '@backstage/plugin-scaffolder-react'; /** * @public */ -export const EntityTagsPickerFieldSchema = makeFieldSchemaFromZod( - z.array(z.string()), - z.object({ - kinds: z - .array(z.string()) - .optional() - .describe('List of kinds of entities to derive tags from'), - showCounts: z - .boolean() - .optional() - .describe('Whether to show usage counts per tag'), - helperText: z.string().optional().describe('Helper text to display'), - }), -); +export const EntityTagsPickerFieldSchema = makeFieldSchema({ + output: z => z.array(z.string()), + uiOptions: z => + z.object({ + kinds: z + .array(z.string()) + .optional() + .describe('List of kinds of entities to derive tags from'), + showCounts: z + .boolean() + .optional() + .describe('Whether to show usage counts per tag'), + helperText: z + .string() + .optional() + .describe( + 'Helper text to display; DEPRECATED, simply use ui:description', + ), + }), +}); export const EntityTagsPickerSchema = EntityTagsPickerFieldSchema.schema; -export type EntityTagsPickerProps = typeof EntityTagsPickerFieldSchema.type; +export type EntityTagsPickerProps = typeof EntityTagsPickerFieldSchema.TProps; /** * The input props that can be specified under `ui:options` for the @@ -44,5 +49,6 @@ export type EntityTagsPickerProps = typeof EntityTagsPickerFieldSchema.type; * * @public */ -export type EntityTagsPickerUiOptions = - typeof EntityTagsPickerFieldSchema.uiOptionsType; +export type EntityTagsPickerUiOptions = NonNullable< + (typeof EntityTagsPickerFieldSchema.TProps.uiSchema)['ui:options'] +>; diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx index eb80d47e78..a02d793a34 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx @@ -30,6 +30,8 @@ import { MultiEntityPickerProps } from './schema'; import { ScaffolderRJSFFieldProps as FieldProps } from '@backstage/plugin-scaffolder-react'; import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { scaffolderTranslationRef } from '../../../translation'; const makeEntity = (kind: string, namespace: string, name: string): Entity => ({ apiVersion: 'scaffolder.backstage.io/v1beta3', @@ -43,7 +45,7 @@ describe('<MultiEntityPicker />', () => { makeEntity('Group', 'default', 'squad-b'), ]; const onChange = jest.fn(); - const schema = {}; + const schema = { type: 'array', items: { type: 'string' } }; const required = false; let uiSchema: MultiEntityPickerProps['uiSchema']; const rawErrors: string[] = []; @@ -847,4 +849,179 @@ describe('<MultiEntityPicker />', () => { ]); }); }); + + describe('MultiEntityPicker description', () => { + const description = { + fromSchema: 'MultiEntityPicker description from schema', + fromUiSchema: 'MultiEntityPicker description from uiSchema', + } as { fromSchema: string; fromUiSchema: string; default?: string }; + + beforeEach(() => { + const RealWrapper = Wrapper; + Wrapper = ({ children }: { children?: ReactNode }) => { + const { t } = useTranslationRef(scaffolderTranslationRef); + description.default = t('fields.multiEntityPicker.description'); + return <RealWrapper>{children}</RealWrapper>; + }; + uiSchema = { + 'ui:options': { + catalogFilter: [ + { + kind: ['Group'], + 'metadata.name': 'test-entity', + }, + { + kind: ['User'], + 'metadata.name': 'test-entity', + }, + ], + }, + }; + }); + it('presents default description', async () => { + props = { + onChange, + schema, + required: true, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps<any>; + + const { getByText, queryByText } = await renderInTestApp( + <Wrapper> + <MultiEntityPicker {...props} /> + </Wrapper>, + ); + expect(getByText(description.default!)).toBeInTheDocument(); + expect(queryByText(description.fromSchema)).toBe(null); + expect(queryByText(description.fromUiSchema)).toBe(null); + }); + + it('presents schema description', async () => { + props = { + onChange, + schema: { + ...schema, + description: description.fromSchema, + }, + required: true, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps<any>; + + const { getByText, queryByText } = await renderInTestApp( + <Wrapper> + <MultiEntityPicker {...props} /> + </Wrapper>, + ); + expect(queryByText(description.default!)).toBe(null); + expect(getByText(description.fromSchema)).toBeInTheDocument(); + expect(queryByText(description.fromUiSchema)).toBe(null); + }); + + it('presents uiSchema description', async () => { + props = { + onChange, + schema: { + ...schema, + description: description.fromSchema, + }, + required: true, + uiSchema: { + ...uiSchema, + 'ui:description': description.fromUiSchema, + }, + rawErrors, + formData, + } as unknown as FieldProps<any>; + + const { getByText, queryByText } = await renderInTestApp( + <Wrapper> + <MultiEntityPicker {...props} /> + </Wrapper>, + ); + expect(queryByText(description.default!)).toBe(null); + expect(queryByText(description.fromSchema)).toBe(null); + expect(getByText(description.fromUiSchema)).toBeInTheDocument(); + }); + }); + + describe('entity presentation', () => { + beforeEach(() => { + uiSchema = { + 'ui:options': { + defaultKind: 'Group', + }, + }; + props = { + onChange, + schema, + required, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps<any>; + }); + + it('renders and filters selection displayName', async () => { + catalogApi.getEntities.mockResolvedValue({ + items: entities.map(item => ({ + ...item, + spec: { + profile: { displayName: item.metadata.name.replace('-', ' ') }, + }, + })), + }); + + const { getByRole, getByText } = await renderInTestApp( + <Wrapper> + <MultiEntityPicker {...props} /> + </Wrapper>, + ); + + const input = getByRole('textbox'); + + fireEvent.change(input, { target: { value: 'team a' } }); + + expect(getByText('team a')).toBeInTheDocument(); + + fireEvent.change(input, { target: { value: 'squad b' } }); + + expect(getByText('squad b')).toBeInTheDocument(); + + fireEvent.blur(input); + }); + + it('renders and filters selection title', async () => { + catalogApi.getEntities.mockResolvedValue({ + items: entities.map(item => ({ + ...item, + metadata: { + ...item.metadata, + title: item.metadata.name.replace('-', ' ').toUpperCase(), + }, + })), + }); + + const { getByRole, getByText } = await renderInTestApp( + <Wrapper> + <MultiEntityPicker {...props} /> + </Wrapper>, + ); + + const input = getByRole('textbox'); + + fireEvent.change(input, { target: { value: 'team a' } }); + + expect(getByText('TEAM A')).toBeInTheDocument(); + + fireEvent.change(input, { target: { value: 'squad b' } }); + + expect(getByText('SQUAD B')).toBeInTheDocument(); + + fireEvent.blur(input); + }); + }); }); diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx index e6a535af41..57c4b87d6d 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx @@ -30,9 +30,9 @@ import { EntityRefPresentationSnapshot, } from '@backstage/plugin-catalog-react'; import TextField from '@material-ui/core/TextField'; -import FormControl from '@material-ui/core/FormControl'; import Autocomplete, { AutocompleteChangeReason, + createFilterOptions, } from '@material-ui/lab/Autocomplete'; import { useCallback, useEffect, useState } from 'react'; import useAsync from 'react-use/esm/useAsync'; @@ -44,6 +44,9 @@ import { MultiEntityPickerFilterQuery, } from './schema'; import { VirtualizedListbox } from '../VirtualizedListbox'; +import { ScaffolderField } from '@backstage/plugin-scaffolder-react/alpha'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { scaffolderTranslationRef } from '../../../translation'; export { MultiEntityPickerSchema } from './schema'; @@ -52,14 +55,19 @@ export { MultiEntityPickerSchema } from './schema'; * field extension. */ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { + const { t } = useTranslationRef(scaffolderTranslationRef); const { onChange, - schema: { title = 'Entity', description = 'An entity from the catalog' }, + schema: { + title = t('fields.multiEntityPicker.title'), + description = t('fields.multiEntityPicker.description'), + }, required, uiSchema, rawErrors, formData, idSchema, + errors, } = props; const catalogFilter = buildCatalogFilter(uiSchema); @@ -144,10 +152,12 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { }, [entities, onChange, required, allowArbitraryValues]); return ( - <FormControl - margin="normal" + <ScaffolderField + rawErrors={rawErrors} + rawDescription={uiSchema['ui:description'] ?? description} required={required} - error={rawErrors?.length > 0 && !formData} + disabled={isDisabled} + errors={errors} > <Autocomplete multiple @@ -182,7 +192,6 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { label={title} disabled={isDisabled} margin="dense" - helperText={description} FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 }, @@ -195,9 +204,14 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { }} /> )} + filterOptions={createFilterOptions<Entity>({ + stringify: option => + entities?.entityRefToPresentation.get(stringifyEntityRef(option)) + ?.primaryTitle!, + })} ListboxComponent={VirtualizedListbox} /> - </FormControl> + </ScaffolderField> ); }; diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/schema.ts b/plugins/scaffolder/src/components/fields/MultiEntityPicker/schema.ts index 2621157d02..667b4638b8 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/schema.ts @@ -13,55 +13,57 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { z } from 'zod'; -import { makeFieldSchemaFromZod } from '../utils'; +import { z as zod } from 'zod'; +import { makeFieldSchema } from '@backstage/plugin-scaffolder-react'; -export const entityQueryFilterExpressionSchema = z.record( - z +export const entityQueryFilterExpressionSchema = zod.record( + zod .string() - .or(z.object({ exists: z.boolean().optional() })) - .or(z.array(z.string())), + .or(zod.object({ exists: zod.boolean().optional() })) + .or(zod.array(zod.string())), ); -export const MultiEntityPickerFieldSchema = makeFieldSchemaFromZod( - z.array(z.string()), - z.object({ - defaultKind: z - .string() - .optional() - .describe( - 'The default entity kind. Options of this kind will not be prefixed.', - ), - allowArbitraryValues: z - .boolean() - .optional() - .describe('Whether to allow arbitrary user input. Defaults to true'), - defaultNamespace: z - .union([z.string(), z.literal(false)]) - .optional() - .describe( - 'The default namespace. Options with this namespace will not be prefixed.', - ), - catalogFilter: z - .array(entityQueryFilterExpressionSchema) - .or(entityQueryFilterExpressionSchema) - .optional() - .describe('List of key-value filter expression for entities'), - }), -); +export const MultiEntityPickerFieldSchema = makeFieldSchema({ + output: z => z.array(z.string()), + uiOptions: z => + z.object({ + defaultKind: z + .string() + .optional() + .describe( + 'The default entity kind. Options of this kind will not be prefixed.', + ), + allowArbitraryValues: z + .boolean() + .optional() + .describe('Whether to allow arbitrary user input. Defaults to true'), + defaultNamespace: z + .union([z.string(), z.literal(false)]) + .optional() + .describe( + 'The default namespace. Options with this namespace will not be prefixed.', + ), + catalogFilter: z + .array(entityQueryFilterExpressionSchema) + .or(entityQueryFilterExpressionSchema) + .optional() + .describe('List of key-value filter expression for entities'), + }), +}); /** * The input props that can be specified under `ui:options` for the * `EntityPicker` field extension. */ -export type MultiEntityPickerUiOptions = - typeof MultiEntityPickerFieldSchema.uiOptionsType; +export type MultiEntityPickerUiOptions = NonNullable< + (typeof MultiEntityPickerFieldSchema.TProps.uiSchema)['ui:options'] +>; -export type MultiEntityPickerProps = typeof MultiEntityPickerFieldSchema.type; +export type MultiEntityPickerProps = typeof MultiEntityPickerFieldSchema.TProps; export const MultiEntityPickerSchema = MultiEntityPickerFieldSchema.schema; -export type MultiEntityPickerFilterQuery = z.TypeOf< +export type MultiEntityPickerFilterQuery = zod.TypeOf< typeof entityQueryFilterExpressionSchema >; diff --git a/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx b/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx index 6e0a3b81bc..dcba013093 100644 --- a/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx @@ -35,6 +35,9 @@ import { import userEvent from '@testing-library/user-event'; import { ScaffolderRJSFFieldProps as FieldProps } from '@backstage/plugin-scaffolder-react'; import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog'; +import { ComponentType, PropsWithChildren, ReactNode } from 'react'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { scaffolderTranslationRef } from '../../../translation'; const mockIdentityApi = mockApis.identity({ userEntityRef: 'user:default/bob', @@ -99,6 +102,7 @@ describe('<MyGroupsPicker />', () => { onChange, schema, required, + uiSchema: {}, } as unknown as FieldProps<string>; await renderInTestApp( @@ -175,6 +179,7 @@ describe('<MyGroupsPicker />', () => { onChange, schema, required, + uiSchema: {}, } as unknown as FieldProps<string>; const { queryByText, getByRole } = await renderInTestApp( @@ -236,6 +241,7 @@ describe('<MyGroupsPicker />', () => { onChange, schema, required, + uiSchema: {}, } as unknown as FieldProps<string>; const { getByRole } = await renderInTestApp( @@ -299,6 +305,7 @@ describe('<MyGroupsPicker />', () => { onChange, schema, required, + uiSchema: {}, formData: 'group:default/group1', } as unknown as FieldProps<string>; @@ -327,4 +334,99 @@ describe('<MyGroupsPicker />', () => { expect(inputFieldValue).toEqual(userGroups[0].metadata.title); }); + + describe('MyGroupsPicker description', () => { + const description = { + fromSchema: 'MyGroupsPicker description from schema', + fromUiSchema: 'MyGroupsPicker description from uiSchema', + } as { fromSchema: string; fromUiSchema: string; default?: string }; + + let Wrapper: ComponentType<PropsWithChildren<{}>>; + + beforeEach(() => { + Wrapper = ({ children }: { children?: ReactNode }) => { + const { t } = useTranslationRef(scaffolderTranslationRef); + description.default = t('fields.myGroupsPicker.description'); + return ( + <TestApiProvider + apis={[ + [identityApiRef, mockIdentityApi], + [catalogApiRef, catalogApi], + [errorApiRef, mockErrorApi], + [ + entityPresentationApiRef, + DefaultEntityPresentationApi.create({ catalogApi }), + ], + ]} + > + {children} + </TestApiProvider> + ); + }; + }); + it('presents default description', async () => { + const props = { + onChange, + schema, + required: true, + uiSchema: {}, + formData: 'group:default/group1', + } as unknown as FieldProps<string>; + + const { getByText, queryByText } = await renderInTestApp( + <Wrapper> + <MyGroupsPicker {...props} /> + </Wrapper>, + ); + expect(getByText(description.default!)).toBeInTheDocument(); + expect(queryByText(description.fromSchema)).toBe(null); + expect(queryByText(description.fromUiSchema)).toBe(null); + }); + + it('presents schema description', async () => { + const props = { + onChange, + schema: { + ...schema, + description: description.fromSchema, + }, + required: true, + uiSchema: {}, + formData: 'group:default/group1', + } as unknown as FieldProps<string>; + + const { getByText, queryByText } = await renderInTestApp( + <Wrapper> + <MyGroupsPicker {...props} /> + </Wrapper>, + ); + expect(queryByText(description.default!)).toBe(null); + expect(getByText(description.fromSchema)).toBeInTheDocument(); + expect(queryByText(description.fromUiSchema)).toBe(null); + }); + + it('presents uiSchema description', async () => { + const props = { + onChange, + schema: { + ...schema, + description: description.fromSchema, + }, + required: true, + uiSchema: { + 'ui:description': description.fromUiSchema, + }, + formData: 'group:default/group1', + } as unknown as FieldProps<string>; + + const { getByText, queryByText } = await renderInTestApp( + <Wrapper> + <MyGroupsPicker {...props} /> + </Wrapper>, + ); + expect(queryByText(description.default!)).toBe(null); + expect(queryByText(description.fromSchema)).toBe(null); + expect(getByText(description.fromUiSchema)).toBeInTheDocument(); + }); + }); }); diff --git a/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.tsx b/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.tsx index b49a1a1531..e92057c713 100644 --- a/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.tsx @@ -21,7 +21,6 @@ import { useApi, } from '@backstage/core-plugin-api'; import TextField from '@material-ui/core/TextField'; -import FormControl from '@material-ui/core/FormControl'; import { MyGroupsPickerProps, MyGroupsPickerSchema } from './schema'; import Autocomplete, { createFilterOptions, @@ -38,6 +37,7 @@ import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import { VirtualizedListbox } from '../VirtualizedListbox'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { scaffolderTranslationRef } from '../../../translation'; +import { ScaffolderField } from '@backstage/plugin-scaffolder-react/alpha'; export { MyGroupsPickerSchema }; @@ -52,12 +52,15 @@ export const MyGroupsPicker = (props: MyGroupsPickerProps) => { rawErrors, onChange, formData, + uiSchema, + errors, } = props; const identityApi = useApi(identityApiRef); const catalogApi = useApi(catalogApiRef); const errorApi = useApi(errorApiRef); const entityPresentationApi = useApi(entityPresentationApiRef); + const isDisabled = uiSchema?.['ui:disabled'] ?? false; const { value: groups, loading } = useAsync(async () => { const { userEntityRef } = await identityApi.getBackstageIdentity(); @@ -108,10 +111,12 @@ export const MyGroupsPicker = (props: MyGroupsPickerProps) => { }, [groups, onChange, selectedEntity, required]); return ( - <FormControl - margin="normal" + <ScaffolderField + rawErrors={rawErrors} + rawDescription={uiSchema['ui:description'] ?? description} required={required} - error={rawErrors?.length > 0} + disabled={isDisabled} + errors={errors} > <Autocomplete disabled={required && groups?.catalogEntities.length === 1} @@ -130,7 +135,6 @@ export const MyGroupsPicker = (props: MyGroupsPickerProps) => { {...params} label={title} margin="dense" - helperText={description} FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }} variant="outlined" required={required} @@ -145,6 +149,6 @@ export const MyGroupsPicker = (props: MyGroupsPickerProps) => { })} ListboxComponent={VirtualizedListbox} /> - </FormControl> + </ScaffolderField> ); }; diff --git a/plugins/scaffolder/src/components/fields/MyGroupsPicker/schema.ts b/plugins/scaffolder/src/components/fields/MyGroupsPicker/schema.ts index d5f8af7c56..6ccda91afb 100644 --- a/plugins/scaffolder/src/components/fields/MyGroupsPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/MyGroupsPicker/schema.ts @@ -14,42 +14,34 @@ * limitations under the License. */ -import { z } from 'zod'; -import { makeFieldSchemaFromZod } from '../utils'; +import { makeFieldSchema } from '@backstage/plugin-scaffolder-react'; /** * Field schema for the MyGroupsPicker. * @public */ -export const MyGroupsPickerFieldSchema = makeFieldSchemaFromZod( - z.string(), - z.object({ - title: z.string().default('Group').describe('Group'), - description: z - .string() - .default('A group you are part of') - .describe('The group to which the entity belongs'), - }), -); +export const MyGroupsPickerFieldSchema = makeFieldSchema({ + output: z => z.string(), +}); /** * UI options for the MyGroupsPicker. * @public */ -export type MyGroupsPickerUiOptions = - typeof MyGroupsPickerFieldSchema.uiOptionsType; +export type MyGroupsPickerUiOptions = NonNullable< + (typeof MyGroupsPickerFieldSchema.TProps.uiSchema)['ui:options'] +>; + /** * Props for the MyGroupsPicker. * @public */ - -export type MyGroupsPickerProps = typeof MyGroupsPickerFieldSchema.type; +export type MyGroupsPickerProps = typeof MyGroupsPickerFieldSchema.TProps; /** * Schema for the MyGroupsPicker. * @public */ - export const MyGroupsPickerSchema = MyGroupsPickerFieldSchema.schema; diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx index a7c29c081e..0651066cfb 100644 --- a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx @@ -24,6 +24,7 @@ import { OwnedEntityPickerProps } from './schema'; import { EntityPickerProps } from '../EntityPicker/schema'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { scaffolderTranslationRef } from '../../../translation'; +import { ScaffolderField } from '@backstage/plugin-scaffolder-react/alpha'; export { OwnedEntityPickerSchema } from './schema'; @@ -52,22 +53,30 @@ export const OwnedEntityPicker = (props: OwnedEntityPickerProps) => { if (loading) return ( - <Autocomplete - loading={loading} - renderInput={params => ( - <TextField - {...params} - label={title} - margin="dense" - helperText={description} - FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }} - variant="outlined" - required={required} - InputProps={params.InputProps} - /> - )} - options={[]} - /> + <ScaffolderField + rawDescription={uiSchema['ui:description'] ?? description} + required={required} + disabled={uiSchema['ui:disabled']} + > + <Autocomplete + loading={loading} + renderInput={params => ( + <TextField + {...params} + label={title} + margin="dense" + FormHelperTextProps={{ + margin: 'dense', + style: { marginLeft: 0 }, + }} + variant="outlined" + required={required} + InputProps={params.InputProps} + /> + )} + options={[]} + /> + </ScaffolderField> ); const entityPickerUISchema = buildEntityPickerUISchema( diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts index 2bb75db263..6dd00df49d 100644 --- a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/schema.ts @@ -13,45 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { z } from 'zod'; -import { makeFieldSchemaFromZod } from '../utils'; -import { entityQueryFilterExpressionSchema } from '../EntityPicker/schema'; +import { EntityPickerFieldSchema } from '../EntityPicker/schema'; /** * @public */ -export const OwnedEntityPickerFieldSchema = makeFieldSchemaFromZod( - z.string(), - z.object({ - allowedKinds: z - .array(z.string()) - .optional() - .describe( - 'DEPRECATED: Use `catalogFilter` instead. List of kinds of entities to derive options from', - ), - defaultKind: z - .string() - .optional() - .describe( - 'The default entity kind. Options of this kind will not be prefixed.', - ), - allowArbitraryValues: z - .boolean() - .optional() - .describe('Whether to allow arbitrary user input. Defaults to true'), - defaultNamespace: z - .union([z.string(), z.literal(false)]) - .optional() - .describe( - 'The default namespace. Options with this namespace will not be prefixed.', - ), - catalogFilter: z - .array(entityQueryFilterExpressionSchema) - .or(entityQueryFilterExpressionSchema) - .optional() - .describe('List of key-value filter expression for entities'), - }), -); +export const OwnedEntityPickerFieldSchema = EntityPickerFieldSchema; /** * The input props that can be specified under `ui:options` for the @@ -59,9 +26,10 @@ export const OwnedEntityPickerFieldSchema = makeFieldSchemaFromZod( * * @public */ -export type OwnedEntityPickerUiOptions = - typeof OwnedEntityPickerFieldSchema.uiOptionsType; +export type OwnedEntityPickerUiOptions = NonNullable< + (typeof OwnedEntityPickerFieldSchema.TProps.uiSchema)['ui:options'] +>; -export type OwnedEntityPickerProps = typeof OwnedEntityPickerFieldSchema.type; +export type OwnedEntityPickerProps = typeof OwnedEntityPickerFieldSchema.TProps; export const OwnedEntityPickerSchema = OwnedEntityPickerFieldSchema.schema; diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts b/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts index 0dbe2fa74f..76389216ba 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts @@ -13,43 +13,43 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { z } from 'zod'; -import { makeFieldSchemaFromZod } from '../utils'; -import { entityQueryFilterExpressionSchema } from '../EntityPicker/schema'; +import { makeFieldSchema } from '@backstage/plugin-scaffolder-react'; +import { createEntityQueryFilterExpressionSchema } from '../EntityPicker/schema'; /** * @public */ -export const OwnerPickerFieldSchema = makeFieldSchemaFromZod( - z.string(), - z.object({ - /** - * @deprecated Use `catalogFilter` instead. - */ - allowedKinds: z - .array(z.string()) - .default(['Group', 'User']) - .optional() - .describe( - 'DEPRECATED: Use `catalogFilter` instead. List of kinds of entities to derive options from. Defaults to Group and User', - ), - allowArbitraryValues: z - .boolean() - .optional() - .describe('Whether to allow arbitrary user input. Defaults to true'), - defaultNamespace: z - .union([z.string(), z.literal(false)]) - .optional() - .describe( - 'The default namespace. Options with this namespace will not be prefixed.', - ), - catalogFilter: z - .array(entityQueryFilterExpressionSchema) - .or(entityQueryFilterExpressionSchema) - .optional() - .describe('List of key-value filter expression for entities'), - }), -); +export const OwnerPickerFieldSchema = makeFieldSchema({ + output: z => z.string(), + uiOptions: z => + z.object({ + /** + * @deprecated Use `catalogFilter` instead. + */ + allowedKinds: z + .array(z.string()) + .default(['Group', 'User']) + .optional() + .describe( + 'DEPRECATED: Use `catalogFilter` instead. List of kinds of entities to derive options from. Defaults to Group and User', + ), + allowArbitraryValues: z + .boolean() + .optional() + .describe('Whether to allow arbitrary user input. Defaults to true'), + defaultNamespace: z + .union([z.string(), z.literal(false)]) + .optional() + .describe( + 'The default namespace. Options with this namespace will not be prefixed.', + ), + catalogFilter: (t => t.or(t.array()))( + createEntityQueryFilterExpressionSchema(z), + ) + .optional() + .describe('List of key-value filter expression for entities'), + }), +}); /** * The input props that can be specified under `ui:options` for the @@ -57,8 +57,10 @@ export const OwnerPickerFieldSchema = makeFieldSchemaFromZod( * * @public */ -export type OwnerPickerUiOptions = typeof OwnerPickerFieldSchema.uiOptionsType; +export type OwnerPickerUiOptions = NonNullable< + (typeof OwnerPickerFieldSchema.TProps.uiSchema)['ui:options'] +>; -export type OwnerPickerProps = typeof OwnerPickerFieldSchema.type; +export type OwnerPickerProps = typeof OwnerPickerFieldSchema.TProps; export const OwnerPickerSchema = OwnerPickerFieldSchema.schema; diff --git a/plugins/scaffolder/src/components/fields/RepoBranchPicker/RepoBranchPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoBranchPicker/RepoBranchPicker.test.tsx index ba1b9642fd..2d479d346a 100644 --- a/plugins/scaffolder/src/components/fields/RepoBranchPicker/RepoBranchPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoBranchPicker/RepoBranchPicker.test.tsx @@ -29,9 +29,11 @@ import { scaffolderApiRef, useTemplateSecrets, ScaffolderRJSFField, + ScaffolderRJSFFormProps as FormProps, } from '@backstage/plugin-scaffolder-react'; import { act, fireEvent, screen } from '@testing-library/react'; import { RepoBranchPicker } from './RepoBranchPicker'; +import { ComponentType, PropsWithChildren, ReactNode } from 'react'; describe('RepoBranchPicker', () => { const mockIntegrationsApi: Partial<ScmIntegrationsApi> = { @@ -333,4 +335,104 @@ describe('RepoBranchPicker', () => { expect(getByText('abc123')).toBeInTheDocument(); }); }); + + describe('RepoBranchPicker description', () => { + const description = { + fromSchema: 'RepoBranchPicker description from schema', + fromUiSchema: 'RepoBranchPicker description from uiSchema', + } as { fromSchema: string; fromUiSchema: string }; + + let Wrapper: ComponentType<PropsWithChildren<{}>>; + + beforeEach(() => { + Wrapper = ({ children }: { children?: ReactNode }) => { + return ( + <TestApiProvider + apis={[ + [scmIntegrationsApiRef, mockIntegrationsApi], + [scmAuthApiRef, {}], + [scaffolderApiRef, {}], + ]} + > + <SecretsContextProvider>{children}</SecretsContextProvider> + </TestApiProvider> + ); + }; + }); + it('omits description', async () => { + const props = { + validator, + schema: { type: 'string' }, + uiSchema: { 'ui:field': 'RepoBranchPicker' }, + fields: { + RepoBranchPicker: RepoBranchPicker as ScaffolderRJSFField<string>, + }, + formContext: { + formData: {}, + }, + } as unknown as FormProps<any>; + + const { container } = await renderInTestApp( + <Wrapper> + <Form {...props} /> + </Wrapper>, + ); + expect( + container.getElementsByClassName('MuiTypography-body1'), + ).toHaveLength(0); + }); + + it('presents schema description', async () => { + const props = { + validator, + schema: { type: 'string', description: description.fromSchema }, + uiSchema: { 'ui:field': 'RepoBranchPicker' }, + fields: { + RepoBranchPicker: RepoBranchPicker as ScaffolderRJSFField<string>, + }, + formContext: { + formData: {}, + }, + } as unknown as FormProps<any>; + + const { container, getByText, queryByText } = await renderInTestApp( + <Wrapper> + <Form {...props} /> + </Wrapper>, + ); + expect( + container.getElementsByClassName('MuiTypography-body1'), + ).toHaveLength(1); + expect(getByText(description.fromSchema)).toBeInTheDocument(); + expect(queryByText(description.fromUiSchema)).toBe(null); + }); + + it('presents uiSchema description', async () => { + const props = { + validator, + schema: { type: 'string', description: description.fromSchema }, + uiSchema: { + 'ui:field': 'RepoBranchPicker', + 'ui:description': description.fromUiSchema, + }, + fields: { + RepoBranchPicker: RepoBranchPicker as ScaffolderRJSFField<string>, + }, + formContext: { + formData: {}, + }, + } as unknown as FormProps<any>; + + const { container, getByText, queryByText } = await renderInTestApp( + <Wrapper> + <Form {...props} /> + </Wrapper>, + ); + expect( + container.getElementsByClassName('MuiTypography-body1'), + ).toHaveLength(1); + expect(queryByText(description.fromSchema)).toBe(null); + expect(getByText(description.fromUiSchema)).toBeInTheDocument(); + }); + }); }); diff --git a/plugins/scaffolder/src/components/fields/RepoBranchPicker/RepoBranchPicker.tsx b/plugins/scaffolder/src/components/fields/RepoBranchPicker/RepoBranchPicker.tsx index f81091f115..34f968d0de 100644 --- a/plugins/scaffolder/src/components/fields/RepoBranchPicker/RepoBranchPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoBranchPicker/RepoBranchPicker.tsx @@ -31,6 +31,7 @@ import { RepoBranchPickerState } from './types'; import { BitbucketRepoBranchPicker } from './BitbucketRepoBranchPicker'; import { DefaultRepoBranchPicker } from './DefaultRepoBranchPicker'; import { GitHubRepoBranchPicker } from './GitHubRepoBranchPicker'; +import { MarkdownContent } from '@backstage/core-components'; /** * The underlying component that is rendered in the form for the `RepoBranchPicker` @@ -164,6 +165,8 @@ export const RepoBranchPicker = (props: RepoBranchPickerProps) => { } }; + const description = uiSchema['ui:description'] ?? schema.description; + return ( <> {schema.title && ( @@ -172,8 +175,10 @@ export const RepoBranchPicker = (props: RepoBranchPickerProps) => { <Divider /> </Box> )} - {schema.description && ( - <Typography variant="body1">{schema.description}</Typography> + {description && ( + <Typography variant="body1"> + <MarkdownContent content={description} /> + </Typography> )} {renderRepoBranchPicker()} </> diff --git a/plugins/scaffolder/src/components/fields/RepoBranchPicker/schema.ts b/plugins/scaffolder/src/components/fields/RepoBranchPicker/schema.ts index fae5e6435e..62a3b66e14 100644 --- a/plugins/scaffolder/src/components/fields/RepoBranchPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/RepoBranchPicker/schema.ts @@ -13,58 +13,58 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { z } from 'zod'; -import { makeFieldSchemaFromZod } from '../utils'; +import { makeFieldSchema } from '@backstage/plugin-scaffolder-react'; /** * @public */ -export const RepoBranchPickerFieldSchema = makeFieldSchemaFromZod( - z.string(), - z.object({ - requestUserCredentials: z - .object({ - secretsKey: z - .string() - .describe( - 'Key used within the template secrets context to store the credential', - ), - additionalScopes: z - .object({ - gitea: z - .array(z.string()) - .optional() - .describe('Additional Gitea scopes to request'), - gerrit: z - .array(z.string()) - .optional() - .describe('Additional Gerrit scopes to request'), - github: z - .array(z.string()) - .optional() - .describe('Additional GitHub scopes to request'), - gitlab: z - .array(z.string()) - .optional() - .describe('Additional GitLab scopes to request'), - bitbucket: z - .array(z.string()) - .optional() - .describe('Additional BitBucket scopes to request'), - azure: z - .array(z.string()) - .optional() - .describe('Additional Azure scopes to request'), - }) - .optional() - .describe('Additional permission scopes to request'), - }) - .optional() - .describe( - 'If defined will request user credentials to auth against the given SCM platform', - ), - }), -); +export const RepoBranchPickerFieldSchema = makeFieldSchema({ + output: z => z.string(), + uiOptions: z => + z.object({ + requestUserCredentials: z + .object({ + secretsKey: z + .string() + .describe( + 'Key used within the template secrets context to store the credential', + ), + additionalScopes: z + .object({ + gitea: z + .array(z.string()) + .optional() + .describe('Additional Gitea scopes to request'), + gerrit: z + .array(z.string()) + .optional() + .describe('Additional Gerrit scopes to request'), + github: z + .array(z.string()) + .optional() + .describe('Additional GitHub scopes to request'), + gitlab: z + .array(z.string()) + .optional() + .describe('Additional GitLab scopes to request'), + bitbucket: z + .array(z.string()) + .optional() + .describe('Additional BitBucket scopes to request'), + azure: z + .array(z.string()) + .optional() + .describe('Additional Azure scopes to request'), + }) + .optional() + .describe('Additional permission scopes to request'), + }) + .optional() + .describe( + 'If defined will request user credentials to auth against the given SCM platform', + ), + }), +}); /** * The input props that can be specified under `ui:options` for the @@ -72,10 +72,11 @@ export const RepoBranchPickerFieldSchema = makeFieldSchemaFromZod( * * @public */ -export type RepoBranchPickerUiOptions = - typeof RepoBranchPickerFieldSchema.uiOptionsType; +export type RepoBranchPickerUiOptions = NonNullable< + (typeof RepoBranchPickerFieldSchema.TProps.uiSchema)['ui:options'] +>; -export type RepoBranchPickerProps = typeof RepoBranchPickerFieldSchema.type; +export type RepoBranchPickerProps = typeof RepoBranchPickerFieldSchema.TProps; // This has been duplicated from /plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts // NOTE: There is a bug with this failing validation in the custom field explorer due diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.tsx index 696b33e9d0..f5861d4946 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.tsx @@ -85,6 +85,7 @@ export const BitbucketRepoPicker = ( scaffolderApi .autocomplete({ + context: {}, token: accessToken, resource: 'workspaces', provider: 'bitbucket-cloud', diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx index c5d88275f6..f23d8356ac 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx @@ -30,9 +30,11 @@ import { ScaffolderApi, useTemplateSecrets, ScaffolderRJSFField, + ScaffolderRJSFFormProps as FormProps, } from '@backstage/plugin-scaffolder-react'; import { act, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { ComponentType, PropsWithChildren, ReactNode } from 'react'; describe('RepoUrlPicker', () => { const mockScaffolderApi: Partial<ScaffolderApi> = { @@ -417,4 +419,106 @@ describe('RepoUrlPicker', () => { }); }); }); + + describe('RepoUrlPicker description', () => { + const description = { + fromSchema: 'RepoUrlPicker description from schema', + fromUiSchema: 'RepoUrlPicker description from uiSchema', + } as { fromSchema: string; fromUiSchema: string }; + + let Wrapper: ComponentType<PropsWithChildren<{}>>; + + beforeEach(() => { + Wrapper = ({ children }: { children?: ReactNode }) => { + return ( + <TestApiProvider + apis={[ + [scmIntegrationsApiRef, mockIntegrationsApi], + [scmAuthApiRef, {}], + [scaffolderApiRef, mockScaffolderApi], + ]} + > + <SecretsContextProvider>{children}</SecretsContextProvider> + </TestApiProvider> + ); + }; + }); + it('omits description', async () => { + const props = { + validator, + schema: { type: 'string' }, + uiSchema: { 'ui:field': 'RepoUrlPicker' }, + fields: { + RepoUrlPicker: RepoUrlPicker as ScaffolderRJSFField<string>, + }, + formContext: { + formData: {}, + }, + } as unknown as FormProps<any>; + + const { container } = await renderInTestApp( + <Wrapper> + <Form {...props} /> + </Wrapper>, + ); + expect( + container.getElementsByClassName('MuiTypography-body1'), + ).toHaveLength(0); + }); + + it('presents schema description', async () => { + const props = { + validator, + schema: { type: 'string', description: description.fromSchema }, + uiSchema: { + 'ui:field': 'RepoUrlPicker', + }, + fields: { + RepoUrlPicker: RepoUrlPicker as ScaffolderRJSFField<string>, + }, + formContext: { + formData: {}, + }, + } as unknown as FormProps<any>; + + const { container, getByText, queryByText } = await renderInTestApp( + <Wrapper> + <Form {...props} /> + </Wrapper>, + ); + expect( + container.getElementsByClassName('MuiTypography-body1'), + ).toHaveLength(1); + expect(getByText(description.fromSchema)).toBeInTheDocument(); + expect(queryByText(description.fromUiSchema)).toBe(null); + }); + + it('presents uiSchema description', async () => { + const props = { + validator, + schema: { type: 'string', description: description.fromSchema }, + uiSchema: { + 'ui:field': 'RepoUrlPicker', + 'ui:description': description.fromUiSchema, + }, + fields: { + RepoUrlPicker: RepoUrlPicker as ScaffolderRJSFField<string>, + }, + formContext: { + formData: {}, + }, + } as unknown as FormProps<any>; + + const { container, getByText, queryByText } = await renderInTestApp( + <Wrapper> + <Form {...props} /> + </Wrapper>, + ); + expect( + container.getElementsByClassName('MuiTypography-body1'), + ).toHaveLength(1); + expect(queryByText(description.fromSchema)).toBe(null); + expect(getByText(description.fromUiSchema)).toBeInTheDocument(); + }); + }); }); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx index 953b607b07..8f1e3a67b7 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -35,6 +35,7 @@ import { RepoUrlPickerRepoName } from './RepoUrlPickerRepoName'; import { RepoUrlPickerFieldSchema } from './schema'; import { RepoUrlPickerState } from './types'; import { parseRepoPickerUrl, serializeRepoPickerUrl } from './utils'; +import { MarkdownContent } from '@backstage/core-components'; export { RepoUrlPickerSchema } from './schema'; @@ -166,6 +167,9 @@ export const RepoUrlPicker = ( const hostType = (state.host && integrationApi.byHost(state.host)?.type) ?? null; + + const description = uiSchema['ui:description'] ?? schema.description; + return ( <> {schema.title && ( @@ -174,8 +178,10 @@ export const RepoUrlPicker = ( <Divider /> </Box> )} - {schema.description && ( - <Typography variant="body1">{schema.description}</Typography> + {description && ( + <Typography variant="body1"> + <MarkdownContent content={description} /> + </Typography> )} <RepoUrlPickerHost host={state.host} diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts index ff307f2ffe..1fec9cc8f7 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts @@ -94,10 +94,11 @@ export const RepoUrlPickerFieldSchema = makeFieldSchema({ * @public * @deprecated this will be removed as it's no longer used */ -export type RepoUrlPickerUiOptions = - typeof RepoUrlPickerFieldSchema.uiOptionsType; +export type RepoUrlPickerUiOptions = NonNullable< + (typeof RepoUrlPickerFieldSchema.TProps.uiSchema)['ui:options'] +>; -export type RepoUrlPickerProps = typeof RepoUrlPickerFieldSchema.type; +export type RepoUrlPickerProps = typeof RepoUrlPickerFieldSchema.TProps; // This has been duplicated to /plugins/scaffolder/src/components/fields/RepoBranchPicker/schema.ts // NOTE: There is a bug with this failing validation in the custom field explorer due diff --git a/plugins/scaffolder/src/components/fields/SecretInput/SecretInput.test.tsx b/plugins/scaffolder/src/components/fields/SecretInput/SecretInput.test.tsx index f1619aacba..d4ddaea537 100644 --- a/plugins/scaffolder/src/components/fields/SecretInput/SecretInput.test.tsx +++ b/plugins/scaffolder/src/components/fields/SecretInput/SecretInput.test.tsx @@ -19,6 +19,7 @@ import { } from '@backstage/plugin-scaffolder-react'; import { SecretInput } from './SecretInput'; import { renderInTestApp } from '@backstage/test-utils'; +import { ScaffolderRJSFFormProps as FormProps } from '@backstage/plugin-scaffolder-react'; import { Form } from '@backstage/plugin-scaffolder-react/alpha'; import validator from '@rjsf/validator-ajv8'; import { fireEvent, act, waitFor } from '@testing-library/react'; @@ -73,4 +74,92 @@ describe('<SecretInput />', () => { { timeout: 500 }, ); }); + + describe('SecretInput description', () => { + const description = { + fromSchema: 'MyGroupsPicker description from schema', + fromUiSchema: 'MyGroupsPicker description from uiSchema', + } as { fromSchema: string; fromUiSchema: string }; + + it('omits description', async () => { + const props = { + validator, + schema: { + properties: { myKey: { type: 'string', title: 'secret' } }, + }, + uiSchema: { + myKey: { + 'ui:field': 'Secret', + }, + }, + fields: { + Secret: SecretInput, + }, + } as unknown as FormProps<any>; + + const { queryByText } = await renderInTestApp( + <SecretsContextProvider> + <Form {...props} /> + <SecretsComponent /> + </SecretsContextProvider>, + ); + expect(queryByText(description.fromSchema)).toBe(null); + expect(queryByText(description.fromUiSchema)).toBe(null); + }); + + it('presents schema description', async () => { + const props = { + validator, + schema: { + properties: { myKey: { type: 'string', title: 'secret' } }, + description: description.fromSchema, + }, + uiSchema: { + myKey: { + 'ui:field': 'Secret', + }, + }, + fields: { + Secret: SecretInput, + }, + } as unknown as FormProps<any>; + + const { getByText, queryByText } = await renderInTestApp( + <SecretsContextProvider> + <Form {...props} /> + <SecretsComponent /> + </SecretsContextProvider>, + ); + expect(getByText(description.fromSchema)).toBeInTheDocument(); + expect(queryByText(description.fromUiSchema)).toBe(null); + }); + + it('presents uiSchema description', async () => { + const props = { + validator, + schema: { + properties: { myKey: { type: 'string', title: 'secret' } }, + description: description.fromSchema, + }, + uiSchema: { + myKey: { + 'ui:field': 'Secret', + }, + 'ui:description': description.fromUiSchema, + }, + fields: { + Secret: SecretInput, + }, + } as unknown as FormProps<any>; + + const { getByText, queryByText } = await renderInTestApp( + <SecretsContextProvider> + <Form {...props} /> + <SecretsComponent /> + </SecretsContextProvider>, + ); + expect(queryByText(description.fromSchema)).toBe(null); + expect(getByText(description.fromUiSchema)).toBeInTheDocument(); + }); + }); }); diff --git a/plugins/scaffolder/src/components/fields/SecretInput/SecretInput.tsx b/plugins/scaffolder/src/components/fields/SecretInput/SecretInput.tsx index d3bfd09502..b21356d6a8 100644 --- a/plugins/scaffolder/src/components/fields/SecretInput/SecretInput.tsx +++ b/plugins/scaffolder/src/components/fields/SecretInput/SecretInput.tsx @@ -26,12 +26,13 @@ export const SecretInput = (props: ScaffolderRJSFFieldProps) => { disabled, errors, required, + uiSchema, } = props; return ( <ScaffolderField rawErrors={rawErrors} - rawDescription={description} + rawDescription={uiSchema['ui:description'] ?? description} disabled={disabled} errors={errors} required={required} diff --git a/plugins/scaffolder/src/deprecated.ts b/plugins/scaffolder/src/deprecated.ts index 907871946c..f3230ab287 100644 --- a/plugins/scaffolder/src/deprecated.ts +++ b/plugins/scaffolder/src/deprecated.ts @@ -21,22 +21,8 @@ */ import { - createScaffolderFieldExtension as createScaffolderFieldExtensionTemp, - ScaffolderFieldExtensions as ScaffolderFieldExtensionsTemp, - useTemplateSecrets as useTemplateSecretsTemp, - scaffolderApiRef as scaffolderApiRefTemp, - createScaffolderLayout as createScaffolderLayoutTemp, - ScaffolderLayouts as ScaffolderLayoutsTemp, - type LayoutOptions as LayoutOptionsTemp, - type LayoutTemplate as LayoutTemplateTemp, type ScaffolderApi as ScaffolderApiTemp, - type ScaffolderUseTemplateSecrets as ScaffolderUseTemplateSecretsTemp, type TemplateParameterSchema as TemplateParameterSchemaTemp, - type CustomFieldExtensionSchema as CustomFieldExtensionSchemaTemp, - type CustomFieldValidator as CustomFieldValidatorTemp, - type FieldExtensionOptions as FieldExtensionOptionsTemp, - type FieldExtensionComponentProps as FieldExtensionComponentPropsTemp, - type FieldExtensionComponent as FieldExtensionComponentTemp, type ListActionsResponse as ListActionsResponseTemp, type LogEvent as LogEventTemp, type ScaffolderDryRunOptions as ScaffolderDryRunOptionsTemp, @@ -50,6 +36,23 @@ import { type ScaffolderTask as ScaffolderTaskTemp, type ScaffolderTaskOutput as ScaffolderTaskOutputTemp, type ScaffolderTaskStatus as ScaffolderTaskStatusTemp, +} from '@backstage/plugin-scaffolder-common'; + +import { + createScaffolderFieldExtension as createScaffolderFieldExtensionTemp, + ScaffolderFieldExtensions as ScaffolderFieldExtensionsTemp, + useTemplateSecrets as useTemplateSecretsTemp, + scaffolderApiRef as scaffolderApiRefTemp, + createScaffolderLayout as createScaffolderLayoutTemp, + ScaffolderLayouts as ScaffolderLayoutsTemp, + type LayoutOptions as LayoutOptionsTemp, + type LayoutTemplate as LayoutTemplateTemp, + type ScaffolderUseTemplateSecrets as ScaffolderUseTemplateSecretsTemp, + type CustomFieldExtensionSchema as CustomFieldExtensionSchemaTemp, + type CustomFieldValidator as CustomFieldValidatorTemp, + type FieldExtensionOptions as FieldExtensionOptionsTemp, + type FieldExtensionComponentProps as FieldExtensionComponentPropsTemp, + type FieldExtensionComponent as FieldExtensionComponentTemp, } from '@backstage/plugin-scaffolder-react'; import { rootRouteRef as rootRouteRefTemp } from './routes'; @@ -82,7 +85,7 @@ export const useTemplateSecrets = useTemplateSecretsTemp; export const scaffolderApiRef = scaffolderApiRefTemp; /** * @public - * @deprecated use import from {@link @backstage/plugin-scaffolder-react#ScaffolderApi} instead as this has now been moved. + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderApi} instead as this has now been moved. */ export type ScaffolderApi = ScaffolderApiTemp; /** @@ -92,7 +95,7 @@ export type ScaffolderApi = ScaffolderApiTemp; export type ScaffolderUseTemplateSecrets = ScaffolderUseTemplateSecretsTemp; /** * @public - * @deprecated use import from {@link @backstage/plugin-scaffolder-react#TemplateParameterSchema} instead as this has now been moved. + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#TemplateParameterSchema} instead as this has now been moved. */ export type TemplateParameterSchema = TemplateParameterSchemaTemp; /** @@ -127,69 +130,69 @@ export type FieldExtensionComponent<_TReturnValue, _TInputProps> = FieldExtensionComponentTemp<_TReturnValue, _TInputProps>; /** * @public - * @deprecated use import from {@link @backstage/plugin-scaffolder-react#ListActionsResponse} instead as this has now been moved. + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ListActionsResponse} instead as this has now been moved. */ export type ListActionsResponse = ListActionsResponseTemp; /** * @public - * @deprecated use import from {@link @backstage/plugin-scaffolder-react#LogEvent} instead as this has now been moved. + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#LogEvent} instead as this has now been moved. */ export type LogEvent = LogEventTemp; /** * @public - * @deprecated use import from {@link @backstage/plugin-scaffolder-react#ScaffolderDryRunOptions} instead as this has now been moved. + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderDryRunOptions} instead as this has now been moved. */ export type ScaffolderDryRunOptions = ScaffolderDryRunOptionsTemp; /** * @public - * @deprecated use import from {@link @backstage/plugin-scaffolder-react#ScaffolderDryRunResponse} instead as this has now been moved. + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderDryRunResponse} instead as this has now been moved. */ export type ScaffolderDryRunResponse = ScaffolderDryRunResponseTemp; /** * @public - * @deprecated use import from {@link @backstage/plugin-scaffolder-react#ScaffolderGetIntegrationsListOptions} instead as this has now been moved. + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderGetIntegrationsListOptions} instead as this has now been moved. */ export type ScaffolderGetIntegrationsListOptions = ScaffolderGetIntegrationsListOptionsTemp; /** * @public - * @deprecated use import from {@link @backstage/plugin-scaffolder-react#ScaffolderGetIntegrationsListResponse} instead as this has now been moved. + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderGetIntegrationsListResponse} instead as this has now been moved. */ export type ScaffolderGetIntegrationsListResponse = ScaffolderGetIntegrationsListResponseTemp; /** * @public - * @deprecated use import from {@link @backstage/plugin-scaffolder-react#ScaffolderOutputlink} instead as this has now been moved. + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderOutputlink} instead as this has now been moved. */ export type ScaffolderOutputlink = ScaffolderOutputLinkTemp; /** * @public - * @deprecated use import from {@link @backstage/plugin-scaffolder-react#ScaffolderScaffoldOptions} instead as this has now been moved. + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderScaffoldOptions} instead as this has now been moved. */ export type ScaffolderScaffoldOptions = ScaffolderScaffoldOptionsTemp; /** * @public - * @deprecated use import from {@link @backstage/plugin-scaffolder-react#ScaffolderScaffoldResponse} instead as this has now been moved. + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderScaffoldResponse} instead as this has now been moved. */ export type ScaffolderScaffoldResponse = ScaffolderScaffoldResponseTemp; /** * @public - * @deprecated use import from {@link @backstage/plugin-scaffolder-react#ScaffolderStreamLogsOptions} instead as this has now been moved. + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderStreamLogsOptions} instead as this has now been moved. */ export type ScaffolderStreamLogsOptions = ScaffolderStreamLogsOptionsTemp; /** * @public - * @deprecated use import from {@link @backstage/plugin-scaffolder-react#ScaffolderTask} instead as this has now been moved. + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderTask} instead as this has now been moved. */ export type ScaffolderTask = ScaffolderTaskTemp; /** * @public - * @deprecated use import from {@link @backstage/plugin-scaffolder-react#ScaffolderTaskOutput} instead as this has now been moved. + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderTaskOutput} instead as this has now been moved. */ export type ScaffolderTaskOutput = ScaffolderTaskOutputTemp; /** * @public - * @deprecated use import from {@link @backstage/plugin-scaffolder-react#ScaffolderTaskStatus} instead as this has now been moved. + * @deprecated use import from {@link @backstage/plugin-scaffolder-common#ScaffolderTaskStatus} instead as this has now been moved. */ export type ScaffolderTaskStatus = ScaffolderTaskStatusTemp; /** diff --git a/plugins/scaffolder/src/translation.ts b/plugins/scaffolder/src/translation.ts index a5419dd6fa..78ca15e2ff 100644 --- a/plugins/scaffolder/src/translation.ts +++ b/plugins/scaffolder/src/translation.ts @@ -19,6 +19,9 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; export const scaffolderTranslationRef = createTranslationRef({ id: 'scaffolder', messages: { + aboutCard: { + launchTemplate: 'Launch Template', + }, actionsPage: { title: 'Installed actions', pageTitle: 'Create a New Component', @@ -51,6 +54,10 @@ export const scaffolderTranslationRef = createTranslationRef({ description: "Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters", }, + multiEntityPicker: { + title: 'Entity', + description: 'An entity from the catalog', + }, myGroupsPicker: { title: 'Entity', description: 'An entity from the catalog', @@ -219,6 +226,7 @@ export const scaffolderTranslationRef = createTranslationRef({ functions: { title: 'Functions', notAvailable: 'There are no global template functions defined.', + metadataAbsent: 'Function metadata unavailable', schema: { arguments: 'Arguments', output: 'Output', diff --git a/plugins/search-backend-module-catalog/CHANGELOG.md b/plugins/search-backend-module-catalog/CHANGELOG.md index 0ba985b2ff..1fcd8bfeec 100644 --- a/plugins/search-backend-module-catalog/CHANGELOG.md +++ b/plugins/search-backend-module-catalog/CHANGELOG.md @@ -1,5 +1,141 @@ # @backstage/plugin-search-backend-module-catalog +## 0.3.7-next.1 + +### Patch Changes + +- d9bda0f: Allow filter to be an array in config schema +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/plugin-search-common@1.2.19 + +## 0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-common@1.2.19 + +## 0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + - @backstage/plugin-search-common@1.2.18 + +## 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 + +## 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 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## 0.3.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 0.3.4-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-catalog/config.d.ts b/plugins/search-backend-module-catalog/config.d.ts index be9122b907..0c4302a9c4 100644 --- a/plugins/search-backend-module-catalog/config.d.ts +++ b/plugins/search-backend-module-catalog/config.d.ts @@ -36,7 +36,7 @@ export interface Config { * * Defaults to no filter, ie indexing all entities. */ - filter?: object; + filter?: object | object[]; /** * The number of entities to process at a time. Keep this at a * reasonable number to avoid overloading either the catalog or the diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index 0ffb660686..4ea8f72d22 100644 --- a/plugins/search-backend-module-catalog/package.json +++ b/plugins/search-backend-module-catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-catalog", - "version": "0.3.4-next.1", + "version": "0.3.7-next.1", "description": "A module for the search backend that exports catalog modules", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index 08897eb4c3..ab1724ef3f 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,95 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-search-common@1.2.19 + +## 1.7.4 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## 1.7.4-next.0 + +### Patch Changes + +- 3507fcd: Just some more circular dep cleanup +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/integration-aws-node@0.1.17-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## 1.7.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-search-common@1.2.18 + +## 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 + +## 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 + +## 1.7.2 + +### Patch Changes + +- 01f772c: Fixed an issue where the `search.elasticsearch.queryOptions` config were not picked up by the `ElasticSearchSearchEngine`. +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/integration-aws-node@0.1.16 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## 1.7.2-next.3 + +### Patch Changes + +- 01f772c: Fixed an issue where the `search.elasticsearch.queryOptions` config were not picked up by the `ElasticSearchSearchEngine`. +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 1.7.2-next.2 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index fb9399cf2d..2f07184948 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", - "version": "1.7.2-next.2", + "version": "1.7.5-next.0", "description": "A module for the search backend that implements search using ElasticSearch", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-elasticsearch/report.api.md b/plugins/search-backend-module-elasticsearch/report.api.md index 9250cfda31..19c44bd566 100644 --- a/plugins/search-backend-module-elasticsearch/report.api.md +++ b/plugins/search-backend-module-elasticsearch/report.api.md @@ -11,7 +11,6 @@ import { BulkHelper } from '@elastic/elasticsearch/lib/Helpers'; import { BulkStats } from '@elastic/elasticsearch/lib/Helpers'; import { Config } from '@backstage/config'; import type { ConnectionOptions } from 'tls'; -import { ElasticSearchQueryTranslator as ElasticSearchQueryTranslator_2 } from '@backstage/plugin-search-backend-module-elasticsearch'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { IndexableDocument } from '@backstage/plugin-search-common'; import { IndexableResultSet } from '@backstage/plugin-search-common'; @@ -332,7 +331,7 @@ export type ElasticSearchQueryTranslator = ( // @public (undocumented) export interface ElasticSearchQueryTranslatorExtensionPoint { // (undocumented) - setTranslator(translator: ElasticSearchQueryTranslator_2): void; + setTranslator(translator: ElasticSearchQueryTranslator): void; } // @public diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts index e2c8a48cd6..2bd5c88f89 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts @@ -192,6 +192,9 @@ export class ElasticSearchSearchEngine implements SearchEngine { config.getOptional<ElasticSearchHighlightOptions>( 'search.elasticsearch.highlightOptions', ), + config.getOptional<ElasticSearchQueryConfig>( + 'search.elasticsearch.queryOptions', + ), ); for (const indexTemplate of this.readIndexTemplateConfig( diff --git a/plugins/search-backend-module-elasticsearch/src/module.ts b/plugins/search-backend-module-elasticsearch/src/module.ts index c5c2a6fc1d..d4ce249093 100644 --- a/plugins/search-backend-module-elasticsearch/src/module.ts +++ b/plugins/search-backend-module-elasticsearch/src/module.ts @@ -22,7 +22,7 @@ import { searchEngineRegistryExtensionPoint } from '@backstage/plugin-search-bac import { ElasticSearchQueryTranslator, ElasticSearchSearchEngine, -} from '@backstage/plugin-search-backend-module-elasticsearch'; +} from './engines/ElasticSearchSearchEngine'; /** @public */ export interface ElasticSearchQueryTranslatorExtensionPoint { diff --git a/plugins/search-backend-module-explore/CHANGELOG.md b/plugins/search-backend-module-explore/CHANGELOG.md index b2fa21aa3b..fece49ed99 100644 --- a/plugins/search-backend-module-explore/CHANGELOG.md +++ b/plugins/search-backend-module-explore/CHANGELOG.md @@ -1,5 +1,83 @@ # @backstage/plugin-search-backend-module-explore +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/plugin-search-common@1.2.19 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## 0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-search-common@1.2.18 + +## 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 + +## 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 + +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 0.3.2-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-explore/package.json b/plugins/search-backend-module-explore/package.json index e57ea07e00..bf910db383 100644 --- a/plugins/search-backend-module-explore/package.json +++ b/plugins/search-backend-module-explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-explore", - "version": "0.3.2-next.1", + "version": "0.3.5-next.0", "description": "A module for the search backend that exports explore modules", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index def19bf30b..4de8332639 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,83 @@ # @backstage/plugin-search-backend-module-pg +## 0.5.47-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/plugin-search-common@1.2.19 + +## 0.5.46 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## 0.5.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## 0.5.45 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-search-common@1.2.18 + +## 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 + +## 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 + +## 0.5.44 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## 0.5.44-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 0.5.44-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 0eb67810d4..1e4450cd0f 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-pg", - "version": "0.5.44-next.1", + "version": "0.5.47-next.0", "description": "A module for the search backend that implements search using PostgreSQL", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md index 3aa92b12ec..f153ca4b2b 100644 --- a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md +++ b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md @@ -1,5 +1,83 @@ # @backstage/plugin-search-backend-module-stack-overflow-collator +## 0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/plugin-search-common@1.2.19 + +## 0.3.11 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## 0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-search-common@1.2.18 + +## 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 + +## 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 + +## 0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## 0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 0.3.9-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-stack-overflow-collator/package.json b/plugins/search-backend-module-stack-overflow-collator/package.json index 465627c118..2e31d85d7d 100644 --- a/plugins/search-backend-module-stack-overflow-collator/package.json +++ b/plugins/search-backend-module-stack-overflow-collator/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-stack-overflow-collator", - "version": "0.3.9-next.1", + "version": "0.3.12-next.0", "description": "A module for the search backend that exports stack overflow modules", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-techdocs/CHANGELOG.md b/plugins/search-backend-module-techdocs/CHANGELOG.md index 4af1906d57..dd16923cb8 100644 --- a/plugins/search-backend-module-techdocs/CHANGELOG.md +++ b/plugins/search-backend-module-techdocs/CHANGELOG.md @@ -1,5 +1,145 @@ # @backstage/plugin-search-backend-module-techdocs +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-techdocs-node@1.13.6-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-common@1.2.19 + +## 0.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/plugin-techdocs-node@1.13.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-techdocs-node@1.13.5-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-techdocs-node@1.13.5-next.0 + - @backstage/catalog-client@1.10.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-common@1.2.18 + +## 0.4.3 + +### Patch Changes + +- b47ec38: Exports the default document collator for use in document transformation during search indexing. +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-node@1.13.4 + +## 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 + +## 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 + +## 0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-techdocs-node@1.13.3 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## 0.4.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-node@1.13.3-next.3 + ## 0.4.2-next.2 ### Patch Changes diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index 8d088fe1b7..301aa7827e 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-techdocs", - "version": "0.4.2-next.2", + "version": "0.4.5-next.0", "description": "A module for the search backend that exports techdocs modules", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-techdocs/report.api.md b/plugins/search-backend-module-techdocs/report.api.md index d0acf61f6f..fd5bc3db2e 100644 --- a/plugins/search-backend-module-techdocs/report.api.md +++ b/plugins/search-backend-module-techdocs/report.api.md @@ -13,6 +13,9 @@ import { TechDocsDocument } from '@backstage/plugin-techdocs-node'; const _default: BackendFeature; export default _default; +// @public (undocumented) +export const defaultTechDocsCollatorDocumentTransformer: TechDocsCollatorDocumentTransformer; + // @public (undocumented) export const defaultTechDocsCollatorEntityTransformer: TechDocsCollatorEntityTransformer; diff --git a/plugins/search-backend-module-techdocs/src/collators/index.ts b/plugins/search-backend-module-techdocs/src/collators/index.ts index 695ce8f0e8..b517cd6d13 100644 --- a/plugins/search-backend-module-techdocs/src/collators/index.ts +++ b/plugins/search-backend-module-techdocs/src/collators/index.ts @@ -15,7 +15,7 @@ */ export { defaultTechDocsCollatorEntityTransformer } from './defaultTechDocsCollatorEntityTransformer'; - +export { defaultTechDocsCollatorDocumentTransformer } from './defaultTechDocsCollatorDocumentTransformer'; export type { TechDocsCollatorEntityTransformer } from './TechDocsCollatorEntityTransformer'; export type { diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index 28e245a5ef..5963068f3f 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,87 @@ # @backstage/plugin-search-backend-node +## 1.3.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-common@1.2.19 + +## 1.3.13 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-search-common@1.2.19 + +## 1.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## 1.3.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-search-common@1.2.18 + +## 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 + +## 1.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## 1.3.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.18 + +## 1.3.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 1.3.11-next.1 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index ab6f99462c..dfcbff9383 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-node", - "version": "1.3.11-next.1", + "version": "1.3.14-next.0", "description": "A library for Backstage backend plugins that want to interact with the search backend plugin", "backstage": { "role": "node-library", diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index 9f51eb47ea..064d1b5530 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,159 @@ # @backstage/plugin-search-backend +## 2.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/backend-openapi-utils@0.6.0-next.0 + - @backstage/plugin-permission-node@0.10.3-next.0 + - @backstage/plugin-search-backend-node@1.3.14-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-common@1.2.19 + +## 2.0.4 + +### Patch Changes + +- 69fb975: Error messages should not contain backend SQL query strings in the API response, this change will ensure that messages are logged and empty response is returned to the user +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-node@0.10.2 + - @backstage/backend-defaults@0.11.1 + - @backstage/backend-openapi-utils@0.5.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-search-backend-node@1.3.13 + - @backstage/plugin-search-common@1.2.19 + +## 2.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/plugin-permission-node@0.10.2-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-node@1.3.13-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/backend-openapi-utils@0.5.5-next.0 + +## 2.0.4-next.0 + +### Patch Changes + +- 69fb975: Error messages should not contain backend SQL query strings in the API response, this change will ensure that messages are logged and empty response is returned to the user +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-permission-node@0.10.1 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/backend-openapi-utils@0.5.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-search-common@1.2.18 + +## 2.0.3 + +### Patch Changes + +- c83cd8b: Fixed some circular or otherwise unclear imports +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/backend-openapi-utils@0.5.4 + - @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 + - @backstage/plugin-search-backend-node@1.3.12 + - @backstage/plugin-search-common@1.2.18 + +## 2.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @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 + +## 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 + +## 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 + +## 2.0.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.0 + - @backstage/backend-openapi-utils@0.5.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-search-backend-node@1.3.11 + - @backstage/plugin-search-common@1.2.18 + +## 2.0.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/backend-openapi-utils@0.5.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-permission-node@0.10.0-next.2 + - @backstage/plugin-search-backend-node@1.3.11-next.2 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 2.0.2-next.2 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 57b212d906..bf9ab8b523 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "2.0.2-next.2", + "version": "2.0.5-next.0", "description": "The Backstage backend plugin that provides your backstage app with search", "backstage": { "role": "backend-plugin", diff --git a/plugins/search-backend/src/schema/openapi/generated/router.ts b/plugins/search-backend/src/schema/openapi/generated/router.ts index cf972d9f6c..a2510346ca 100644 --- a/plugins/search-backend/src/schema/openapi/generated/router.ts +++ b/plugins/search-backend/src/schema/openapi/generated/router.ts @@ -18,7 +18,7 @@ // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** import { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage/backend-openapi-utils'; -import { EndpointMap } from './'; +import { EndpointMap } from './apis'; export const spec = { openapi: '3.0.3', diff --git a/plugins/search-backend/src/service/router.test.ts b/plugins/search-backend/src/service/router.test.ts index 5aea71b7c1..623b78016a 100644 --- a/plugins/search-backend/src/service/router.test.ts +++ b/plugins/search-backend/src/service/router.test.ts @@ -106,7 +106,7 @@ describe('createRouter', () => { expect.objectContaining({ error: { name: 'Error', - message: `There was a problem performing the search query: ${error.message}`, + message: `There was a problem performing the search query`, }, }), ); diff --git a/plugins/search-backend/src/service/router.ts b/plugins/search-backend/src/service/router.ts index 0ba5eede81..eedd84105a 100644 --- a/plugins/search-backend/src/service/router.ts +++ b/plugins/search-backend/src/service/router.ts @@ -197,14 +197,17 @@ export async function createRouter( res.json(filterResultSet(toSearchResults(resultSet))); } catch (error) { + // Log the error message here, but don't expose it to the user in the response + logger.error( + `There was a problem performing the search query: ${error.message}`, + ); if (error.name === 'MissingIndexError') { // re-throw and let the default error handler middleware captures it and serializes it with the right response code on the standard form throw error; } - throw new Error( - `There was a problem performing the search query: ${error.message}`, - ); + // If the error is not a MissingIndexError, we want to throw a generic error without the error message as it may leak internal information + throw new Error(`There was a problem performing the search query`); } }); diff --git a/plugins/search-common/CHANGELOG.md b/plugins/search-common/CHANGELOG.md index 62f058ccc5..ef5deee025 100644 --- a/plugins/search-common/CHANGELOG.md +++ b/plugins/search-common/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-search-common +## 1.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1 + +## 1.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.1-next.0 + +## 1.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.0 + - @backstage/types@1.2.1 + ## 1.2.18-next.0 ### Patch Changes diff --git a/plugins/search-common/package.json b/plugins/search-common/package.json index fcd8cf3eaf..582e6b3434 100644 --- a/plugins/search-common/package.json +++ b/plugins/search-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-common", - "version": "1.2.18-next.0", + "version": "1.2.19", "description": "Common functionalities for Search, to be shared between various search-enabled plugins", "backstage": { "role": "common-library", diff --git a/plugins/search-react/CHANGELOG.md b/plugins/search-react/CHANGELOG.md index a6c9421949..6109252329 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,135 @@ # @backstage/plugin-search-react +## 1.9.3-next.1 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-components@0.17.5-next.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.8-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.19 + +## 1.9.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.19 + +## 1.9.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-search-common@1.2.19 + +## 1.9.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 1.9.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + +## 1.9.1 + +### Patch Changes + +- 869fa46: SearchBar clear button support i18n +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + +## 1.9.1-next.1 + +### Patch Changes + +- 869fa46: SearchBar clear button support i18n +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/core-plugin-api@1.10.7 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + +## 1.9.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + +## 1.9.0 + +### Minor Changes + +- 611c941: Allow search filters to provide labels and values separately, and not only values + +### Patch Changes + +- 2c76614: Fix memoization of `filterValue` in `SearchFilter.Autocomplete` to prevent unintended resets +- fa48594: search plugin support i18n +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + +## 1.9.0-next.2 + +### Patch Changes + +- 2c76614: Fix memoization of `filterValue` in `SearchFilter.Autocomplete` to prevent unintended resets +- fa48594: search plugin support i18n +- Updated dependencies + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 1.9.0-next.1 ### Patch Changes diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index 59bc492e10..4c7cd939a7 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-react", - "version": "1.9.0-next.1", + "version": "1.9.3-next.1", "backstage": { "role": "web-library", "pluginId": "search", diff --git a/plugins/search-react/report-alpha.api.md b/plugins/search-react/report-alpha.api.md index 98a3c533db..59d0ed17f9 100644 --- a/plugins/search-react/report-alpha.api.md +++ b/plugins/search-react/report-alpha.api.md @@ -5,9 +5,11 @@ ```ts import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ListItemProps } from '@material-ui/core/ListItem'; import { SearchDocument } from '@backstage/plugin-search-common'; import { SearchResult } from '@backstage/plugin-search-common'; +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) export type BaseSearchResultListItemProps<T = {}> = T & { @@ -18,9 +20,8 @@ export type BaseSearchResultListItemProps<T = {}> = T & { // @alpha (undocumented) export const SearchFilterBlueprint: ExtensionBlueprint<{ kind: 'search-filter'; - name: undefined; params: SearchFilterBlueprintParams; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { component: SearchFilterExtensionComponent; }, @@ -60,9 +61,8 @@ export type SearchFilterExtensionComponentProps = { // @alpha (undocumented) export const SearchFilterResultTypeBlueprint: ExtensionBlueprint<{ kind: 'search-filter-result-type'; - name: undefined; params: SearchFilterResultTypeBlueprintParams; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { value: string; name: string; @@ -94,6 +94,24 @@ export interface SearchFilterResultTypeBlueprintParams { value: string; } +// @alpha (undocumented) +export const searchReactTranslationRef: TranslationRef< + 'search-react', + { + readonly 'searchBar.title': 'Search'; + readonly 'searchBar.placeholder': 'Search in {{org}}'; + readonly 'searchBar.clearButtonTitle': 'Clear'; + readonly 'searchFilter.allOptionTitle': 'All'; + readonly 'searchPagination.limitLabel': 'Results per page:'; + readonly 'searchPagination.limitText': 'of {{num}}'; + readonly noResultsDescription: 'Sorry, no results were found'; + readonly 'searchResultGroup.linkTitle': 'See All'; + readonly 'searchResultGroup.addFilterButtonTitle': 'Add filter'; + readonly 'searchResultPager.next': 'Next'; + readonly 'searchResultPager.previous': 'Previous'; + } +>; + // @alpha (undocumented) export type SearchResultItemExtensionComponent = < P extends BaseSearchResultListItemProps, @@ -109,9 +127,8 @@ export type SearchResultItemExtensionPredicate = ( // @alpha export const SearchResultListItemBlueprint: ExtensionBlueprint<{ kind: 'search-result-list-item'; - name: undefined; params: SearchResultListItemBlueprintParams; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { predicate?: SearchResultItemExtensionPredicate; component: SearchResultItemExtensionComponent; diff --git a/plugins/search-react/src/alpha/blueprints/SearchFilterBlueprint.test.tsx b/plugins/search-react/src/alpha/blueprints/SearchFilterBlueprint.test.tsx index e8ca2d5888..adfceeec47 100644 --- a/plugins/search-react/src/alpha/blueprints/SearchFilterBlueprint.test.tsx +++ b/plugins/search-react/src/alpha/blueprints/SearchFilterBlueprint.test.tsx @@ -73,7 +73,7 @@ describe('SearchFilterBlueprint', () => { }, factory(originalFactory, { inputs }) { return originalFactory({ - defaultPath: '/', + path: '/', loader: async () => { const searchFilters = inputs.searchFilters.map( t => t.get(searchFilterDataRef).component, diff --git a/plugins/search-react/src/alpha/blueprints/SearchFilterResultTypeBlueprint.test.tsx b/plugins/search-react/src/alpha/blueprints/SearchFilterResultTypeBlueprint.test.tsx index 3ebc4c5a24..d9f8e97080 100644 --- a/plugins/search-react/src/alpha/blueprints/SearchFilterResultTypeBlueprint.test.tsx +++ b/plugins/search-react/src/alpha/blueprints/SearchFilterResultTypeBlueprint.test.tsx @@ -77,7 +77,7 @@ describe('SearchFilterResultTypeBlueprint', () => { }, factory(originalFactory, { inputs }) { return originalFactory({ - defaultPath: '/', + path: '/', loader: async () => { const resultTypes = inputs.resultTypes.map(t => t.get(searchResultTypeDataRef), diff --git a/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.test.tsx b/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.test.tsx index 1accf8f3d4..112c0a8470 100644 --- a/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.test.tsx +++ b/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.test.tsx @@ -91,7 +91,7 @@ describe('SearchResultListItemBlueprint', () => { }, factory(originalFactory, { inputs }) { return originalFactory({ - defaultPath: '/', + path: '/', loader: async () => { const items = inputs.items.map(i => i.get(searchResultListItemDataRef), diff --git a/plugins/search-react/src/alpha/index.ts b/plugins/search-react/src/alpha/index.ts index 239ffa89dd..7c44f59d56 100644 --- a/plugins/search-react/src/alpha/index.ts +++ b/plugins/search-react/src/alpha/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export * from './blueprints'; +export { searchReactTranslationRef } from '../translation'; diff --git a/plugins/search-react/src/components/SearchBar/SearchBar.tsx b/plugins/search-react/src/components/SearchBar/SearchBar.tsx index 98bfe81b32..41074a22fb 100644 --- a/plugins/search-react/src/components/SearchBar/SearchBar.tsx +++ b/plugins/search-react/src/components/SearchBar/SearchBar.tsx @@ -38,6 +38,8 @@ import { } from 'react'; import useDebounce from 'react-use/esm/useDebounce'; import { SearchContextProvider, useSearch } from '../../context'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchReactTranslationRef } from '../../translation'; /** * Props for {@link SearchBarBase}. @@ -81,6 +83,7 @@ export const SearchBarBase = forwardRef((props: SearchBarBaseProps, ref) => { const configApi = useApi(configApiRef); const [value, setValue] = useState<string>(''); const forwardedValueRef = useRef<string>(''); + const { t } = useTranslationRef(searchReactTranslationRef); useEffect(() => { setValue(prevValue => { @@ -129,12 +132,15 @@ export const SearchBarBase = forwardRef((props: SearchBarBaseProps, ref) => { } }, [onChange, onClear]); - const ariaLabel: string | undefined = label ? undefined : 'Search'; + const ariaLabel: string | undefined = label + ? undefined + : t('searchBar.title'); const inputPlaceholder = placeholder ?? - `Search in ${configApi.getOptionalString('app.title') || 'Backstage'}`; - + t('searchBar.placeholder', { + org: configApi.getOptionalString('app.title') || 'Backstage', + }); const SearchIcon = useApp().getSystemIcon('search') || DefaultSearchIcon; const startAdornment = ( @@ -148,7 +154,7 @@ export const SearchBarBase = forwardRef((props: SearchBarBaseProps, ref) => { const clearButtonEndAdornment = ( <InputAdornment position="end"> <Button - aria-label="Clear" + aria-label={t('searchBar.clearButtonTitle')} size="small" onClick={handleClear} onKeyDown={event => { @@ -158,7 +164,7 @@ export const SearchBarBase = forwardRef((props: SearchBarBaseProps, ref) => { } }} > - Clear + {t('searchBar.clearButtonTitle')} </Button> </InputAdornment> ); diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.test.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.test.tsx index 736e3f3f93..d8d3b75aa2 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.test.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.test.tsx @@ -14,14 +14,18 @@ * limitations under the License. */ -import { screen, render, waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { configApiRef } from '@backstage/core-plugin-api'; import { SearchContextProvider } from '../../context'; import { SearchFilter } from './SearchFilter'; -import { mockApis, TestApiProvider } from '@backstage/test-utils'; +import { + mockApis, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; import { searchApiRef } from '../../api'; describe('SearchFilter', () => { @@ -55,14 +59,16 @@ describe('SearchFilter', () => { it('Check that element was rendered and received props', async () => { const CustomFilter = (props: { name: string }) => <h6>{props.name}</h6>; - render(<SearchFilter name={name} component={CustomFilter} />); + await renderInTestApp( + <SearchFilter name={name} component={CustomFilter} />, + ); expect(screen.getByRole('heading', { name })).toBeInTheDocument(); }); describe('Checkbox', () => { it('Renders field name and values when provided as props', async () => { - render( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -88,7 +94,7 @@ describe('SearchFilter', () => { }); it('Renders correctly based on filter state', async () => { - render( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -119,7 +125,7 @@ describe('SearchFilter', () => { }); it('Renders correctly based on defaultValue', async () => { - render( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -148,7 +154,7 @@ describe('SearchFilter', () => { }); it('Checking / unchecking a value sets filter state', async () => { - render( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -185,7 +191,7 @@ describe('SearchFilter', () => { }); it('Checking / unchecking a value maintains unrelated filter state', async () => { - render( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -226,7 +232,7 @@ describe('SearchFilter', () => { describe('Select', () => { it('Renders field name and values when provided as props', async () => { - render( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -258,7 +264,7 @@ describe('SearchFilter', () => { }); it('Renders values when provided asynchronously', async () => { - render( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -299,7 +305,7 @@ describe('SearchFilter', () => { }); it('Renders correctly based on filter state', async () => { - render( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -342,7 +348,7 @@ describe('SearchFilter', () => { }); it('Renders correctly based on defaultValue', async () => { - render( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -383,7 +389,7 @@ describe('SearchFilter', () => { }); it('Selecting a value sets filter state', async () => { - render( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -436,7 +442,7 @@ describe('SearchFilter', () => { }); it('Selecting a value maintains unrelated filter state', async () => { - render( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.tsx index da96c711c4..1d0ce79f75 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.tsx @@ -31,6 +31,8 @@ import { } from './SearchFilter.Autocomplete'; import { useAsyncFilterValues, useDefaultFilterValue } from './hooks'; import { ensureFilterValueWithLabel, FilterValue } from './types'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchReactTranslationRef } from '../../translation'; const useStyles = makeStyles({ label: { @@ -165,6 +167,7 @@ export const SelectFilter = (props: SearchFilterComponentProps) => { values: givenValues, valuesDebounceMs, } = props; + const { t } = useTranslationRef(searchReactTranslationRef); useDefaultFilterValue(name, defaultValue); const asyncValues = typeof givenValues === 'function' ? givenValues : undefined; @@ -179,7 +182,10 @@ export const SelectFilter = (props: SearchFilterComponentProps) => { valuesDebounceMs, ); const allOptionValue = useRef(uuid()); - const allOption = { value: allOptionValue.current, label: 'All' }; + const allOption = { + value: allOptionValue.current, + label: t('searchFilter.allOptionTitle'), + }; const { filters, setFilters } = useSearch(); const handleChange = (value: SelectedItems) => { diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx index 94d90cff59..d601caadd9 100644 --- a/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx @@ -19,7 +19,7 @@ import userEvent from '@testing-library/user-event'; import { mockApis, - renderWithEffects, + renderInTestApp, TestApiProvider, } from '@backstage/test-utils'; @@ -53,7 +53,7 @@ describe('SearchPagination', () => { }); it('Renders without exploding', async () => { - await renderWithEffects( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -74,7 +74,7 @@ describe('SearchPagination', () => { }); it('Define default page limit options', async () => { - await renderWithEffects( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -99,7 +99,7 @@ describe('SearchPagination', () => { it('Accept custom page limit label', async () => { const label = 'Page limit:'; - await renderWithEffects( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -116,7 +116,7 @@ describe('SearchPagination', () => { }); it('Show the total in text', async () => { - await renderWithEffects( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -133,7 +133,7 @@ describe('SearchPagination', () => { }); it('Accept custom page limit text', async () => { - await renderWithEffects( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -152,7 +152,7 @@ describe('SearchPagination', () => { }); it('Accept custom page limit options', async () => { - await renderWithEffects( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -176,7 +176,7 @@ describe('SearchPagination', () => { }); it('Set page limit in the context', async () => { - await renderWithEffects( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -208,7 +208,7 @@ describe('SearchPagination', () => { pageCursor: 'MQ==', // page: 1 }; - await renderWithEffects( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], @@ -250,7 +250,7 @@ describe('SearchPagination', () => { pageCursor: 'Mg==', // page: 2 }; - await renderWithEffects( + await renderInTestApp( <TestApiProvider apis={[ [searchApiRef, searchApiMock], diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx index 8906b82e79..571fa3f1a1 100644 --- a/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx @@ -23,6 +23,8 @@ import { } from 'react'; import TablePagination from '@material-ui/core/TablePagination'; import { useSearch } from '../../context'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchReactTranslationRef } from '../../translation'; const encodePageCursor = (pageCursor: number): string => { return Buffer.from(pageCursor.toString(), 'utf-8').toString('base64'); @@ -119,15 +121,18 @@ export type SearchPaginationBaseProps = { * @public */ export const SearchPaginationBase = (props: SearchPaginationBaseProps) => { + const { t } = useTranslationRef(searchReactTranslationRef); const { total: count = -1, cursor: pageCursor, hasNextPage, onCursorChange: onPageCursorChange, limit: rowsPerPage = 25, - limitLabel: labelRowsPerPage = 'Results per page:', + limitLabel: labelRowsPerPage = t('searchPagination.limitLabel'), limitText: labelDisplayedRows = ({ from, to }) => - count > 0 ? `of ${count}` : `${from}-${to}`, + count > 0 + ? t('searchPagination.limitText', { num: `${count}` }) + : `${from}-${to}`, limitOptions: rowsPerPageOptions, onLimitChange: onPageLimitChange, ...rest diff --git a/plugins/search-react/src/components/SearchResult/SearchResult.tsx b/plugins/search-react/src/components/SearchResult/SearchResult.tsx index b0f4570885..99c7f1fb3a 100644 --- a/plugins/search-react/src/components/SearchResult/SearchResult.tsx +++ b/plugins/search-react/src/components/SearchResult/SearchResult.tsx @@ -32,6 +32,8 @@ import { SearchResultListItemExtensions, SearchResultListItemExtensionsProps, } from '../../extensions'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchReactTranslationRef } from '../../translation'; /** * Props for {@link SearchResultContext} @@ -186,11 +188,12 @@ export type SearchResultProps = Pick<SearchResultStateProps, 'query'> & * @public */ export const SearchResultComponent = (props: SearchResultProps) => { + const { t } = useTranslationRef(searchReactTranslationRef); const { query, children, noResultsComponent = ( - <EmptyState missing="data" title="Sorry, no results were found" /> + <EmptyState missing="data" title={t('noResultsDescription')} /> ), ...rest } = props; diff --git a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.tsx b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.tsx index 4c2d300314..9a9290bcbc 100644 --- a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.tsx +++ b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.tsx @@ -51,6 +51,8 @@ import { useSearchResultListItemExtensions } from '../../extensions'; import { DefaultResultListItem } from '../DefaultResultListItem'; import { SearchResultState, SearchResultStateProps } from '../SearchResult'; +import { searchReactTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; const useStyles = makeStyles((theme: Theme) => ({ listSubheader: { @@ -356,6 +358,7 @@ export function SearchResultGroupLayout<FilterOption>( ) { const classes = useStyles(); const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); + const { t } = useTranslationRef(searchReactTranslationRef); const { error, @@ -365,7 +368,7 @@ export function SearchResultGroupLayout<FilterOption>( titleProps = {}, link = ( <> - See all + {t('searchResultGroup.linkTitle')} <ArrowRightIcon className={classes.listSubheaderLinkIcon} /> </> ), @@ -387,7 +390,7 @@ export function SearchResultGroupLayout<FilterOption>( ), disableRenderingWithNoResults, noResultsComponent = disableRenderingWithNoResults ? null : ( - <EmptyState missing="data" title="Sorry, no results were found" /> + <EmptyState missing="data" title={t('noResultsDescription')} /> ), ...rest } = props; @@ -434,7 +437,7 @@ export function SearchResultGroupLayout<FilterOption>( component="button" icon={<AddIcon />} variant="outlined" - label="Add filter" + label={t('searchResultGroup.addFilterButtonTitle')} aria-controls="filters-menu" aria-haspopup="true" onClick={handleClick} diff --git a/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx b/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx index 48ed8ac92f..a5459d8619 100644 --- a/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx +++ b/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx @@ -30,6 +30,8 @@ import { useSearchResultListItemExtensions } from '../../extensions'; import { DefaultResultListItem } from '../DefaultResultListItem'; import { SearchResultState, SearchResultStateProps } from '../SearchResult'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchReactTranslationRef } from '../../translation'; /** * Props for {@link SearchResultListLayout} @@ -72,6 +74,7 @@ export type SearchResultListLayoutProps = ListProps & { * @public */ export const SearchResultListLayout = (props: SearchResultListLayoutProps) => { + const { t } = useTranslationRef(searchReactTranslationRef); const { error, loading, @@ -84,7 +87,7 @@ export const SearchResultListLayout = (props: SearchResultListLayoutProps) => { ), disableRenderingWithNoResults, noResultsComponent = disableRenderingWithNoResults ? null : ( - <EmptyState missing="data" title="Sorry, no results were found" /> + <EmptyState missing="data" title={t('noResultsDescription')} /> ), ...rest } = props; diff --git a/plugins/search-react/src/components/SearchResultPager/SearchResultPager.tsx b/plugins/search-react/src/components/SearchResultPager/SearchResultPager.tsx index 764bb28129..d1dca8bb95 100644 --- a/plugins/search-react/src/components/SearchResultPager/SearchResultPager.tsx +++ b/plugins/search-react/src/components/SearchResultPager/SearchResultPager.tsx @@ -20,6 +20,8 @@ import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos'; import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos'; import { useSearch } from '../../context'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchReactTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ root: { @@ -36,6 +38,7 @@ const useStyles = makeStyles(theme => ({ export const SearchResultPager = () => { const { fetchNextPage, fetchPreviousPage } = useSearch(); const classes = useStyles(); + const { t } = useTranslationRef(searchReactTranslationRef); if (!fetchNextPage && !fetchPreviousPage) { return <></>; @@ -49,7 +52,7 @@ export const SearchResultPager = () => { onClick={fetchPreviousPage} startIcon={<ArrowBackIosIcon />} > - Previous + {t('searchResultPager.previous')} </Button> <Button @@ -58,7 +61,7 @@ export const SearchResultPager = () => { onClick={fetchNextPage} endIcon={<ArrowForwardIosIcon />} > - Next + {t('searchResultPager.next')} </Button> </nav> ); diff --git a/plugins/search-react/src/translation.ts b/plugins/search-react/src/translation.ts new file mode 100644 index 0000000000..e4c394d952 --- /dev/null +++ b/plugins/search-react/src/translation.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 { createTranslationRef } from '@backstage/core-plugin-api/alpha'; + +/** + * @alpha + */ +export const searchReactTranslationRef = createTranslationRef({ + id: 'search-react', + messages: { + searchBar: { + title: 'Search', + placeholder: 'Search in {{org}}', + clearButtonTitle: 'Clear', + }, + searchFilter: { + allOptionTitle: 'All', + }, + searchPagination: { + limitLabel: 'Results per page:', + limitText: 'of {{num}}', + }, + noResultsDescription: 'Sorry, no results were found', + searchResultGroup: { + linkTitle: 'See All', + addFilterButtonTitle: 'Add filter', + }, + searchResultPager: { + previous: 'Previous', + next: 'Next', + }, + }, +}); diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 8a4d42569e..e85e2c852e 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,186 @@ # @backstage/plugin-search +## 1.4.29-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-search-react@1.9.3-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.19 + +## 1.4.29-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/plugin-search-react@1.9.3-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.19 + +## 1.4.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-search-react@1.9.2 + +## 1.4.28 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-search-react@1.9.2 + +## 1.4.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 1.4.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/plugin-search-react@1.9.2-next.0 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + +## 1.4.27 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/plugin-search-react@1.9.1 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + +## 1.4.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + +## 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 + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- fa48594: search plugin support i18n +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-search-react@1.9.0 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + +## 1.4.26-next.3 + +### Patch Changes + +- fa48594: search plugin support i18n +- Updated dependencies + - @backstage/plugin-search-react@1.9.0-next.2 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 1.4.26-next.2 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 8a872978e2..719a7d589c 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "1.4.26-next.2", + "version": "1.4.29-next.2", "description": "The Backstage plugin that provides your backstage app with search", "backstage": { "role": "frontend-plugin", diff --git a/plugins/search/report-alpha.api.md b/plugins/search/report-alpha.api.md index b8df4d3b4c..276fa437e7 100644 --- a/plugins/search/report-alpha.api.md +++ b/plugins/search/report-alpha.api.md @@ -5,7 +5,10 @@ ```ts import { AnyApiFactory } from '@backstage/core-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/core-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -15,6 +18,7 @@ import { RouteRef } from '@backstage/frontend-plugin-api'; import { SearchFilterExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) const _default: FrontendPlugin< @@ -28,22 +32,22 @@ const _default: FrontendPlugin< name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory<TApi, TImpl, TDeps>, + ) => ExtensionBlueprintParams<AnyApiFactory>; }>; 'nav-item:search': ExtensionDefinition<{ kind: 'nav-item'; name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { title: string; icon: IconComponent; @@ -71,9 +75,9 @@ const _default: FrontendPlugin< path?: string | undefined; }; output: - | ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}> - | ConfigurableExtensionDataRef<string, 'core.routing.path', {}> - | ConfigurableExtensionDataRef< + | ExtensionDataRef<string, 'core.routing.path', {}> + | ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}> + | ExtensionDataRef< RouteRef<AnyRouteRefParams>, 'core.routing.ref', { @@ -127,7 +131,8 @@ const _default: FrontendPlugin< kind: 'page'; name: undefined; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise<JSX.Element>; routeRef?: RouteRef; }; @@ -142,11 +147,15 @@ export const searchApi: ExtensionDefinition<{ name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>; + output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory<TApi, TImpl, TDeps>, + ) => ExtensionBlueprintParams<AnyApiFactory>; }>; // @alpha (undocumented) @@ -155,7 +164,7 @@ export const searchNavItem: ExtensionDefinition<{ name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { title: string; icon: IconComponent; @@ -185,9 +194,9 @@ export const searchPage: ExtensionDefinition<{ path?: string | undefined; }; output: - | ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}> - | ConfigurableExtensionDataRef<string, 'core.routing.path', {}> - | ConfigurableExtensionDataRef< + | ExtensionDataRef<string, 'core.routing.path', {}> + | ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}> + | ExtensionDataRef< RouteRef<AnyRouteRefParams>, 'core.routing.ref', { @@ -241,11 +250,26 @@ export const searchPage: ExtensionDefinition<{ kind: 'page'; name: undefined; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise<JSX.Element>; routeRef?: RouteRef; }; }>; +// @alpha (undocumented) +export const searchTranslationRef: TranslationRef< + 'search', + { + readonly 'searchModal.viewFullResults': 'View Full Results'; + readonly 'searchType.tabs.allTitle': 'All'; + readonly 'searchType.allResults': 'All Results'; + readonly 'searchType.accordion.collapse': 'Collapse'; + readonly 'searchType.accordion.allTitle': 'All'; + readonly 'searchType.accordion.numberOfResults': '{{number}} results'; + readonly 'sidebarSearchModal.title': 'Search'; + } +>; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx index fed0124fa7..97bd7dfe87 100644 --- a/plugins/search/src/alpha.tsx +++ b/plugins/search/src/alpha.tsx @@ -31,7 +31,6 @@ import { useApi, discoveryApiRef, fetchApiRef, - createApiFactory, } from '@backstage/core-plugin-api'; import { @@ -77,14 +76,13 @@ import { /** @alpha */ export const searchApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: searchApiRef, deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef }, factory: ({ discoveryApi, fetchApi }) => new SearchClient({ discoveryApi, fetchApi }), }), - }, }); const useSearchPageStyles = makeStyles((theme: Theme) => ({ @@ -117,7 +115,7 @@ export const searchPage = PageBlueprint.makeWithOverrides({ }, factory(originalFactory, { config, inputs }) { return originalFactory({ - defaultPath: '/search', + path: '/search', routeRef: convertLegacyRouteRef(rootRouteRef), loader: async () => { const getResultItemComponent = (result: SearchResult) => { @@ -279,8 +277,12 @@ export const searchNavItem = NavItemBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'search', + info: { packageJson: () => import('../package.json') }, extensions: [searchApi, searchPage, searchNavItem], routes: convertLegacyRouteRefs({ root: rootRouteRef, }), }); + +/** @alpha */ +export { searchTranslationRef } from './translation'; diff --git a/plugins/search/src/components/SearchModal/SearchModal.tsx b/plugins/search/src/components/SearchModal/SearchModal.tsx index 454d2eae96..af7e1c6e10 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.tsx @@ -39,6 +39,8 @@ import { useNavigate } from 'react-router-dom'; import { rootRouteRef } from '../../plugin'; import { SearchResultSet } from '@backstage/plugin-search-common'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchTranslationRef } from '../../translation'; /** * @public @@ -121,6 +123,7 @@ export const Modal = ({ const navigate = useNavigate(); const { transitions } = useTheme(); const { focusContent } = useContent(); + const { t } = useTranslationRef(searchTranslationRef); const searchRootRoute = useRouteRef(rootRouteRef)(); const searchBarRef = useRef<HTMLInputElement | null>(null); @@ -171,7 +174,7 @@ export const Modal = ({ onClick={handleSearchBarSubmit} disableRipple > - View Full Results + {t('searchModal.viewFullResults')} </Button> </Grid> </Grid> diff --git a/plugins/search/src/components/SearchType/SearchType.Accordion.test.tsx b/plugins/search/src/components/SearchType/SearchType.Accordion.test.tsx index 6a124884c0..1245ceed5a 100644 --- a/plugins/search/src/components/SearchType/SearchType.Accordion.test.tsx +++ b/plugins/search/src/components/SearchType/SearchType.Accordion.test.tsx @@ -15,8 +15,12 @@ */ import { ReactNode } from 'react'; -import { mockApis, TestApiProvider } from '@backstage/test-utils'; -import { act, render, waitFor } from '@testing-library/react'; +import { + mockApis, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; +import { act, waitFor } from '@testing-library/react'; import user from '@testing-library/user-event'; import { searchApiRef, @@ -81,7 +85,7 @@ describe('SearchType.Accordion', () => { }; it('should render as expected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( <Wrapper> <SearchType.Accordion name={expectedLabel} types={[expectedType]} /> </Wrapper>, @@ -103,7 +107,7 @@ describe('SearchType.Accordion', () => { }); it('should set entire types array when a type is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( <Wrapper> <SearchType.Accordion name={expectedLabel} types={[expectedType]} /> </Wrapper>, @@ -115,7 +119,7 @@ describe('SearchType.Accordion', () => { }); it('should reset types array when all is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( <Wrapper> <SearchType.Accordion name={expectedLabel} @@ -131,7 +135,7 @@ describe('SearchType.Accordion', () => { }); it('should reset page cursor when a new type is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( <Wrapper> <SearchType.Accordion name={expectedLabel} types={[expectedType]} /> </Wrapper>, @@ -143,7 +147,7 @@ describe('SearchType.Accordion', () => { }); it('should show result counts if enabled', async () => { - const { getAllByText } = render( + const { getAllByText } = await renderInTestApp( <Wrapper> <SearchType.Accordion name={expectedLabel} diff --git a/plugins/search/src/components/SearchType/SearchType.Accordion.tsx b/plugins/search/src/components/SearchType/SearchType.Accordion.tsx index 7726ee4565..65c96b1e60 100644 --- a/plugins/search/src/components/SearchType/SearchType.Accordion.tsx +++ b/plugins/search/src/components/SearchType/SearchType.Accordion.tsx @@ -31,6 +31,8 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import Typography from '@material-ui/core/Typography'; import AllIcon from '@material-ui/icons/FontDownload'; import useAsync from 'react-use/esm/useAsync'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ icon: { @@ -83,6 +85,7 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { const searchApi = useApi(searchApiRef); const [expanded, setExpanded] = useState(true); const { defaultValue, name, showCounts, types: givenTypes } = props; + const { t } = useTranslationRef(searchTranslationRef); const toggleExpanded = () => setExpanded(prevState => !prevState); const handleClick = (type: string) => { @@ -103,7 +106,7 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { const definedTypes = [ { value: '', - name: 'All', + name: t('searchType.accordion.allTitle'), icon: <AllIcon />, }, ...givenTypes, @@ -117,7 +120,7 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { const counts = await Promise.all( definedTypes - .map(t => t.value) + .map(type => type.value) .map(async type => { const { numberOfResults } = await searchApi.query({ term, @@ -130,9 +133,10 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { return [ type, numberOfResults !== undefined - ? `${ - numberOfResults >= 10000 ? `>10000` : numberOfResults - } results` + ? t('searchType.accordion.numberOfResults', { + number: + numberOfResults >= 10000 ? `>10000` : `${numberOfResults}`, + }) : ' -- ', ]; }), @@ -160,8 +164,8 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { IconButtonProps={{ size: 'small' }} > {expanded - ? 'Collapse' - : definedTypes.filter(t => t.value === selected)[0]!.name} + ? t('searchType.accordion.collapse') + : definedTypes.filter(type => type.value === selected)[0]!.name} </AccordionSummary> <AccordionDetails classes={{ root: classes.accordionDetails }}> <List diff --git a/plugins/search/src/components/SearchType/SearchType.Tabs.test.tsx b/plugins/search/src/components/SearchType/SearchType.Tabs.test.tsx index e8dc9e1b8f..657b8f0b45 100644 --- a/plugins/search/src/components/SearchType/SearchType.Tabs.test.tsx +++ b/plugins/search/src/components/SearchType/SearchType.Tabs.test.tsx @@ -15,8 +15,12 @@ */ import { ReactNode } from 'react'; -import { mockApis, TestApiProvider } from '@backstage/test-utils'; -import { act, render } from '@testing-library/react'; +import { + mockApis, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; +import { act } from '@testing-library/react'; import user from '@testing-library/user-event'; import { SearchContextProvider, @@ -69,7 +73,7 @@ describe('SearchType.Tabs', () => { }; it('should render as expected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( <Wrapper> <SearchType.Tabs types={[expectedType]} /> </Wrapper>, @@ -85,7 +89,7 @@ describe('SearchType.Tabs', () => { }); it('should set entire types array when a type is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( <Wrapper> <SearchType.Tabs types={[expectedType]} /> </Wrapper>, @@ -97,7 +101,7 @@ describe('SearchType.Tabs', () => { }); it('should reset types array when all is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( <Wrapper> <SearchType.Tabs defaultValue={expectedType.value} @@ -112,7 +116,7 @@ describe('SearchType.Tabs', () => { }); it('should reset page cursor when a new type is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( <Wrapper> <SearchType.Tabs types={[expectedType]} /> </Wrapper>, diff --git a/plugins/search/src/components/SearchType/SearchType.Tabs.tsx b/plugins/search/src/components/SearchType/SearchType.Tabs.tsx index f4323da915..ed09ea940a 100644 --- a/plugins/search/src/components/SearchType/SearchType.Tabs.tsx +++ b/plugins/search/src/components/SearchType/SearchType.Tabs.tsx @@ -20,6 +20,8 @@ import Tab from '@material-ui/core/Tab'; import Tabs from '@material-ui/core/Tabs'; import { makeStyles } from '@material-ui/core/styles'; import { Theme } from '@material-ui/core/styles'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchTranslationRef } from '../../translation'; const useStyles = makeStyles((theme: Theme) => ({ tabs: { @@ -49,6 +51,7 @@ export const SearchTypeTabs = (props: SearchTypeTabsProps) => { const classes = useStyles(); const { setPageCursor, setTypes, types } = useSearch(); const { defaultValue, types: givenTypes } = props; + const { t } = useTranslationRef(searchTranslationRef); const changeTab = (_: ChangeEvent<{}>, newType: string) => { setTypes(newType !== '' ? [newType] : []); @@ -66,7 +69,7 @@ export const SearchTypeTabs = (props: SearchTypeTabsProps) => { const definedTypes = [ { value: '', - name: 'All', + name: t('searchType.tabs.allTitle'), }, ...givenTypes, ]; diff --git a/plugins/search/src/components/SearchType/SearchType.test.tsx b/plugins/search/src/components/SearchType/SearchType.test.tsx index fd014369f7..231463c9bf 100644 --- a/plugins/search/src/components/SearchType/SearchType.test.tsx +++ b/plugins/search/src/components/SearchType/SearchType.test.tsx @@ -15,14 +15,18 @@ */ import { configApiRef } from '@backstage/core-plugin-api'; -import { render, screen, waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { SearchContextProvider, searchApiRef, } from '@backstage/plugin-search-react'; import { SearchType } from './SearchType'; -import { mockApis, TestApiProvider } from '@backstage/test-utils'; +import { + mockApis, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; describe('SearchType', () => { const initialState = { @@ -53,7 +57,7 @@ describe('SearchType', () => { describe('Type Filter', () => { it('Renders field name and values when provided as props', async () => { - render( + await renderInTestApp( <TestApiProvider apis={[ [configApiRef, configApiMock], @@ -85,7 +89,7 @@ describe('SearchType', () => { }); it('Renders correctly based on type filter state', async () => { - render( + await renderInTestApp( <TestApiProvider apis={[ [configApiRef, configApiMock], @@ -123,7 +127,7 @@ describe('SearchType', () => { }); it('Renders correctly based on type filter defaultValue', async () => { - render( + await renderInTestApp( <TestApiProvider apis={[ [configApiRef, configApiMock], @@ -156,7 +160,7 @@ describe('SearchType', () => { }); it('Selecting a value sets type filter state', async () => { - render( + await renderInTestApp( <TestApiProvider apis={[ [configApiRef, configApiMock], @@ -199,7 +203,7 @@ describe('SearchType', () => { }); it('Selecting none defaults to empty state', async () => { - render( + await renderInTestApp( <TestApiProvider apis={[ [configApiRef, configApiMock], diff --git a/plugins/search/src/components/SearchType/SearchType.tsx b/plugins/search/src/components/SearchType/SearchType.tsx index 48b78f00d3..b40aa431a1 100644 --- a/plugins/search/src/components/SearchType/SearchType.tsx +++ b/plugins/search/src/components/SearchType/SearchType.tsx @@ -29,6 +29,8 @@ import { } from './SearchType.Accordion'; import { SearchTypeTabs, SearchTypeTabsProps } from './SearchType.Tabs'; import { useSearch } from '@backstage/plugin-search-react'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ label: { @@ -63,6 +65,7 @@ const SearchType = (props: SearchTypeProps) => { const { className, defaultValue, name, values = [] } = props; const classes = useStyles(); const { types, setTypes } = useSearch(); + const { t } = useTranslationRef(searchTranslationRef); useEffectOnce(() => { if (!types.length) { @@ -94,7 +97,7 @@ const SearchType = (props: SearchTypeProps) => { variant="outlined" value={types} onChange={handleChange} - placeholder="All Results" + placeholder={t('searchType.allResults')} renderValue={selected => ( <div className={classes.chips}> {(selected as string[]).map(value => ( diff --git a/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx b/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx index ad687a35db..5dde4f385b 100644 --- a/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx +++ b/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx @@ -22,6 +22,8 @@ import { SearchModalProvider, useSearchModal, } from '../SearchModal'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchTranslationRef } from '../../translation'; /** * Props for {@link SidebarSearchModal}. @@ -38,13 +40,14 @@ export type SidebarSearchModalProps = Pick< const SidebarSearchModalContent = (props: SidebarSearchModalProps) => { const { state, toggleModal } = useSearchModal(); const Icon = props.icon ? props.icon : SearchIcon; + const { t } = useTranslationRef(searchTranslationRef); return ( <> <SidebarItem className="search-icon" icon={Icon} - text="Search" + text={t('sidebarSearchModal.title')} onClick={toggleModal} /> <SearchModal diff --git a/plugins/search/src/translation.ts b/plugins/search/src/translation.ts new file mode 100644 index 0000000000..37f86b9218 --- /dev/null +++ b/plugins/search/src/translation.ts @@ -0,0 +1,43 @@ +/* + * 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 searchTranslationRef = createTranslationRef({ + id: 'search', + messages: { + searchModal: { + viewFullResults: 'View Full Results', + }, + searchType: { + allResults: 'All Results', + tabs: { + allTitle: 'All', + }, + accordion: { + allTitle: 'All', + collapse: 'Collapse', + numberOfResults: '{{number}} results', + }, + }, + sidebarSearchModal: { + title: 'Search', + }, + }, +}); diff --git a/plugins/signals-backend/CHANGELOG.md b/plugins/signals-backend/CHANGELOG.md index 93c0bfc099..579abe991c 100644 --- a/plugins/signals-backend/CHANGELOG.md +++ b/plugins/signals-backend/CHANGELOG.md @@ -1,5 +1,97 @@ # @backstage/plugin-signals-backend +## 0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.23-next.0 + +## 0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-events-node@0.4.13 + - @backstage/plugin-signals-node@0.1.22 + +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-signals-node@0.1.22-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + - @backstage/plugin-signals-node@0.1.21 + +## 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 + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + - @backstage/plugin-signals-node@0.1.20 + +## 0.3.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + - @backstage/plugin-signals-node@0.1.20-next.2 + ## 0.3.4-next.1 ### Patch Changes diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index 39ca69672f..5e6aad0197 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-next.1", + "version": "0.3.7-next.0", "backstage": { "role": "backend-plugin", "pluginId": "signals", diff --git a/plugins/signals-node/CHANGELOG.md b/plugins/signals-node/CHANGELOG.md index 23fa9ade45..ad280ee31b 100644 --- a/plugins/signals-node/CHANGELOG.md +++ b/plugins/signals-node/CHANGELOG.md @@ -1,5 +1,89 @@ # @backstage/plugin-signals-node +## 0.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-events-node@0.4.14-next.0 + - @backstage/config@1.3.3 + - @backstage/types@1.2.1 + +## 0.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-events-node@0.4.13 + +## 0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-events-node@0.4.13-next.0 + +## 0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12 + +## 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 + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11 + +## 0.1.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.2 + ## 0.1.20-next.1 ### Patch Changes diff --git a/plugins/signals-node/package.json b/plugins/signals-node/package.json index b3510d3d5a..e3cc09a877 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-next.1", + "version": "0.1.23-next.0", "description": "Node.js library for the signals plugin", "backstage": { "role": "node-library", diff --git a/plugins/signals-react/CHANGELOG.md b/plugins/signals-react/CHANGELOG.md index 7983ebd1c2..d5cfb09e39 100644 --- a/plugins/signals-react/CHANGELOG.md +++ b/plugins/signals-react/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-signals-react +## 0.0.15 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.9 + +## 0.0.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.9-next.0 + +## 0.0.14 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.8 + - @backstage/types@1.2.1 + +## 0.0.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7 + - @backstage/types@1.2.1 + ## 0.0.13-next.0 ### Patch Changes diff --git a/plugins/signals-react/package.json b/plugins/signals-react/package.json index 8367209df4..cc92bb120c 100644 --- a/plugins/signals-react/package.json +++ b/plugins/signals-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-react", - "version": "0.0.13-next.0", + "version": "0.0.15", "description": "Web library for the signals plugin", "backstage": { "role": "web-library", diff --git a/plugins/signals/CHANGELOG.md b/plugins/signals/CHANGELOG.md index 54dad6dbcd..479f265ba9 100644 --- a/plugins/signals/CHANGELOG.md +++ b/plugins/signals/CHANGELOG.md @@ -1,5 +1,133 @@ # @backstage/plugin-signals +## 0.0.22-next.2 + +### Patch Changes + +- 121899a: **BREAKING ALPHA**: The `app-root-element` extension now only accepts `JSX.Element` in its `element` param, meaning overrides need to be updated. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.8-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.15 + +## 0.0.22-next.1 + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.15 + +## 0.0.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.0.21 + +### Patch Changes + +- d52d67f: Added a New Frontend System App Root Element for the `<SignalsDisplay />` component +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-signals-react@0.0.15 + +## 0.0.21-next.2 + +### Patch Changes + +- d52d67f: Added a New Frontend System App Root Element for the `<SignalsDisplay />` component +- Updated dependencies + - @backstage/theme@0.6.7-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + +## 0.0.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + - @backstage/plugin-signals-react@0.0.15-next.0 + +## 0.0.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.14 + +## 0.0.20 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.14 + +## 0.0.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/core-plugin-api@1.10.7 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.13 + +## 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 + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-signals-react@0.0.13 + - @backstage/types@1.2.1 + ## 0.0.19-next.1 ### Patch Changes diff --git a/plugins/signals/package.json b/plugins/signals/package.json index 0af3bc534b..694b40343a 100644 --- a/plugins/signals/package.json +++ b/plugins/signals/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals", - "version": "0.0.19-next.1", + "version": "0.0.22-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "signals", @@ -51,6 +51,7 @@ "test": "backstage-cli package test" }, "dependencies": { + "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", diff --git a/plugins/signals/report-alpha.api.md b/plugins/signals/report-alpha.api.md index 45ee0da3b8..ff1d4d2c3b 100644 --- a/plugins/signals/report-alpha.api.md +++ b/plugins/signals/report-alpha.api.md @@ -4,9 +4,12 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; -import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; +import { JSX as JSX_2 } from 'react'; // @alpha (undocumented) const _default: FrontendPlugin< @@ -18,14 +21,25 @@ const _default: FrontendPlugin< name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>; + inputs: {}; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory<TApi, TImpl, TDeps>, + ) => ExtensionBlueprintParams<AnyApiFactory>; + }>; + 'app-root-element:signals/signals-display': ExtensionDefinition<{ + kind: 'app-root-element'; + name: 'signals-display'; + config: {}; + configInput: {}; + output: ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>; inputs: {}; params: { - factory: AnyApiFactory; + element: JSX.Element; }; }>; } diff --git a/plugins/signals/src/alpha.tsx b/plugins/signals/src/alpha.tsx index ba7f72f420..5f9cd6f91b 100644 --- a/plugins/signals/src/alpha.tsx +++ b/plugins/signals/src/alpha.tsx @@ -16,17 +16,19 @@ import { ApiBlueprint, - createApiFactory, + AppRootElementBlueprint, createFrontendPlugin, discoveryApiRef, identityApiRef, } from '@backstage/frontend-plugin-api'; import { signalApiRef } from '@backstage/plugin-signals-react'; import { SignalClient } from './api/SignalClient'; +import { SignalsDisplay } from './plugin'; +import { compatWrapper } from '@backstage/core-compat-api'; const api = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: signalApiRef, deps: { identity: identityApiRef, @@ -39,11 +41,18 @@ const api = ApiBlueprint.make({ }); }, }), +}); + +const signalsDisplayAppRootElement = AppRootElementBlueprint.make({ + name: 'signals-display', + params: { + element: compatWrapper(<SignalsDisplay />), }, }); /** @alpha */ export default createFrontendPlugin({ pluginId: 'signals', - extensions: [api], + info: { packageJson: () => import('../package.json') }, + extensions: [api, signalsDisplayAppRootElement], }); diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index b59f735816..24df3849fd 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,176 @@ # @backstage/plugin-techdocs-addons-test-utils +## 1.0.52-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.2-next.1 + - @backstage/plugin-techdocs@1.14.0-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/plugin-search-react@1.9.3-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + - @backstage/test-utils@1.7.11-next.0 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration-react@1.2.9 + +## 1.0.52-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/plugin-catalog@1.31.2-next.0 + - @backstage/plugin-techdocs@1.13.3-next.0 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-search-react@1.9.2 + +## 1.0.51 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.2 + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/plugin-catalog@1.31.1 + - @backstage/core-plugin-api@1.10.9 + - @backstage/core-app-api@1.18.0 + - @backstage/integration-react@1.2.9 + - @backstage/test-utils@1.7.10 + - @backstage/plugin-search-react@1.9.2 + - @backstage/plugin-techdocs-react@1.3.1 + +## 1.0.51-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/test-utils@1.7.10-next.2 + - @backstage/plugin-catalog@1.31.1-next.2 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/plugin-techdocs@1.13.2-next.1 + +## 1.0.51-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/test-utils@1.7.10-next.1 + - @backstage/plugin-techdocs@1.13.2-next.1 + - @backstage/plugin-techdocs-react@1.3.1-next.1 + - @backstage/plugin-catalog@1.31.1-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + +## 1.0.51-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.2-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/plugin-catalog@1.31.1-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/test-utils@1.7.10-next.0 + - @backstage/plugin-search-react@1.9.2-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.0 + - @backstage/core-app-api@1.17.1 + - @backstage/core-plugin-api@1.10.8 + +## 1.0.49 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.0 + - @backstage/plugin-catalog@1.31.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/plugin-techdocs-react@1.3.0 + - @backstage/plugin-search-react@1.9.1 + - @backstage/integration-react@1.2.8 + - @backstage/core-app-api@1.17.1 + - @backstage/test-utils@1.7.9 + +## 1.0.49-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.0-next.2 + - @backstage/plugin-techdocs@1.13.0-next.2 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/core-app-api@1.17.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/test-utils@1.7.8 + +## 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 + +- Updated dependencies + - @backstage/test-utils@1.7.8 + - @backstage/core-app-api@1.17.0 + - @backstage/plugin-techdocs@1.12.6 + - @backstage/plugin-catalog@1.30.0 + - @backstage/plugin-search-react@1.9.0 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/plugin-techdocs-react@1.2.17 + - @backstage/core-plugin-api@1.10.7 + - @backstage/integration-react@1.2.7 + +## 1.0.48-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/plugin-catalog@1.30.0-next.3 + - @backstage/plugin-search-react@1.9.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/test-utils@1.7.8-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/plugin-techdocs@1.12.6-next.3 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + ## 1.0.48-next.2 ### Patch Changes diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index 0b6ee4f297..54d36af0f2 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-next.2", + "version": "1.0.52-next.1", "backstage": { "role": "web-library", "pluginId": "techdocs-addons", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 8ba2abcdb2..21215ce108 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,205 @@ # @backstage/plugin-techdocs-backend +## 2.0.5-next.1 + +### Patch Changes + +- 484e500: Updated CachedEntityLoader to use BackstageCredentials instead of raw tokens for cache key generation. It now uses principal-based identification (user entity ref for users, subject for services) instead of token-based keys, providing more consistent caching behavior. +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.5-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-node@1.13.6-next.0 + +## 2.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.18.0-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.5-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/plugin-techdocs-node@1.13.6-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-techdocs-common@0.1.1 + +## 2.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/catalog-model@1.7.5 + - @backstage/backend-defaults@0.11.1 + - @backstage/catalog-client@1.10.2 + - @backstage/integration@1.17.1 + - @backstage/plugin-techdocs-node@1.13.5 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.17.2 + - @backstage/plugin-search-backend-module-techdocs@0.4.4 + +## 2.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/plugin-permission-common@0.9.1-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/integration@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.4-next.1 + - @backstage/plugin-techdocs-node@1.13.5-next.1 + - @backstage/plugin-catalog-common@1.1.5-next.0 + - @backstage/plugin-catalog-node@1.17.2-next.0 + +## 2.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.4-next.0 + - @backstage/plugin-techdocs-node@1.13.5-next.0 + - @backstage/catalog-client@1.10.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-techdocs-common@0.1.1 + +## 2.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1 + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-catalog-node@1.17.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.3 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/backend-plugin-api@1.4.0 + - @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-node@1.13.4 + +## 2.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.3-next.1 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/plugin-techdocs-node@1.13.4-next.1 + +## 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 + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/backend-defaults@0.10.0 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-node@1.17.0 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-techdocs-node@1.13.3 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-search-backend-module-techdocs@0.4.2 + - @backstage/plugin-techdocs-common@0.1.0 + +## 2.0.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.3 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-node@1.13.3-next.3 + ## 2.0.2-next.2 ### Patch Changes diff --git a/plugins/techdocs-backend/knip-report.md b/plugins/techdocs-backend/knip-report.md index 2661c35327..764d9383f0 100644 --- a/plugins/techdocs-backend/knip-report.md +++ b/plugins/techdocs-backend/knip-report.md @@ -1,2 +1,12 @@ # Knip report +## Unused dependencies (5) + +| Name | Location | Severity | +| :----------------------------------------------- | :----------- | :------- | +| @backstage/plugin-search-backend-module-techdocs | package.json | error | +| @backstage/plugin-permission-common | package.json | error | +| @backstage/plugin-techdocs-common | package.json | error | +| @backstage/plugin-catalog-common | package.json | error | +| lodash | package.json | error | + diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 380d37f79a..e86734c08c 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-next.2", + "version": "2.0.5-next.1", "description": "The Backstage backend plugin that renders technical documentation for your components", "backstage": { "role": "backend-plugin", diff --git a/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts b/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts index 2eb6bf03f4..df5c038dce 100644 --- a/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts +++ b/plugins/techdocs-backend/src/service/CachedEntityLoader.test.ts @@ -18,9 +18,11 @@ import { CachedEntityLoader } from './CachedEntityLoader'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { mockServices } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; +import { BackstageCredentials } from '@backstage/backend-plugin-api'; describe('CachedEntityLoader', () => { const cache = mockServices.cache.mock(); + const auth = mockServices.auth.mock(); const entityName: CompoundEntityRef = { kind: 'component', @@ -39,20 +41,37 @@ describe('CachedEntityLoader', () => { const token = 'test-token'; + const userCredentials: BackstageCredentials = { + $$type: '@backstage/BackstageCredentials', + principal: { + type: 'user', + userEntityRef: 'user:default/test-user', + }, + }; + + const pluginCredentials: BackstageCredentials = { + $$type: '@backstage/BackstageCredentials', + principal: { + type: 'plugin', + subject: 'plugin:test-plugin', + }, + }; + afterEach(() => { jest.resetAllMocks(); }); - it('writes entities to cache', async () => { + it('writes entities to cache for user credentials', async () => { cache.get.mockResolvedValue(undefined); const catalog = catalogServiceMock({ entities: [entity] }); + auth.isPrincipal.mockReturnValue(true); - const loader = new CachedEntityLoader({ catalog, cache }); - const result = await loader.load(entityName, token); + const loader = new CachedEntityLoader({ auth, catalog, cache }); + const result = await loader.load(userCredentials, entityName, token); expect(result).toEqual(entity); expect(cache.set).toHaveBeenCalledWith( - 'catalog:component:default/test:test-token', + 'catalog:component:default/test:user:default/test-user', entity, { ttl: 5000 }, ); @@ -62,9 +81,10 @@ describe('CachedEntityLoader', () => { const catalog = catalogServiceMock(); jest.spyOn(catalog, 'getEntityByRef'); cache.get.mockResolvedValue(entity); + auth.isPrincipal.mockReturnValue(true); - const loader = new CachedEntityLoader({ catalog, cache }); - const result = await loader.load(entityName, token); + const loader = new CachedEntityLoader({ auth, catalog, cache }); + const result = await loader.load(userCredentials, entityName, token); expect(result).toEqual(entity); expect(catalog.getEntityByRef).not.toHaveBeenCalled(); @@ -73,24 +93,26 @@ describe('CachedEntityLoader', () => { it('does not cache missing entities', async () => { const catalog = catalogServiceMock({ entities: [] }); cache.get.mockResolvedValue(undefined); + auth.isPrincipal.mockReturnValue(true); - const loader = new CachedEntityLoader({ catalog, cache }); - const result = await loader.load(entityName, token); + const loader = new CachedEntityLoader({ auth, catalog, cache }); + const result = await loader.load(userCredentials, entityName, token); expect(result).toBeUndefined(); expect(cache.set).not.toHaveBeenCalled(); }); - it('uses entity ref as cache key for anonymous users', async () => { + it('uses entity ref as cache key for service credentials', async () => { const catalog = catalogServiceMock({ entities: [entity] }); cache.get.mockResolvedValue(undefined); + auth.isPrincipal.mockReturnValueOnce(false).mockReturnValueOnce(true); - const loader = new CachedEntityLoader({ catalog, cache }); - const result = await loader.load(entityName, undefined); + const loader = new CachedEntityLoader({ auth, catalog, cache }); + const result = await loader.load(pluginCredentials, entityName, undefined); expect(result).toEqual(entity); expect(cache.set).toHaveBeenCalledWith( - 'catalog:component:default/test', + 'catalog:component:default/test:plugin:test-plugin', entity, { ttl: 5000, @@ -106,10 +128,82 @@ describe('CachedEntityLoader', () => { }), ); const catalog = catalogServiceMock({ entities: [entity] }); + auth.isPrincipal.mockReturnValue(true); - const loader = new CachedEntityLoader({ catalog, cache }); - const result = await loader.load(entityName, token); + const loader = new CachedEntityLoader({ auth, catalog, cache }); + const result = await loader.load(userCredentials, entityName, token); expect(result).toEqual(entity); }); + + it('creates different cache keys for different users', async () => { + const catalog = catalogServiceMock({ entities: [entity] }); + cache.get.mockResolvedValue(undefined); + auth.isPrincipal.mockReturnValue(true); + + const loader = new CachedEntityLoader({ auth, catalog, cache }); + + const anotherUserCredentials: BackstageCredentials = { + $$type: '@backstage/BackstageCredentials', + principal: { + type: 'user', + userEntityRef: 'user:default/another-user', + }, + }; + + await loader.load(userCredentials, entityName, token); + await loader.load(anotherUserCredentials, entityName, token); + + expect(cache.set).toHaveBeenCalledWith( + 'catalog:component:default/test:user:default/test-user', + entity, + { ttl: 5000 }, + ); + expect(cache.set).toHaveBeenCalledWith( + 'catalog:component:default/test:user:default/another-user', + entity, + { ttl: 5000 }, + ); + }); + + it('creates cache key with service subject for service credentials', async () => { + const catalog = catalogServiceMock({ entities: [entity] }); + cache.get.mockResolvedValue(undefined); + auth.isPrincipal.mockReturnValueOnce(false).mockReturnValueOnce(true); + + const loader = new CachedEntityLoader({ auth, catalog, cache }); + const result = await loader.load(pluginCredentials, entityName, token); + + expect(result).toEqual(entity); + expect(cache.set).toHaveBeenCalledWith( + 'catalog:component:default/test:plugin:test-plugin', + entity, + { ttl: 5000 }, + ); + expect(auth.isPrincipal).toHaveBeenCalledWith(pluginCredentials, 'user'); + expect(auth.isPrincipal).toHaveBeenCalledWith(pluginCredentials, 'service'); + }); + + it('handles credentials that are neither user nor service', async () => { + const catalog = catalogServiceMock({ entities: [entity] }); + cache.get.mockResolvedValue(undefined); + auth.isPrincipal.mockReturnValue(false); + + const unknownCredentials: BackstageCredentials = { + $$type: '@backstage/BackstageCredentials', + principal: { + type: 'unknown' as any, + }, + }; + + const loader = new CachedEntityLoader({ auth, catalog, cache }); + const result = await loader.load(unknownCredentials, entityName, token); + + expect(result).toEqual(entity); + expect(cache.set).toHaveBeenCalledWith( + 'catalog:component:default/test', + entity, + { ttl: 5000 }, + ); + }); }); diff --git a/plugins/techdocs-backend/src/service/CachedEntityLoader.ts b/plugins/techdocs-backend/src/service/CachedEntityLoader.ts index 7541509907..9f97ed2cd4 100644 --- a/plugins/techdocs-backend/src/service/CachedEntityLoader.ts +++ b/plugins/techdocs-backend/src/service/CachedEntityLoader.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { CacheService } from '@backstage/backend-plugin-api'; +import { + AuthService, + BackstageCredentials, + CacheService, +} from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { Entity, @@ -23,25 +27,29 @@ import { } from '@backstage/catalog-model'; export type CachedEntityLoaderOptions = { + auth: AuthService; catalog: CatalogApi; cache: CacheService; }; export class CachedEntityLoader { + private readonly auth: AuthService; private readonly catalog: CatalogApi; private readonly cache: CacheService; private readonly readTimeout = 1000; - constructor({ catalog, cache }: CachedEntityLoaderOptions) { + constructor({ auth, catalog, cache }: CachedEntityLoaderOptions) { + this.auth = auth; this.catalog = catalog; this.cache = cache; } async load( + credentials: BackstageCredentials, entityRef: CompoundEntityRef, token: string | undefined, ): Promise<Entity | undefined> { - const cacheKey = this.getCacheKey(entityRef, token); + const cacheKey = this.getCacheKey(entityRef, credentials); let result = await this.getFromCache(cacheKey); if (result) { @@ -68,12 +76,14 @@ export class CachedEntityLoader { private getCacheKey( entityName: CompoundEntityRef, - token: string | undefined, + credentials: BackstageCredentials, ): string { const key = ['catalog', stringifyEntityRef(entityName)]; - if (token) { - key.push(token); + if (this.auth.isPrincipal(credentials, 'user')) { + key.push(credentials.principal.userEntityRef); + } else if (this.auth.isPrincipal(credentials, 'service')) { + key.push(credentials.principal.subject); } return key.join(':'); diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index bfaf072717..7ce26232e1 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -124,6 +124,7 @@ export async function createRouter( // Entities are cached to optimize the /static/docs request path, which can be called many times // when loading a single techdocs page. const entityLoader = new CachedEntityLoader({ + auth, catalog: catalogClient, cache: options.cache, }); @@ -158,7 +159,7 @@ export async function createRouter( }); // Verify that the related entity exists and the current user has permission to view it. - const entity = await entityLoader.load(entityName, token); + const entity = await entityLoader.load(credentials, entityName, token); if (!entity) { throw new NotFoundError( @@ -196,7 +197,7 @@ export async function createRouter( targetPluginId: 'catalog', }); - const entity = await entityLoader.load(entityName, token); + const entity = await entityLoader.load(credentials, entityName, token); if (!entity) { throw new NotFoundError( @@ -234,7 +235,11 @@ export async function createRouter( targetPluginId: 'catalog', }); - const entity = await entityLoader.load({ kind, namespace, name }, token); + const entity = await entityLoader.load( + credentials, + { kind, namespace, name }, + token, + ); if (!entity?.metadata?.uid) { throw new NotFoundError('Entity metadata UID missing'); @@ -305,7 +310,7 @@ export async function createRouter( targetPluginId: 'catalog', }); - const entity = await entityLoader.load(entityName, token); + const entity = await entityLoader.load(credentials, entityName, token); if (!entity) { throw new NotFoundError( diff --git a/plugins/techdocs-common/CHANGELOG.md b/plugins/techdocs-common/CHANGELOG.md index 619884e4ec..7e2568ab78 100644 --- a/plugins/techdocs-common/CHANGELOG.md +++ b/plugins/techdocs-common/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-techdocs-common +## 0.1.1 + +### 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.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..1f43a48f13 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", "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 df7bb86451..58b1c8b72b 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,124 @@ # @backstage/plugin-techdocs-module-addons-contrib +## 1.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + +## 1.1.26 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-techdocs-react@1.3.1 + +## 1.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-techdocs-react@1.3.1-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 1.1.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + +## 1.1.25 + +### Patch Changes + +- 9dde3ba: Improved Keyboard accessibility in techdocs. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-techdocs-react@1.3.0 + - @backstage/integration-react@1.2.8 + - @backstage/integration@1.17.0 + +## 1.1.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/core-plugin-api@1.10.7 + - @backstage/integration@1.17.0 + +## 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 + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/integration@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/plugin-techdocs-react@1.2.17 + - @backstage/core-plugin-api@1.10.7 + - @backstage/integration-react@1.2.7 + +## 1.1.24-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + ## 1.1.24-next.2 ### Patch Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 8896409a09..c5fd222ff7 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-next.2", + "version": "1.1.27-next.0", "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<HTMLElement>, + 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<HTMLDivElement>, + 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 = () => { <StyledButton size="small" onClick={handleState} + onKeyDown={event => 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 b7c474a1e9..3ffa811697 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,129 @@ # @backstage/plugin-techdocs-node +## 1.13.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.13.5 + +### Patch Changes + +- 029526c: Updated the error message thrown by parseReferenceAnnotation to reflect the annotation value passed as an argument rather than in correctly assuming location. +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/integration@1.17.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-search-common@1.2.19 + +## 1.13.5-next.2 + +### Patch Changes + +- 029526c: Updated the error message thrown by parseReferenceAnnotation to reflect the annotation value passed as an argument rather than in correctly assuming location. + +## 1.13.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/integration-aws-node@0.1.17-next.0 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-search-common@1.2.19-next.0 + +## 1.13.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.16 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.13.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/backend-plugin-api@1.4.0 + - @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 + +## 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 + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/integration@1.17.0 + - @backstage/catalog-model@1.7.4 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/integration-aws-node@0.1.16 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.13.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + ## 1.13.3-next.2 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index 2cabc2b3c2..e28a87c2f0 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-next.2", + "version": "1.13.6-next.0", "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-node/src/helpers.test.ts b/plugins/techdocs-node/src/helpers.test.ts index 33f4018f47..aa1aa69d30 100644 --- a/plugins/techdocs-node/src/helpers.test.ts +++ b/plugins/techdocs-node/src/helpers.test.ts @@ -113,9 +113,12 @@ describe('parseReferenceAnnotation', () => { }); it('should throw error without annotation', () => { + const logMsgRegex = new RegExp( + `No ${TECHDOCS_ANNOTATION} annotation provided`, + ); expect(() => { parseReferenceAnnotation(TECHDOCS_ANNOTATION, entityBase); - }).toThrow(/No location annotation/); + }).toThrow(logMsgRegex); }); it('should throw error with bad annotation', () => { diff --git a/plugins/techdocs-node/src/helpers.ts b/plugins/techdocs-node/src/helpers.ts index 67bb862c62..f1655aa9a6 100644 --- a/plugins/techdocs-node/src/helpers.ts +++ b/plugins/techdocs-node/src/helpers.ts @@ -52,7 +52,7 @@ export const parseReferenceAnnotation = ( const annotation = entity.metadata.annotations?.[annotationName]; if (!annotation) { throw new InputError( - `No location annotation provided in entity: ${entity.metadata.name}`, + `No ${annotationName} annotation provided in entity: ${entity.metadata.name}`, ); } diff --git a/plugins/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index 1713b0f271..0f619cd643 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,108 @@ # @backstage/plugin-techdocs-react +## 1.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/frontend-plugin-api@0.10.4 + +## 1.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/frontend-plugin-api@0.10.4-next.1 + +## 1.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.3.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/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/version-bridge@1.0.11 + +## 1.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + +## 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 + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-plugin-api@1.10.7 + - @backstage/config@1.3.2 + - @backstage/version-bridge@1.0.11 + ## 1.2.17-next.1 ### Patch Changes diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index 420775b251..c5527dfec1 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-next.1", + "version": "1.3.2-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-alpha.api.md b/plugins/techdocs-react/report-alpha.api.md index d916d1fcb0..1fed274f3d 100644 --- a/plugins/techdocs-react/report-alpha.api.md +++ b/plugins/techdocs-react/report-alpha.api.md @@ -6,17 +6,13 @@ import { ComponentType } from 'react'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; // @alpha export const AddonBlueprint: ExtensionBlueprint<{ kind: 'addon'; - name: undefined; params: TechDocsAddonOptions; - output: ConfigurableExtensionDataRef< - TechDocsAddonOptions, - 'techdocs.addon', - {} - >; + output: ExtensionDataRef<TechDocsAddonOptions, 'techdocs.addon', {}>; inputs: {}; config: {}; configInput: {}; 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<TComponentProps>( options: TechDocsAddonOptions<TComponentProps>, ): 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 bcc2f88efa..aa541dde7c 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,285 @@ # @backstage/plugin-techdocs +## 1.14.0-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-search-react@1.9.3-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/theme@0.6.8-next.0 + - @backstage/plugin-auth-react@0.1.18-next.0 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + +## 1.14.0-next.1 + +### Minor Changes + +- cb0541f: Adds `additionalAllowedURIProtocols` to sanitizer config + +### Patch Changes + +- f2f133c: Internal update to use the new variant of `ApiBlueprint`. +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/catalog-client@1.11.0-next.0 + - @backstage/plugin-search-react@1.9.3-next.0 + - @backstage/plugin-techdocs-react@1.3.2-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.1 + - @backstage/integration-react@1.2.9 + - @backstage/plugin-auth-react@0.1.18-next.0 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.13.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/integration-react@1.2.9 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-search-react@1.9.2 + +## 1.13.2 + +### Patch Changes + +- 1debf7f: Fixed an issue causing TechDocs to not properly handle initial redirect. +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/config@1.3.3 + - @backstage/catalog-model@1.7.5 + - @backstage/catalog-client@1.10.2 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/integration@1.17.1 + - @backstage/theme@0.6.7 + - @backstage/integration-react@1.2.9 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-auth-react@0.1.17 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-search-react@1.9.2 + - @backstage/plugin-techdocs-react@1.3.1 + +## 1.13.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.3-next.0 + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/catalog-client@1.10.2-next.0 + - @backstage/integration@1.17.1-next.1 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/integration-react@1.2.9-next.1 + - @backstage/plugin-techdocs-react@1.3.1-next.1 + - @backstage/plugin-search-common@1.2.19-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/plugin-search-react@1.9.2-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + - @backstage/plugin-auth-react@0.1.17-next.1 + +## 1.13.2-next.0 + +### Patch Changes + +- 1debf7f: Fixed an issue causing TechDocs to not properly handle initial redirect. +- Updated dependencies + - @backstage/integration@1.17.1-next.0 + - @backstage/integration-react@1.2.9-next.0 + - @backstage/theme@0.6.7-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/plugin-search-react@1.9.2-next.0 + - @backstage/plugin-techdocs-react@1.3.1-next.0 + - @backstage/catalog-client@1.10.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/plugin-auth-react@0.1.17-next.0 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.1 + +## 1.13.0 + +### Minor Changes + +- 3c59ece: **New Frontend System Only:** + The `TechDocs` plugin is now responsible for providing an entity icon link extension to read documentation from the catalog entity page. +- ec7b35d: Introduced `backstage.io/techdocs-entity-path` annotation which allows deep linking into another entities TechDocs in conjunction with `backstage.io/techdocs-entity`. + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- 9dde3ba: Improved Keyboard accessibility in techdocs. +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/catalog-client@1.10.1 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/plugin-techdocs-react@1.3.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-search-react@1.9.1 + - @backstage/integration-react@1.2.8 + - @backstage/plugin-auth-react@0.1.16 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/theme@0.6.6 + - @backstage/plugin-search-common@1.2.18 + +## 1.13.0-next.2 + +### Minor Changes + +- 3c59ece: **New Frontend System Only:** + The `TechDocs` plugin is now responsible for providing an entity icon link extension to read documentation from the catalog entity page. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/plugin-search-react@1.9.1-next.1 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-auth-react@0.1.16-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.1 + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/theme@0.6.6 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + +## 1.13.0-next.1 + +### Patch Changes + +- 9dde3ba: Improved Keyboard accessibility in techdocs. +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/integration@1.17.0 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## 1.13.0-next.0 + +### Minor Changes + +- ec7b35d: Introduced `backstage.io/techdocs-entity-path` annotation which allows deep linking into another entities TechDocs in conjunction with `backstage.io/techdocs-entity`. + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/integration-react@1.2.7 + +## 1.12.6 + +### Patch Changes + +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 7d445da: Update keyboard focus on when clicking hash links. This fixes the issue where the "skip to content" link rendered by Material MkDocs isn't focused when used. +- 72d019d: Removed various typos +- 2ffd273: Add hover and focus styling to the "copy to clipboard" button within codeblocks in techdocs. Also added an aria-label to the button for accessibility. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/integration@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-search-react@1.9.0 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/plugin-techdocs-react@1.2.17 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/core-plugin-api@1.10.7 + - @backstage/catalog-client@1.10.0 + - @backstage/config@1.3.2 + - @backstage/integration-react@1.2.7 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.0 + +## 1.12.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.3 + - @backstage/plugin-search-react@1.9.0-next.2 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + ## 1.12.6-next.2 ### Patch Changes diff --git a/plugins/techdocs/config.d.ts b/plugins/techdocs/config.d.ts index bfc98a7927..7294f52816 100644 --- a/plugins/techdocs/config.d.ts +++ b/plugins/techdocs/config.d.ts @@ -58,6 +58,16 @@ export interface Config { * @visibility frontend */ allowedCustomElementAttributeNameRegExp?: string; + /** + * Allows listed protocols in attributes with URI values + * Example: + * additionalAllowedURIProtocols: ['vscode'] + * this will allow all attributes with URI values to have `vscode` protocol like `vscode://some/path` in addition to the default protocols + * matched by DOMPurify's IS_ALLOWED_URI RegExp: + * @see: https://raw.githubusercontent.com/cure53/DOMPurify/master/src/regexp.ts + * @visibility frontend + */ + additionalAllowedURIProtocols?: string; }; }; } diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 8ddeaa597f..4de9a8bba4 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "1.12.6-next.2", + "version": "1.14.0-next.2", "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..1b41610b14 100644 --- a/plugins/techdocs/report-alpha.api.md +++ b/plugins/techdocs/report-alpha.api.md @@ -4,16 +4,19 @@ ```ts import { AnyApiFactory } from '@backstage/frontend-plugin-api'; -import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { defaultEntityContentGroups } from '@backstage/plugin-catalog-react/alpha'; import { Entity } from '@backstage/catalog-model'; import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; 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'; @@ -39,35 +42,35 @@ const _default: FrontendPlugin< name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory<TApi, TImpl, TDeps>, + ) => ExtensionBlueprintParams<AnyApiFactory>; }>; 'api:techdocs/storage': ExtensionDefinition<{ kind: 'api'; name: 'storage'; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; + output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>; inputs: {}; - params: { - factory: AnyApiFactory; - }; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory<TApi, TImpl, TDeps>, + ) => ExtensionBlueprintParams<AnyApiFactory>; }>; 'empty-state:techdocs/entity-content': ExtensionDefinition<{ config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< JSX_2.Element, 'core.reactElement', { @@ -76,7 +79,7 @@ const _default: FrontendPlugin< >; inputs: { [x: string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; @@ -101,35 +104,31 @@ const _default: FrontendPlugin< group?: string | false | undefined; }; output: - | ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}> - | ConfigurableExtensionDataRef<string, 'core.routing.path', {}> - | ConfigurableExtensionDataRef< + | ExtensionDataRef<string, 'core.routing.path', {}> + | ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}> + | ExtensionDataRef< RouteRef<AnyRouteRefParams>, 'core.routing.ref', { optional: true; } > - | ConfigurableExtensionDataRef< - string, - 'catalog.entity-content-title', - {} - > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< (entity: Entity) => boolean, 'catalog.entity-filter-function', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef< string, 'catalog.entity-filter-expression', { optional: true; } > - | ConfigurableExtensionDataRef< + | ExtensionDataRef<string, 'catalog.entity-content-title', {}> + | ExtensionDataRef< string, 'catalog.entity-content-group', { @@ -165,20 +164,62 @@ const _default: FrontendPlugin< kind: 'entity-content'; name: undefined; params: { + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; + defaultTitle?: [Error: `Use the 'title' param instead`]; + title: string; + defaultGroup?: [Error: `Use the 'group' param instead`]; + group?: keyof defaultEntityContentGroups | (string & {}); loader: () => Promise<JSX.Element>; - defaultPath: string; - defaultTitle: string; - defaultGroup?: keyof defaultEntityContentGroups | (string & {}); routeRef?: RouteRef; 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: + | ExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > + | ExtensionDataRef< + () => IconLinkVerticalProps, + 'entity-icon-link-props', + {} + >; + inputs: {}; + params: { + useProps: () => Omit<IconLinkVerticalProps, 'color'>; + filter?: EntityPredicate | ((entity: Entity) => boolean); + }; + }>; 'nav-item:techdocs': ExtensionDefinition<{ kind: 'nav-item'; name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { title: string; icon: IconComponent; @@ -204,9 +245,9 @@ const _default: FrontendPlugin< path?: string | undefined; }; output: - | ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}> - | ConfigurableExtensionDataRef<string, 'core.routing.path', {}> - | ConfigurableExtensionDataRef< + | ExtensionDataRef<string, 'core.routing.path', {}> + | ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}> + | ExtensionDataRef< RouteRef<AnyRouteRefParams>, 'core.routing.ref', { @@ -215,7 +256,8 @@ const _default: FrontendPlugin< >; inputs: {}; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise<JSX.Element>; routeRef?: RouteRef; }; @@ -228,9 +270,9 @@ const _default: FrontendPlugin< path?: string | undefined; }; output: - | ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}> - | ConfigurableExtensionDataRef<string, 'core.routing.path', {}> - | ConfigurableExtensionDataRef< + | ExtensionDataRef<string, 'core.routing.path', {}> + | ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}> + | ExtensionDataRef< RouteRef<AnyRouteRefParams>, 'core.routing.ref', { @@ -253,7 +295,8 @@ const _default: FrontendPlugin< kind: 'page'; name: 'reader'; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise<JSX.Element>; routeRef?: RouteRef; }; @@ -275,7 +318,7 @@ const _default: FrontendPlugin< } & { noTrack?: boolean | undefined; }; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { predicate?: SearchResultItemExtensionPredicate; component: SearchResultItemExtensionComponent; @@ -285,7 +328,7 @@ const _default: FrontendPlugin< >; inputs: { [x: string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; @@ -318,7 +361,7 @@ export const techDocsSearchResultListItemExtension: ExtensionDefinition<{ } & { noTrack?: boolean | undefined; }; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { predicate?: SearchResultItemExtensionPredicate; component: SearchResultItemExtensionComponent; @@ -328,7 +371,7 @@ export const techDocsSearchResultListItemExtension: ExtensionDefinition<{ >; inputs: { [x: string]: ExtensionInput< - AnyExtensionDataRef, + ExtensionDataRef, { optional: boolean; singleton: boolean; 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 ( <TechDocsReaderPage entityRef={entityRef}> <TechDocsReaderPageSubheader /> <TechDocsReaderPageContent withSearch={withSearch} searchResultUrlMapper={searchResultUrlMapper} + defaultPath={defaultPath} /> </TechDocsReaderPage> ); 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: <DocsIcon />, + href: buildTechDocsURL(entity, viewTechdocLink), + }; +} diff --git a/plugins/techdocs/src/alpha.tsx b/plugins/techdocs/src/alpha/index.tsx similarity index 86% rename from plugins/techdocs/src/alpha.tsx rename to plugins/techdocs/src/alpha/index.tsx index b2bf794e73..5b1033876a 100644 --- a/plugins/techdocs/src/alpha.tsx +++ b/plugins/techdocs/src/alpha/index.tsx @@ -26,7 +26,6 @@ import { } from '@backstage/frontend-plugin-api'; import { configApiRef, - createApiFactory, discoveryApiRef, fetchApiRef, } from '@backstage/core-plugin-api'; @@ -35,16 +34,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,11 +54,21 @@ 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', - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: techdocsStorageApiRef, deps: { configApi: configApiRef, @@ -70,13 +82,12 @@ const techDocsStorageApi = ApiBlueprint.make({ fetchApi, }), }), - }, }); /** @alpha */ const techDocsClientApi = ApiBlueprint.make({ - params: { - factory: createApiFactory({ + params: defineParams => + defineParams({ api: techdocsApiRef, deps: { configApi: configApiRef, @@ -90,7 +101,6 @@ const techDocsClientApi = ApiBlueprint.make({ fetchApi, }), }), - }, }); /** @alpha */ @@ -109,7 +119,7 @@ export const techDocsSearchResultListItemExtension = predicate: result => result.type === 'techdocs', component: async () => { const { TechDocsSearchResultListItem } = await import( - './search/components/TechDocsSearchResultListItem' + '../search/components/TechDocsSearchResultListItem' ); return props => compatWrapper( @@ -127,10 +137,10 @@ export const techDocsSearchResultListItemExtension = */ const techDocsPage = PageBlueprint.make({ params: { - defaultPath: '/docs', + path: '/docs', routeRef: convertLegacyRouteRef(rootRouteRef), loader: () => - import('./home/components/TechDocsIndexPage').then(m => + import('../home/components/TechDocsIndexPage').then(m => compatWrapper(<m.TechDocsIndexPage />), ), }, @@ -155,10 +165,10 @@ const techDocsReaderPage = PageBlueprint.makeWithOverrides({ }); return originalFactory({ - defaultPath: '/docs/:namespace/:kind/:name', + path: '/docs/:namespace/:kind/:name', routeRef: convertLegacyRouteRef(rootDocsRouteRef), loader: async () => - await import('./Router').then(({ TechDocsReaderRouter }) => { + await import('../Router').then(({ TechDocsReaderRouter }) => { return compatWrapper( <TechDocsReaderRouter> <TechDocsReaderLayout /> @@ -189,11 +199,11 @@ const techDocsEntityContent = EntityContentBlueprint.makeWithOverrides({ factory(originalFactory, context) { return originalFactory( { - defaultPath: 'docs', - defaultTitle: 'TechDocs', + path: 'docs', + title: '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 +246,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('<TechDocsReaderPageContent />', () => { }); }); + 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( + <Wrapper> + <TechDocsReaderPageContent + withSearch={false} + defaultPath={defaultPath} + /> + </Wrapper>, + ); + + 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.test.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.test.tsx new file mode 100644 index 0000000000..80b19f0308 --- /dev/null +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.test.tsx @@ -0,0 +1,72 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { render } from '@testing-library/react'; + +// We need to mock react-router-dom hooks used by useInitialRedirect +import { useLocation, useNavigate, useParams } from 'react-router-dom'; + +// Import the module from which the hook is defined +import { useInitialRedirect } from './dom'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: jest.fn(), + useNavigate: jest.fn(), + useParams: jest.fn(), +})); + +describe('useInitialRedirect', () => { + const mockNavigate = jest.fn(); + + beforeEach(() => { + // Reset mocks before each test + mockNavigate.mockReset(); + (useNavigate as jest.Mock).mockReturnValue(mockNavigate); + (useLocation as jest.Mock).mockReturnValue({ + pathname: '/docs/default/Component/backstage-demo', + }); + // Simulate that no current path is provided + (useParams as jest.Mock).mockReturnValue({ '*': '' }); + }); + + const TestComponent: React.FC<{ defaultPath?: string }> = ({ + defaultPath, + }) => { + // Call hook that should trigger a redirect on mount only if defaultPath is a non-empty string. + useInitialRedirect(defaultPath); + return <div>Test</div>; + }; + + it('should not navigate when defaultPath is undefined', () => { + render(<TestComponent defaultPath={undefined} />); + expect(mockNavigate).not.toHaveBeenCalled(); + }); + + it('should navigate when defaultPath is a non-empty string', () => { + render(<TestComponent defaultPath="/overview" />); + expect(mockNavigate).toHaveBeenCalledWith( + '/docs/default/Component/backstage-demo/overview', + { replace: true }, + ); + }); + + it('should not navigate if currPath is non-empty', () => { + // Override useParams to simulate a non-empty currPath + (useParams as jest.Mock).mockReturnValue({ '*': 'existing-path' }); + render(<TestComponent defaultPath={undefined} />); + expect(mockNavigate).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx index 9dbb9745d3..35e847a091 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,28 @@ 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. +export const useInitialRedirect = (defaultPath?: string) => { + 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 +80,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 +99,8 @@ export const useTechDocsReaderDom = ( const [dom, setDom] = useState<HTMLElement | null>(null); const isStyleLoading = useShadowDomStylesLoading(dom); + useInitialRedirect(defaultPath); + const updateSidebarPositionAndHeight = useCallback(() => { if (!dom) return; @@ -267,6 +292,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( + ` + <input type="checkbox" id="cb1" /> + <label class="md-nav__link" for="cb1">Section</label> + `, + { + 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( + `<label class ="md-nav__link">No for</label>`, + { + 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/html/transformer.sanitizer.test.tsx b/plugins/techdocs/src/reader/transformers/html/transformer.sanitizer.test.tsx index f79a41a73d..39d3d229aa 100644 --- a/plugins/techdocs/src/reader/transformers/html/transformer.sanitizer.test.tsx +++ b/plugins/techdocs/src/reader/transformers/html/transformer.sanitizer.test.tsx @@ -28,6 +28,7 @@ const configApiMock: ConfigApi = new ConfigReader({ sanitizer: { allowedCustomElementTagNameRegExp: '^backstage-', allowedCustomElementAttributeNameRegExp: 'attribute1|attribute2', + additionalAllowedURIProtocols: ['permitted'], }, }, }); @@ -39,6 +40,28 @@ const wrapper: FC<PropsWithChildren<{}>> = ({ children }) => ( ); describe('Transformers > Html > Sanitizer Custom Elements', () => { + it('allows additional protocols in URIs when provided via config', async () => { + const { result } = renderHook(() => useSanitizerTransformer(), { wrapper }); + const dirtyDom = document.createElement('html'); + + const dirtyHTML = ` + <body> + <a href="permitted:mcp/install">Yep</a> + <a href="nope://not-allowed">Nope</a> + <a href="https://example.com">Example</a> + </body>`; + dirtyDom.innerHTML = dirtyHTML; + + const clearDom = await result.current(dirtyDom); // calling html transformer + const elements = Array.from( + clearDom.querySelectorAll<HTMLAnchorElement>('body > a'), + ); + expect(elements).toHaveLength(3); + expect(elements[0].getAttribute('href')).toEqual('permitted:mcp/install'); + expect(elements[1].getAttribute('href')).toBeNull(); + expect(elements[2].getAttribute('href')).toEqual('https://example.com'); + }); + it('should return a function that allows custom elements matching the pattern in the given dom element', async () => { const { result } = renderHook(() => useSanitizerTransformer(), { wrapper }); diff --git a/plugins/techdocs/src/reader/transformers/html/transformer.ts b/plugins/techdocs/src/reader/transformers/html/transformer.ts index 42f34f0410..7c341c783e 100644 --- a/plugins/techdocs/src/reader/transformers/html/transformer.ts +++ b/plugins/techdocs/src/reader/transformers/html/transformer.ts @@ -78,6 +78,35 @@ export const useSanitizerTransformer = (): Transformer => { const attributeNameCheck = config?.getOptionalString( 'allowedCustomElementAttributeNameRegExp', ); + const additionalAllowedURIProtocols = + config?.getOptionalStringArray('additionalAllowedURIProtocols') || []; + + // Define allowed URI protocols, including any additional ones from the config. + // The default protocols are based on the DOMPurify defaults. + const allowedURIProtocols = [ + 'callto', + 'cid', + 'ftp', + 'ftps', + 'http', + 'https', + 'mailto', + 'matrix', + 'sms', + 'tel', + 'xmpp', + ...additionalAllowedURIProtocols, + ].filter(Boolean); + + const allowedURIRegExp = new RegExp( + // This regex is not exposed by DOMPurify, so we need to define it ourselves. + // It is possible for this to drift from the default in future versions of DOMPurify. + // See: https://raw.githubusercontent.com/cure53/DOMPurify/master/src/regexp.ts + `^(?:${allowedURIProtocols.join( + '|', + )}:|[^a-z]|[a-z+.-]+(?:[^a-z+.\\-:]|$))`, + 'i', + ); // using outerHTML as we want to preserve the html tag attributes (lang) return DOMPurify.sanitize(dom.outerHTML, { @@ -86,6 +115,7 @@ export const useSanitizerTransformer = (): Transformer => { ADD_ATTR: ['http-equiv', 'content', 'dominant-baseline'], WHOLE_DOCUMENT: true, RETURN_DOM: true, + ALLOWED_URI_REGEXP: allowedURIRegExp, CUSTOM_ELEMENT_HANDLING: { tagNameCheck: tagNameCheck ? new RegExp(tagNameCheck) : undefined, attributeNameCheck: attributeNameCheck 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/reader/transformers/styles/rules/layout.ts b/plugins/techdocs/src/reader/transformers/styles/rules/layout.ts index 1a326c62b8..43d3112a73 100644 --- a/plugins/techdocs/src/reader/transformers/styles/rules/layout.ts +++ b/plugins/techdocs/src/reader/transformers/styles/rules/layout.ts @@ -16,9 +16,21 @@ import { RuleOptions } from './types'; -const SIDEBAR_WIDTH = '224px'; +export default ({ theme, sidebar }: RuleOptions) => { + const sidebarWidth = sidebar.isPresent ? `${sidebar.width}px` : '0px'; + const sidebarMargin = sidebar.isPresent ? '16rem' : '0'; -export default ({ theme, sidebar }: RuleOptions) => ` + // Calculate the sidebar position for mobile layouts + let sidebarLeftPosition; + if (!sidebar.isPresent) { + sidebarLeftPosition = '-16rem'; + } else if (sidebar.isPinned) { + sidebarLeftPosition = `calc(-16rem + ${sidebarWidth})`; + } else { + sidebarLeftPosition = 'calc(-16rem + 72px)'; + } + + return ` /*================== Layout ==================*/ /* mkdocs material v9 compat */ @@ -122,12 +134,12 @@ export default ({ theme, sidebar }: RuleOptions) => ` .md-content { max-width: calc(100% - 16rem * 2); - margin-left: 16rem; + margin-left: ${sidebarMargin}; margin-bottom: 50px; } .md-content > .md-sidebar { - left: 16rem; + left: ${sidebarMargin}; } .md-footer { @@ -209,11 +221,7 @@ export default ({ theme, sidebar }: RuleOptions) => ` .md-sidebar--primary { width: 16rem !important; z-index: 200; - left: ${ - sidebar.isPinned - ? `calc(-16rem + ${SIDEBAR_WIDTH})` - : 'calc(-16rem + 72px)' - } !important; + left: ${sidebarLeftPosition} !important; } .md-sidebar--secondary:not([hidden]) { display: none; @@ -269,3 +277,4 @@ export default ({ theme, sidebar }: RuleOptions) => ` } } `; +}; diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/types.ts b/plugins/techdocs/src/reader/transformers/styles/rules/types.ts index e2747ad85a..1b6b86cccd 100644 --- a/plugins/techdocs/src/reader/transformers/styles/rules/types.ts +++ b/plugins/techdocs/src/reader/transformers/styles/rules/types.ts @@ -22,6 +22,10 @@ import { Theme } from '@material-ui/core/styles'; type BackstageSidebar = { /** Tracks whether the user pinned the sidebar or not. */ isPinned: boolean; + /** Tracks whether a Backstage sidebar is actually present in the DOM. */ + isPresent: boolean; + /** The actual width of the sidebar in pixels, if present. */ + width: number; }; /** diff --git a/plugins/techdocs/src/reader/transformers/styles/transformer.ts b/plugins/techdocs/src/reader/transformers/styles/transformer.ts index b1b1b437c2..fdcb5a2e73 100644 --- a/plugins/techdocs/src/reader/transformers/styles/transformer.ts +++ b/plugins/techdocs/src/reader/transformers/styles/transformer.ts @@ -20,10 +20,26 @@ import { useSidebarPinState } from '@backstage/core-components'; import { Transformer } from '../transformer'; import { rules } from './rules'; +const SIDEBAR_WIDTH = 224; /** - * Sidebar pinned state to be used in computing style injections. + * Enhanced sidebar state that detects presence through CSS environment. */ -const useSidebar = () => useSidebarPinState(); +const useSidebar = () => { + const pinState = useSidebarPinState(); + + const hasSidebar = useMemo( + () => + typeof window !== 'undefined' && + Boolean(document.querySelector('[class*="BackstageSidebar"]')), + [], + ); + + return { + isPinned: pinState.isPinned, + isPresent: hasSidebar, + width: SIDEBAR_WIDTH, + }; +}; /** * Process all rules and concatenate their definitions into a single style. 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 5c3a22f044..a7a14282f4 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,126 @@ # @backstage/plugin-user-settings-backend +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.2-next.0 + - @backstage/plugin-auth-node@0.6.6-next.0 + - @backstage/backend-plugin-api@1.4.2-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.23-next.0 + - @backstage/plugin-user-settings-common@0.0.1 + +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1 + - @backstage/backend-plugin-api@1.4.1 + - @backstage/plugin-auth-node@0.6.5 + - @backstage/plugin-signals-node@0.1.22 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.1 + - @backstage/backend-plugin-api@1.4.1-next.0 + - @backstage/plugin-auth-node@0.6.5-next.0 + - @backstage/plugin-signals-node@0.1.22-next.0 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.1-next.0 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.21 + - @backstage/plugin-user-settings-common@0.0.1 + +## 0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0 + - @backstage/plugin-auth-node@0.6.4 + - @backstage/backend-plugin-api@1.4.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.21 + - @backstage/plugin-user-settings-common@0.0.1 + +## 0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.11.0-next.2 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-signals-node@0.1.21-next.1 + - @backstage/plugin-user-settings-common@0.0.1 + +## 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 + +- Updated dependencies + - @backstage/backend-defaults@0.10.0 + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.20 + - @backstage/plugin-user-settings-common@0.0.1 + +## 0.3.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.3 + - @backstage/plugin-auth-node@0.6.3-next.2 + - @backstage/backend-plugin-api@1.3.1-next.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.20-next.2 + - @backstage/plugin-user-settings-common@0.0.1 + ## 0.3.2-next.2 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index b216e566a5..17158fc0b3 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-next.2", + "version": "0.3.5-next.0", "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 f5170c340f..44efabc017 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,219 @@ # @backstage/plugin-user-settings +## 0.8.25-next.2 + +### Patch Changes + +- e4ddf22: Internal update to align with new blueprint parameter naming in the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.11.0-next.1 + - @backstage/core-compat-api@0.5.0-next.2 + - @backstage/plugin-catalog-react@1.20.0-next.2 + - @backstage/core-components@0.17.5-next.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.8-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.15 + - @backstage/plugin-user-settings-common@0.0.1 + +## 0.8.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.5-next.1 + - @backstage/plugin-catalog-react@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.11.0-next.0 + - @backstage/theme@0.6.8-next.0 + - @backstage/core-components@0.17.5-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-app-api@1.18.0 + - @backstage/core-plugin-api@1.10.9 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.15 + - @backstage/plugin-user-settings-common@0.0.1 + +## 0.8.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.2-next.0 + - @backstage/core-compat-api@0.4.5-next.0 + - @backstage/frontend-plugin-api@0.10.4 + +## 0.8.24 + +### Patch Changes + +- aa3b054: Added `signIn` and `signOut` analytic events to the `@backstage/core-components` of sign in and sign out. +- Updated dependencies + - @backstage/plugin-catalog-react@1.19.1 + - @backstage/catalog-model@1.7.5 + - @backstage/core-components@0.17.4 + - @backstage/core-plugin-api@1.10.9 + - @backstage/theme@0.6.7 + - @backstage/core-app-api@1.18.0 + - @backstage/core-compat-api@0.4.4 + - @backstage/frontend-plugin-api@0.10.4 + - @backstage/plugin-signals-react@0.0.15 + +## 0.8.24-next.2 + +### Patch Changes + +- aa3b054: Added `signIn` and `signOut` analytic events to the `@backstage/core-components` of sign in and sign out. +- Updated dependencies + - @backstage/theme@0.6.7-next.1 + - @backstage/core-app-api@1.18.0-next.1 + - @backstage/core-components@0.17.4-next.2 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + +## 0.8.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.5-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.1 + - @backstage/core-app-api@1.17.2-next.0 + - @backstage/core-components@0.17.4-next.1 + - @backstage/core-plugin-api@1.10.9-next.0 + - @backstage/core-compat-api@0.4.4-next.1 + - @backstage/frontend-plugin-api@0.10.4-next.1 + - @backstage/plugin-signals-react@0.0.15-next.0 + +## 0.8.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.7-next.0 + - @backstage/plugin-catalog-react@1.19.1-next.0 + - @backstage/core-components@0.17.4-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-app-api@1.17.1 + - @backstage/core-compat-api@0.4.4-next.0 + - @backstage/core-plugin-api@1.10.8 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.4-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.14 + - @backstage/plugin-user-settings-common@0.0.1 + +## 0.8.23 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- ab6d3dd: Add some translation to user-setting plugin +- c83cd8b: Fixed some circular or otherwise unclear imports +- Updated dependencies + - @backstage/core-components@0.17.3 + - @backstage/core-plugin-api@1.10.8 + - @backstage/frontend-plugin-api@0.10.3 + - @backstage/plugin-catalog-react@1.19.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-app-api@1.17.1 + - @backstage/core-compat-api@0.4.3 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.14 + - @backstage/plugin-user-settings-common@0.0.1 + +## 0.8.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.3-next.0 + - @backstage/plugin-catalog-react@1.19.0-next.2 + - @backstage/frontend-plugin-api@0.10.3-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-app-api@1.17.0 + - @backstage/core-compat-api@0.4.3-next.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @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.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 + +- a7bfdb6: plugin-user-settingsgs support i18n +- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`. +- 5b04b14: Uppercase language name in language select +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2 + - @backstage/theme@0.6.6 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.2 + - @backstage/plugin-catalog-react@1.18.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-signals-react@0.0.13 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-user-settings-common@0.0.1 + +## 0.8.22-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.17.0-next.1 + - @backstage/core-compat-api@0.4.2-next.3 + - @backstage/core-components@0.17.2-next.1 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog-react@1.18.0-next.3 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.13-next.0 + - @backstage/plugin-user-settings-common@0.0.1 + ## 0.8.22-next.2 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 54dfcc0d26..e04571e1e2 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-next.2", + "version": "0.8.25-next.2", "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..1e87395800 100644 --- a/plugins/user-settings/report-alpha.api.md +++ b/plugins/user-settings/report-alpha.api.md @@ -5,6 +5,7 @@ ```ts import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -25,7 +26,7 @@ const _default: FrontendPlugin< name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { title: string; icon: IconComponent; @@ -49,9 +50,9 @@ const _default: FrontendPlugin< path?: string | undefined; }; output: - | ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}> - | ConfigurableExtensionDataRef<string, 'core.routing.path', {}> - | ConfigurableExtensionDataRef< + | ExtensionDataRef<string, 'core.routing.path', {}> + | ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}> + | ExtensionDataRef< RouteRef<AnyRouteRefParams>, 'core.routing.ref', { @@ -70,7 +71,8 @@ const _default: FrontendPlugin< kind: 'page'; name: undefined; params: { - defaultPath: string; + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; loader: () => Promise<JSX.Element>; routeRef?: RouteRef; }; @@ -85,7 +87,7 @@ export const settingsNavItem: ExtensionDefinition<{ name: undefined; config: {}; configInput: {}; - output: ConfigurableExtensionDataRef< + output: ExtensionDataRef< { title: string; icon: IconComponent; @@ -154,6 +156,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..e71c61febc 100644 --- a/plugins/user-settings/src/alpha.tsx +++ b/plugins/user-settings/src/alpha.tsx @@ -39,7 +39,7 @@ const userSettingsPage = PageBlueprint.makeWithOverrides({ }, factory(originalFactory, { inputs }) { return originalFactory({ - defaultPath: '/settings', + path: '/settings', routeRef: convertLegacyRouteRef(settingsRouteRef), loader: () => import('./components/SettingsPage').then(m => @@ -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 ( - <InfoCard title="Appearance" variant="gridItem"> + <InfoCard title={t('appearanceCard.title')} variant="gridItem"> <List dense> <UserSettingsThemeToggle /> <UserSettingsLanguageToggle /> diff --git a/plugins/user-settings/src/components/General/UserSettingsMenu.tsx b/plugins/user-settings/src/components/General/UserSettingsMenu.tsx index 6fb55896e9..dc954e862f 100644 --- a/plugins/user-settings/src/components/General/UserSettingsMenu.tsx +++ b/plugins/user-settings/src/components/General/UserSettingsMenu.tsx @@ -25,6 +25,7 @@ import { identityApiRef, errorApiRef, useApi, + useAnalytics, } from '@backstage/core-plugin-api'; import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { userSettingsTranslationRef } from '../../translation'; @@ -36,6 +37,7 @@ export const UserSettingsMenu = () => { const [open, setOpen] = useState(false); const [anchorEl, setAnchorEl] = useState<undefined | HTMLElement>(undefined); const { t } = useTranslationRef(userSettingsTranslationRef); + const analytics = useAnalytics(); const handleOpen = (event: MouseEvent<HTMLButtonElement>) => { setAnchorEl(event.currentTarget); @@ -59,9 +61,10 @@ export const UserSettingsMenu = () => { <Menu anchorEl={anchorEl} open={open} onClose={handleClose}> <MenuItem data-testid="sign-out" - onClick={() => - identityApi.signOut().catch(error => errorApi.post(error)) - } + onClick={() => { + identityApi.signOut().catch(error => errorApi.post(error)); + analytics.captureEvent('signOut', 'success'); + }} > <ListItemIcon> <SignOutIcon /> 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 ( - <InfoCard title="Profile" variant="gridItem"> + <InfoCard title={t('profileCard.title')} variant="gridItem"> <Grid container spacing={6}> <Grid item> <UserSettingsSignInAvatar size={96} /> diff --git a/plugins/user-settings/src/index.ts b/plugins/user-settings/src/index.ts index bc878ecbb3..e64d3c926c 100644 --- a/plugins/user-settings/src/index.ts +++ b/plugins/user-settings/src/index.ts @@ -26,4 +26,4 @@ export { userSettingsPlugin as plugin, UserSettingsPage, } from './plugin'; -export * from './components/'; +export * from './components'; 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/scripts/techdocs-cli.js b/scripts/techdocs-cli.js index 7cc07f15fa..90dcc40f39 100644 --- a/scripts/techdocs-cli.js +++ b/scripts/techdocs-cli.js @@ -19,7 +19,7 @@ const { execSync } = require('child_process'); const args = process.argv.slice(2); -execSync(`yarn -s workspace @techdocs/cli build`, { stdio: 'inherit' }); +execSync(`yarn workspace @techdocs/cli build`, { stdio: 'inherit' }); execSync(`yarn workspace @techdocs/cli link`, { stdio: 'ignore' }); execSync(`techdocs-cli ${args.join(' ')}`, { stdio: 'inherit' }); execSync(`yarn workspace @techdocs/cli unlink`, { stdio: 'ignore' }); diff --git a/storybook/.gitignore b/storybook/.gitignore deleted file mode 100644 index bc6d3062c4..0000000000 --- a/storybook/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -*storybook.log \ No newline at end of file diff --git a/storybook/.npmrc b/storybook/.npmrc deleted file mode 100644 index c3c66347fd..0000000000 --- a/storybook/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -registry=https://registry.npmjs.org/ -engine-strict=true diff --git a/storybook/.storybook/preview.tsx b/storybook/.storybook/preview.tsx deleted file mode 100644 index 18c07a6266..0000000000 --- a/storybook/.storybook/preview.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { TestApiProvider } from '@backstage/test-utils'; -import { Content, AlertDisplay } from '@backstage/core-components'; -import { lightTheme, darkTheme } from '@backstage/theme'; -import { CssBaseline, ThemeProvider } from '@material-ui/core'; -import { apis } from './apis'; -import { withThemeFromJSXProvider } from '@storybook/addon-themes'; - -import type { Preview, ReactRenderer } from '@storybook/react'; - -const preview: Preview = { - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - darkMode: { - current: 'light', - }, - layout: 'fullscreen', - }, - decorators: [ - withThemeFromJSXProvider<ReactRenderer>({ - themes: { - Light: lightTheme, - Dark: darkTheme, - }, - defaultTheme: 'Light', - Provider: ThemeProvider, - GlobalStyles: CssBaseline, - }), - Story => ( - // @ts-ignore - TODO: Fix this - <TestApiProvider apis={apis}> - <AlertDisplay /> - <Content> - <Story /> - </Content> - </TestApiProvider> - ), - ], -}; - -export default preview; - -export const parameters = { - options: { - storySort: { - order: ['Plugins', 'Layout', 'Navigation'], - }, - }, -}; diff --git a/storybook/CHANGELOG.md b/storybook/CHANGELOG.md deleted file mode 100644 index 37b76b4de3..0000000000 --- a/storybook/CHANGELOG.md +++ /dev/null @@ -1,36 +0,0 @@ -# storybook - -## 0.2.2 - -### Patch Changes - -- Updated dependencies - - @backstage/core-plugin-api@0.6.0 - - @backstage/test-utils@0.2.3 - - @backstage/core-app-api@0.5.0 - -## 0.2.2-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/core-plugin-api@0.6.0-next.0 - - @backstage/core-app-api@0.5.0-next.0 - - @backstage/test-utils@0.2.3-next.0 - -## 0.2.1 - -### Patch Changes - -- Updated dependencies - - @backstage/test-utils@0.2.2 - - @backstage/core-plugin-api@0.5.0 - - @backstage/core-app-api@0.4.0 - -## 0.2.0 - -### Patch Changes - -- Updated dependencies [ae5983387] -- Updated dependencies [0d4459c08] - - @backstage/theme@0.2.0 diff --git a/storybook/README.md b/storybook/README.md deleted file mode 100644 index 600e2409ab..0000000000 --- a/storybook/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# storybook - -This package provides a Storybook build for Backstage. See https://backstage.io/storybook/. - -## Usage - -To run the storybook locally, call `yarn storybook` in repo root. This will show the default set of stories that we publish to https://backstage.io/storybook - -If you want to show stories other than the default set, you can pass one or more package paths as an argument when calling storybook, for example: - -```sh -yarn storybook plugins/search -``` - -## Why is this not part of `@backstage/core-components`? - -This separate storybook package exists because of dependency conflicts with `@backstage/cli`. It uses `nohoist` to avoid the conflicts, and since you can only use that in private packages it has to be separated out of `@backstage/core-components`. diff --git a/storybook/package.json b/storybook/package.json deleted file mode 100644 index 58cce5e57e..0000000000 --- a/storybook/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "backstage-storybook", - "version": "0.2.2", - "description": "Storybook build for components package", - "private": true, - "type": "module", - "scripts": { - "build-storybook": "storybook build --output-dir dist", - "storybook": "storybook dev -p 6006" - }, - "eslintConfig": { - "extends": [ - "plugin:storybook/recommended" - ] - }, - "dependencies": { - "@storybook/addon-storysource": "^8.3.6", - "react": "^18.0.2", - "react-dom": "^18.0.2" - }, - "devDependencies": { - "@chromatic-com/storybook": "^1.9.0", - "@storybook/addon-essentials": "^8.3.5", - "@storybook/addon-interactions": "^8.3.5", - "@storybook/addon-links": "^8.3.5", - "@storybook/addon-themes": "^8.3.6", - "@storybook/blocks": "^8.3.5", - "@storybook/react": "^8.3.5", - "@storybook/react-vite": "^8.3.5", - "@storybook/test": "^8.3.5", - "@storybook/types": "^8.3.5", - "@types/react": "^18.0.0", - "@types/react-dom": "^18.0.0", - "@typescript-eslint/eslint-plugin": "^5.0.0", - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^9.11.1", - "eslint-plugin-storybook": "^0.11.0", - "globals": "^15.9.0", - "react-router-dom": "^6.3.0", - "storybook": "^8.3.5", - "typescript": "^5.5.3", - "typescript-eslint": "^8.7.0", - "vite": "^5.4.8" - } -} diff --git a/storybook/yarn.lock b/storybook/yarn.lock deleted file mode 100644 index 09cd7357fa..0000000000 --- a/storybook/yarn.lock +++ /dev/null @@ -1,4961 +0,0 @@ -# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - -__metadata: - version: 8 - cacheKey: 10 - -"@adobe/css-tools@npm:^4.4.0": - version: 4.4.0 - resolution: "@adobe/css-tools@npm:4.4.0" - checksum: 10/9c6315fe9efa5075d6ddb6ded7a1424bc9c41a01f2314b6bdcc368723985fe161008d03ddcc2b27b2da50cb9c14190fbce965d15cefe5f9a31bdd43f35b52115 - languageName: node - linkType: hard - -"@ampproject/remapping@npm:^2.2.0": - version: 2.3.0 - resolution: "@ampproject/remapping@npm:2.3.0" - dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10/f3451525379c68a73eb0a1e65247fbf28c0cccd126d93af21c75fceff77773d43c0d4a2d51978fb131aff25b5f2cb41a9fe48cc296e61ae65e679c4f6918b0ab - languageName: node - linkType: hard - -"@babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/code-frame@npm:7.25.7" - dependencies: - "@babel/highlight": "npm:^7.25.7" - picocolors: "npm:^1.0.0" - checksum: 10/000fb8299fb35b6217d4f6c6580dcc1fa2f6c0f82d0a54b8a029966f633a8b19b490a7a906b56a94e9d8bee91c3bc44c74c44c33fb0abaa588202f6280186291 - languageName: node - linkType: hard - -"@babel/compat-data@npm:^7.25.7": - version: 7.25.8 - resolution: "@babel/compat-data@npm:7.25.8" - checksum: 10/269fcb0d89e02e36c8a11e0c1b960a6b4204e88f59f20c374d28f8e318f4cd5ded42dfedc4b54162065e6a10f71c0de651f5ed3f9b45d3a4b52240196df85726 - languageName: node - linkType: hard - -"@babel/core@npm:^7.18.9": - version: 7.25.8 - resolution: "@babel/core@npm:7.25.8" - dependencies: - "@ampproject/remapping": "npm:^2.2.0" - "@babel/code-frame": "npm:^7.25.7" - "@babel/generator": "npm:^7.25.7" - "@babel/helper-compilation-targets": "npm:^7.25.7" - "@babel/helper-module-transforms": "npm:^7.25.7" - "@babel/helpers": "npm:^7.25.7" - "@babel/parser": "npm:^7.25.8" - "@babel/template": "npm:^7.25.7" - "@babel/traverse": "npm:^7.25.7" - "@babel/types": "npm:^7.25.8" - convert-source-map: "npm:^2.0.0" - debug: "npm:^4.1.0" - gensync: "npm:^1.0.0-beta.2" - json5: "npm:^2.2.3" - semver: "npm:^6.3.1" - checksum: 10/31eb1a8ca1a3cc0026060720eb290e68205d95c5c00fbd831e69ddc0810f5920b8eb2749db1889ac0a0312b6eddbf321d18a996a88858f3b75c9582bef9ec1e4 - languageName: node - linkType: hard - -"@babel/generator@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/generator@npm:7.25.7" - dependencies: - "@babel/types": "npm:^7.25.7" - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.25" - jsesc: "npm:^3.0.2" - checksum: 10/01542829621388077fa8a7464970c1db0f748f1482968dddf5332926afe4003f953cbe08e3bbbb0a335b11eba0126c9a81779bd1c5baed681a9ccec4ae63b217 - languageName: node - linkType: hard - -"@babel/helper-compilation-targets@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/helper-compilation-targets@npm:7.25.7" - dependencies: - "@babel/compat-data": "npm:^7.25.7" - "@babel/helper-validator-option": "npm:^7.25.7" - browserslist: "npm:^4.24.0" - lru-cache: "npm:^5.1.1" - semver: "npm:^6.3.1" - checksum: 10/bbf9be8480da3f9a89e36e9ea2e1c76601014c1074ccada7c2edb1adeb3b62bc402cc4abaf8d16760734b25eceb187a9510ce44f6a7a6f696ccc74f69283625b - languageName: node - linkType: hard - -"@babel/helper-module-imports@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/helper-module-imports@npm:7.25.7" - dependencies: - "@babel/traverse": "npm:^7.25.7" - "@babel/types": "npm:^7.25.7" - checksum: 10/94556712c27058ea35a1a39e21a3a9f067cd699405b64333d7d92b2b3d2f24d6f0ffa51aedba0b908e320acb1854e70d296259622e636fb021eeae9a6d996f01 - languageName: node - linkType: hard - -"@babel/helper-module-transforms@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/helper-module-transforms@npm:7.25.7" - dependencies: - "@babel/helper-module-imports": "npm:^7.25.7" - "@babel/helper-simple-access": "npm:^7.25.7" - "@babel/helper-validator-identifier": "npm:^7.25.7" - "@babel/traverse": "npm:^7.25.7" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/480309b1272ceaa985de1393f0e4c41aede0d5921ca644cec5aeaf43c8e4192b6dd56a58ef6d7e9acd02a43184ab45d3b241fc8c3a0a00f9dbb30235fd8a1181 - languageName: node - linkType: hard - -"@babel/helper-simple-access@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/helper-simple-access@npm:7.25.7" - dependencies: - "@babel/traverse": "npm:^7.25.7" - "@babel/types": "npm:^7.25.7" - checksum: 10/42da1c358f2516337a4f2927c77ebb952907543b9f85d7cb1e2b5b5f6d808cdc081ee66a73e2ecdf48c315d9b0c2a81a857d5e1923ea210b8e81aba5e6cd2b53 - languageName: node - linkType: hard - -"@babel/helper-string-parser@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/helper-string-parser@npm:7.25.7" - checksum: 10/2b8de9fa86c3f3090a349f1ce6e8ee2618a95355cbdafc6f228d82fa4808c84bf3d1d25290c6616d0a18b26b6cfeb6ec2aeebf01404bc8c60051d0094209f0e6 - languageName: node - linkType: hard - -"@babel/helper-validator-identifier@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/helper-validator-identifier@npm:7.25.7" - checksum: 10/ec6934cc47fc35baaeb968414a372b064f14f7b130cf6489a014c9486b0fd2549b3c6c682cc1fc35080075e8e38d96aeb40342d63d09fc1a62510c8ce25cde1e - languageName: node - linkType: hard - -"@babel/helper-validator-option@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/helper-validator-option@npm:7.25.7" - checksum: 10/3c46cbdd666d176f90a0b7e952a0c6e92184b66633336eca79aca243d1f86085ec339a6e45c3d44efa9e03f1829b470a350ddafa70926af6bbf1ac611284f8d3 - languageName: node - linkType: hard - -"@babel/helpers@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/helpers@npm:7.25.7" - dependencies: - "@babel/template": "npm:^7.25.7" - "@babel/types": "npm:^7.25.7" - checksum: 10/2632909f83aa99e8b0da4e10e5ab7fc4f0274e6497bb0f17071e004e037d25e4a595583620261dc21410a526fb32b4f7063c3e15e60ed7890a6f9b8ad52312c5 - languageName: node - linkType: hard - -"@babel/highlight@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/highlight@npm:7.25.7" - dependencies: - "@babel/helper-validator-identifier": "npm:^7.25.7" - chalk: "npm:^2.4.2" - js-tokens: "npm:^4.0.0" - picocolors: "npm:^1.0.0" - checksum: 10/823be2523d246dbf80aab3cc81c2a36c6111b16ac2949ef06789da54387824c2bfaa88c6627cdeb4ba7151d047a5d6765e49ebd0b478aba09759250111e65e08 - languageName: node - linkType: hard - -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.25.7, @babel/parser@npm:^7.25.8": - version: 7.25.8 - resolution: "@babel/parser@npm:7.25.8" - dependencies: - "@babel/types": "npm:^7.25.8" - bin: - parser: ./bin/babel-parser.js - checksum: 10/0396eb71e379903cedb43862f84ebb1bec809c41e82b4894d2e6e83b8e8bc636ba6eff45382e615baefdb2399ede76ca82247ecc3a9877ac16eb3140074a3276 - languageName: node - linkType: hard - -"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.17.8": - version: 7.25.7 - resolution: "@babel/runtime@npm:7.25.7" - dependencies: - regenerator-runtime: "npm:^0.14.0" - checksum: 10/73411fe0f1bff3a962586cef05b30f49e554b6563767e6d84f7d79d605b2c20e7fc3df291a3aebef69043181a8f893afdab9e6672557a5c2d08b9377d6f678cd - languageName: node - linkType: hard - -"@babel/template@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/template@npm:7.25.7" - dependencies: - "@babel/code-frame": "npm:^7.25.7" - "@babel/parser": "npm:^7.25.7" - "@babel/types": "npm:^7.25.7" - checksum: 10/49e1e88d2eac17d31ae28d6cf13d6d29c1f49384c4f056a6751c065d6565c351e62c01ce6b11fef5edb5f3a77c87e114ea7326ca384fa618b4834e10cf9b20f3 - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/traverse@npm:7.25.7" - dependencies: - "@babel/code-frame": "npm:^7.25.7" - "@babel/generator": "npm:^7.25.7" - "@babel/parser": "npm:^7.25.7" - "@babel/template": "npm:^7.25.7" - "@babel/types": "npm:^7.25.7" - debug: "npm:^4.3.1" - globals: "npm:^11.1.0" - checksum: 10/5b2d332fcd6bc78e6500c997e79f7e2a54dfb357e06f0908cb7f0cdd9bb54e7fd3c5673f45993849d433d01ea6076a6d04b825958f0cfa01288ad55ffa5c286f - languageName: node - linkType: hard - -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.25.7, @babel/types@npm:^7.25.8": - version: 7.25.8 - resolution: "@babel/types@npm:7.25.8" - dependencies: - "@babel/helper-string-parser": "npm:^7.25.7" - "@babel/helper-validator-identifier": "npm:^7.25.7" - to-fast-properties: "npm:^2.0.0" - checksum: 10/973108dbb189916bb87360f2beff43ae97f1b08f1c071bc6499d363cce48b3c71674bf3b59dfd617f8c5062d1c76dc2a64232bc07b6ccef831fd0c06162d44d9 - languageName: node - linkType: hard - -"@chromatic-com/storybook@npm:^1.9.0": - version: 1.9.0 - resolution: "@chromatic-com/storybook@npm:1.9.0" - dependencies: - chromatic: "npm:^11.4.0" - filesize: "npm:^10.0.12" - jsonfile: "npm:^6.1.0" - react-confetti: "npm:^6.1.0" - strip-ansi: "npm:^7.1.0" - checksum: 10/27ca6930a4978a52471ed7256cbf549e57b5c9c45b650b55461400a63692f5b30a7a0a7436faadd713952ce6285b873041494c10e92cccdc5bdafee1f1755459 - languageName: node - linkType: hard - -"@esbuild/aix-ppc64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/aix-ppc64@npm:0.21.5" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/aix-ppc64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/aix-ppc64@npm:0.25.0" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/android-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-arm64@npm:0.21.5" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/android-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/android-arm64@npm:0.25.0" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/android-arm@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-arm@npm:0.21.5" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@esbuild/android-arm@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/android-arm@npm:0.25.0" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@esbuild/android-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-x64@npm:0.21.5" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/android-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/android-x64@npm:0.25.0" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/darwin-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/darwin-arm64@npm:0.21.5" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/darwin-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/darwin-arm64@npm:0.25.0" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/darwin-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/darwin-x64@npm:0.21.5" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/darwin-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/darwin-x64@npm:0.25.0" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/freebsd-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/freebsd-arm64@npm:0.21.5" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/freebsd-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/freebsd-arm64@npm:0.25.0" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/freebsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/freebsd-x64@npm:0.21.5" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/freebsd-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/freebsd-x64@npm:0.25.0" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/linux-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-arm64@npm:0.21.5" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/linux-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-arm64@npm:0.25.0" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/linux-arm@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-arm@npm:0.21.5" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@esbuild/linux-arm@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-arm@npm:0.25.0" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@esbuild/linux-ia32@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-ia32@npm:0.21.5" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/linux-ia32@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-ia32@npm:0.25.0" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/linux-loong64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-loong64@npm:0.21.5" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - -"@esbuild/linux-loong64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-loong64@npm:0.25.0" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - -"@esbuild/linux-mips64el@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-mips64el@npm:0.21.5" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - -"@esbuild/linux-mips64el@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-mips64el@npm:0.25.0" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - -"@esbuild/linux-ppc64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-ppc64@npm:0.21.5" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/linux-ppc64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-ppc64@npm:0.25.0" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/linux-riscv64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-riscv64@npm:0.21.5" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - -"@esbuild/linux-riscv64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-riscv64@npm:0.25.0" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - -"@esbuild/linux-s390x@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-s390x@npm:0.21.5" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - -"@esbuild/linux-s390x@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-s390x@npm:0.25.0" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - -"@esbuild/linux-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-x64@npm:0.21.5" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/linux-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-x64@npm:0.25.0" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/netbsd-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/netbsd-arm64@npm:0.25.0" - conditions: os=netbsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/netbsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/netbsd-x64@npm:0.21.5" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/netbsd-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/netbsd-x64@npm:0.25.0" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openbsd-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/openbsd-arm64@npm:0.25.0" - conditions: os=openbsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/openbsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/openbsd-x64@npm:0.21.5" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openbsd-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/openbsd-x64@npm:0.25.0" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/sunos-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/sunos-x64@npm:0.21.5" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/sunos-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/sunos-x64@npm:0.25.0" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/win32-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-arm64@npm:0.21.5" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/win32-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/win32-arm64@npm:0.25.0" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/win32-ia32@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-ia32@npm:0.21.5" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/win32-ia32@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/win32-ia32@npm:0.25.0" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/win32-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-x64@npm:0.21.5" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/win32-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/win32-x64@npm:0.25.0" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": - version: 4.4.0 - resolution: "@eslint-community/eslint-utils@npm:4.4.0" - dependencies: - eslint-visitor-keys: "npm:^3.3.0" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10/8d70bcdcd8cd279049183aca747d6c2ed7092a5cf0cf5916faac1ef37ffa74f0c245c2a3a3d3b9979d9dfdd4ca59257b4c5621db699d637b847a2c5e02f491c2 - languageName: node - linkType: hard - -"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.12.1, @eslint-community/regexpp@npm:^4.4.0": - version: 4.12.1 - resolution: "@eslint-community/regexpp@npm:4.12.1" - checksum: 10/c08f1dd7dd18fbb60bdd0d85820656d1374dd898af9be7f82cb00451313402a22d5e30569c150315b4385907cdbca78c22389b2a72ab78883b3173be317620cc - languageName: node - linkType: hard - -"@eslint/config-array@npm:^0.20.0": - version: 0.20.0 - resolution: "@eslint/config-array@npm:0.20.0" - dependencies: - "@eslint/object-schema": "npm:^2.1.6" - debug: "npm:^4.3.1" - minimatch: "npm:^3.1.2" - checksum: 10/9db7f6cbb5363f2f98ee4805ce09d1a95c4349e86f3f456f2c23a0849b7a6aa8d2be4c25e376ee182af062762e15a101844881c89b566eea0856c481ffcb2090 - languageName: node - linkType: hard - -"@eslint/config-helpers@npm:^0.2.1": - version: 0.2.1 - resolution: "@eslint/config-helpers@npm:0.2.1" - checksum: 10/7627d01a654c61a71387edd235e663fea50a23f0f521a174b77d94e3d1f6834a5da9205a101ffbe4ee5cf6fab1f384693c7b47080f059debdf338dd9b590aadf - languageName: node - linkType: hard - -"@eslint/core@npm:^0.13.0": - version: 0.13.0 - resolution: "@eslint/core@npm:0.13.0" - dependencies: - "@types/json-schema": "npm:^7.0.15" - checksum: 10/737fd1c237405b62592e8daa4b7e25b45ab22108bfec65258cabd091d5717b7c9573acea1f27c4ee7198cefc5a0874f5caefe3d9636851227b1f12d28ef52cf2 - languageName: node - linkType: hard - -"@eslint/eslintrc@npm:^3.3.1": - version: 3.3.1 - resolution: "@eslint/eslintrc@npm:3.3.1" - dependencies: - ajv: "npm:^6.12.4" - debug: "npm:^4.3.2" - espree: "npm:^10.0.1" - globals: "npm:^14.0.0" - ignore: "npm:^5.2.0" - import-fresh: "npm:^3.2.1" - js-yaml: "npm:^4.1.0" - minimatch: "npm:^3.1.2" - strip-json-comments: "npm:^3.1.1" - checksum: 10/cc240addbab3c5fceaa65b2c8d5d4fd77ddbbf472c2f74f0270b9d33263dc9116840b6099c46b64c9680301146250439b044ed79278a1bcc557da412a4e3c1bb - languageName: node - linkType: hard - -"@eslint/js@npm:9.25.1": - version: 9.25.1 - resolution: "@eslint/js@npm:9.25.1" - checksum: 10/ad5812889598de32d674ef60c0e61468ac5c7f3b6ecf98b0e29d1e88d7af8ba3aab255b8c0a46bbaf654047bbd2ee5aa033db9b53e330f97615093fcccde4cbb - languageName: node - linkType: hard - -"@eslint/object-schema@npm:^2.1.6": - version: 2.1.6 - resolution: "@eslint/object-schema@npm:2.1.6" - checksum: 10/266085c8d3fa6cd99457fb6350dffb8ee39db9c6baf28dc2b86576657373c92a568aec4bae7d142978e798b74c271696672e103202d47a0c148da39154351ed6 - languageName: node - linkType: hard - -"@eslint/plugin-kit@npm:^0.2.8": - version: 0.2.8 - resolution: "@eslint/plugin-kit@npm:0.2.8" - dependencies: - "@eslint/core": "npm:^0.13.0" - levn: "npm:^0.4.1" - checksum: 10/2e7fe7a88ebdbbf805e9e7265347b7dcfb6bf50beec314def997572b2e8ae4a7b9504fb67b1698a70c348a0dd87251d1e9028292a96fd49b58cb5277d88bdea7 - languageName: node - linkType: hard - -"@humanfs/core@npm:^0.19.1": - version: 0.19.1 - resolution: "@humanfs/core@npm:0.19.1" - checksum: 10/270d936be483ab5921702623bc74ce394bf12abbf57d9145a69e8a0d1c87eb1c768bd2d93af16c5705041e257e6d9cc7529311f63a1349f3678abc776fc28523 - languageName: node - linkType: hard - -"@humanfs/node@npm:^0.16.6": - version: 0.16.6 - resolution: "@humanfs/node@npm:0.16.6" - dependencies: - "@humanfs/core": "npm:^0.19.1" - "@humanwhocodes/retry": "npm:^0.3.0" - checksum: 10/6d43c6727463772d05610aa05c83dab2bfbe78291022ee7a92cb50999910b8c720c76cc312822e2dea2b497aa1b3fef5fe9f68803fc45c9d4ed105874a65e339 - languageName: node - linkType: hard - -"@humanwhocodes/module-importer@npm:^1.0.1": - version: 1.0.1 - resolution: "@humanwhocodes/module-importer@npm:1.0.1" - checksum: 10/e993950e346331e5a32eefb27948ecdee2a2c4ab3f072b8f566cd213ef485dd50a3ca497050608db91006f5479e43f91a439aef68d2a313bd3ded06909c7c5b3 - languageName: node - linkType: hard - -"@humanwhocodes/retry@npm:^0.3.0": - version: 0.3.1 - resolution: "@humanwhocodes/retry@npm:0.3.1" - checksum: 10/eb457f699529de7f07649679ec9e0353055eebe443c2efe71c6dd950258892475a038e13c6a8c5e13ed1fb538cdd0a8794faa96b24b6ffc4c87fb1fc9f70ad7f - languageName: node - linkType: hard - -"@humanwhocodes/retry@npm:^0.4.2": - version: 0.4.2 - resolution: "@humanwhocodes/retry@npm:0.4.2" - checksum: 10/8910c4cdf8d46ce406e6f0cb4407ff6cfef70b15039bd5713cc059f32e02fe5119d833cfe2ebc5f522eae42fdd453b6d88f3fa7a1d8c4275aaad6eb3d3e9b117 - languageName: node - linkType: hard - -"@isaacs/cliui@npm:^8.0.2": - version: 8.0.2 - resolution: "@isaacs/cliui@npm:8.0.2" - dependencies: - string-width: "npm:^5.1.2" - string-width-cjs: "npm:string-width@^4.2.0" - strip-ansi: "npm:^7.0.1" - strip-ansi-cjs: "npm:strip-ansi@^6.0.1" - wrap-ansi: "npm:^8.1.0" - wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" - checksum: 10/e9ed5fd27c3aec1095e3a16e0c0cf148d1fee55a38665c35f7b3f86a9b5d00d042ddaabc98e8a1cb7463b9378c15f22a94eb35e99469c201453eb8375191f243 - languageName: node - linkType: hard - -"@joshwooding/vite-plugin-react-docgen-typescript@npm:0.5.0": - version: 0.5.0 - resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.5.0" - dependencies: - glob: "npm:^10.0.0" - magic-string: "npm:^0.27.0" - react-docgen-typescript: "npm:^2.2.2" - peerDependencies: - typescript: ">= 4.3.x" - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10/1dcb03f2df1723799a7a9c75ac8360990f75c44fd2425d2d52a9e21882fc3054d372892ab1cad0927864d8a934ad5b347f4ae00b01785649e2a8f1c4b861aa67 - languageName: node - linkType: hard - -"@jridgewell/gen-mapping@npm:^0.3.5": - version: 0.3.5 - resolution: "@jridgewell/gen-mapping@npm:0.3.5" - dependencies: - "@jridgewell/set-array": "npm:^1.2.1" - "@jridgewell/sourcemap-codec": "npm:^1.4.10" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10/81587b3c4dd8e6c60252122937cea0c637486311f4ed208b52b62aae2e7a87598f63ec330e6cd0984af494bfb16d3f0d60d3b21d7e5b4aedd2602ff3fe9d32e2 - languageName: node - linkType: hard - -"@jridgewell/resolve-uri@npm:^3.1.0": - version: 3.1.2 - resolution: "@jridgewell/resolve-uri@npm:3.1.2" - checksum: 10/97106439d750a409c22c8bff822d648f6a71f3aa9bc8e5129efdc36343cd3096ddc4eeb1c62d2fe48e9bdd4db37b05d4646a17114ecebd3bbcacfa2de51c3c1d - languageName: node - linkType: hard - -"@jridgewell/set-array@npm:^1.2.1": - version: 1.2.1 - resolution: "@jridgewell/set-array@npm:1.2.1" - checksum: 10/832e513a85a588f8ed4f27d1279420d8547743cc37fcad5a5a76fc74bb895b013dfe614d0eed9cb860048e6546b798f8f2652020b4b2ba0561b05caa8c654b10 - languageName: node - linkType: hard - -"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.13, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": - version: 1.5.0 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" - checksum: 10/4ed6123217569a1484419ac53f6ea0d9f3b57e5b57ab30d7c267bdb27792a27eb0e4b08e84a2680aa55cc2f2b411ffd6ec3db01c44fdc6dc43aca4b55f8374fd - languageName: node - linkType: hard - -"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": - version: 0.3.25 - resolution: "@jridgewell/trace-mapping@npm:0.3.25" - dependencies: - "@jridgewell/resolve-uri": "npm:^3.1.0" - "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10/dced32160a44b49d531b80a4a2159dceab6b3ddf0c8e95a0deae4b0e894b172defa63d5ac52a19c2068e1fe7d31ea4ba931fbeec103233ecb4208953967120fc - languageName: node - linkType: hard - -"@mdx-js/react@npm:^3.0.0": - version: 3.1.0 - resolution: "@mdx-js/react@npm:3.1.0" - dependencies: - "@types/mdx": "npm:^2.0.0" - peerDependencies: - "@types/react": ">=16" - react: ">=16" - checksum: 10/cf89d6392c76091622fb647f205e1ab5cbdf5edd4401dde7092138cefc9fbb6d61428aa63557de0bccca3695d5a8854dd4a93b34a27cb8e27369da7eaeaa3e73 - languageName: node - linkType: hard - -"@nodelib/fs.scandir@npm:2.1.5": - version: 2.1.5 - resolution: "@nodelib/fs.scandir@npm:2.1.5" - dependencies: - "@nodelib/fs.stat": "npm:2.0.5" - run-parallel: "npm:^1.1.9" - checksum: 10/6ab2a9b8a1d67b067922c36f259e3b3dfd6b97b219c540877a4944549a4d49ea5ceba5663905ab5289682f1f3c15ff441d02f0447f620a42e1cb5e1937174d4b - languageName: node - linkType: hard - -"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": - version: 2.0.5 - resolution: "@nodelib/fs.stat@npm:2.0.5" - checksum: 10/012480b5ca9d97bff9261571dbbec7bbc6033f69cc92908bc1ecfad0792361a5a1994bc48674b9ef76419d056a03efadfce5a6cf6dbc0a36559571a7a483f6f0 - languageName: node - linkType: hard - -"@nodelib/fs.walk@npm:^1.2.3": - version: 1.2.8 - resolution: "@nodelib/fs.walk@npm:1.2.8" - dependencies: - "@nodelib/fs.scandir": "npm:2.1.5" - fastq: "npm:^1.6.0" - checksum: 10/40033e33e96e97d77fba5a238e4bba4487b8284678906a9f616b5579ddaf868a18874c0054a75402c9fbaaa033a25ceae093af58c9c30278e35c23c9479e79b0 - languageName: node - linkType: hard - -"@npmcli/agent@npm:^2.0.0": - version: 2.2.2 - resolution: "@npmcli/agent@npm:2.2.2" - dependencies: - agent-base: "npm:^7.1.0" - http-proxy-agent: "npm:^7.0.0" - https-proxy-agent: "npm:^7.0.1" - lru-cache: "npm:^10.0.1" - socks-proxy-agent: "npm:^8.0.3" - checksum: 10/96fc0036b101bae5032dc2a4cd832efb815ce9b33f9ee2f29909ee49d96a0026b3565f73c507a69eb8603f5cb32e0ae45a70cab1e2655990a4e06ae99f7f572a - languageName: node - linkType: hard - -"@npmcli/fs@npm:^3.1.0": - version: 3.1.1 - resolution: "@npmcli/fs@npm:3.1.1" - dependencies: - semver: "npm:^7.3.5" - checksum: 10/1e0e04087049b24b38bc0b30d87a9388ee3ca1d3fdfc347c2f77d84fcfe6a51f250bc57ba2c1f614d7e4285c6c62bf8c769bc19aa0949ea39e5b043ee023b0bd - languageName: node - linkType: hard - -"@pkgjs/parseargs@npm:^0.11.0": - version: 0.11.0 - resolution: "@pkgjs/parseargs@npm:0.11.0" - checksum: 10/115e8ceeec6bc69dff2048b35c0ab4f8bbee12d8bb6c1f4af758604586d802b6e669dcb02dda61d078de42c2b4ddce41b3d9e726d7daa6b4b850f4adbf7333ff - languageName: node - linkType: hard - -"@remix-run/router@npm:1.21.1": - version: 1.21.1 - resolution: "@remix-run/router@npm:1.21.1" - checksum: 10/22a3dde5dd4ee131bddb5b589f974d6cb9e8378bda219bcdf592f399b9f10b8431d5f230bea507fc3b7f295998f8379942f5f07f920c087e2ecae65c76494e51 - languageName: node - linkType: hard - -"@rollup/pluginutils@npm:^5.0.2": - version: 5.1.2 - resolution: "@rollup/pluginutils@npm:5.1.2" - dependencies: - "@types/estree": "npm:^1.0.0" - estree-walker: "npm:^2.0.2" - picomatch: "npm:^2.3.1" - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - checksum: 10/cc1fe3285ab48915a6535ab2f0c90dc511bd3e63143f8e9994bb036c6c5071fd14d641cff6c89a7fde6a4faa85227d4e2cf46ee36b7d962099e0b9e4c9b8a4b0 - languageName: node - linkType: hard - -"@rollup/rollup-android-arm-eabi@npm:4.24.0": - version: 4.24.0 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.24.0" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@rollup/rollup-android-arm64@npm:4.24.0": - version: 4.24.0 - resolution: "@rollup/rollup-android-arm64@npm:4.24.0" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-darwin-arm64@npm:4.24.0": - version: 4.24.0 - resolution: "@rollup/rollup-darwin-arm64@npm:4.24.0" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-darwin-x64@npm:4.24.0": - version: 4.24.0 - resolution: "@rollup/rollup-darwin-x64@npm:4.24.0" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm-gnueabihf@npm:4.24.0": - version: 4.24.0 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.24.0" - conditions: os=linux & cpu=arm & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm-musleabihf@npm:4.24.0": - version: 4.24.0 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.24.0" - conditions: os=linux & cpu=arm & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm64-gnu@npm:4.24.0": - version: 4.24.0 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.24.0" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm64-musl@npm:4.24.0": - version: 4.24.0 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.24.0" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-powerpc64le-gnu@npm:4.24.0": - version: 4.24.0 - resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.24.0" - conditions: os=linux & cpu=ppc64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-riscv64-gnu@npm:4.24.0": - version: 4.24.0 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.24.0" - conditions: os=linux & cpu=riscv64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-s390x-gnu@npm:4.24.0": - version: 4.24.0 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.24.0" - conditions: os=linux & cpu=s390x & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-x64-gnu@npm:4.24.0": - version: 4.24.0 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.24.0" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-x64-musl@npm:4.24.0": - version: 4.24.0 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.24.0" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-win32-arm64-msvc@npm:4.24.0": - version: 4.24.0 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.24.0" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-win32-ia32-msvc@npm:4.24.0": - version: 4.24.0 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.24.0" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@rollup/rollup-win32-x64-msvc@npm:4.24.0": - version: 4.24.0 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.24.0" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@storybook/addon-actions@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-actions@npm:8.6.12" - dependencies: - "@storybook/global": "npm:^5.0.0" - "@types/uuid": "npm:^9.0.1" - dequal: "npm:^2.0.2" - polished: "npm:^4.2.2" - uuid: "npm:^9.0.0" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/c5eaaf5274bfe382877720ba9c379c0d7f6ec6173addb8bb94e80ba7318363359c677b57f25ca7582dd0007b4564683d627c51f69466e7a54faffdccae19f31f - languageName: node - linkType: hard - -"@storybook/addon-backgrounds@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-backgrounds@npm:8.6.12" - dependencies: - "@storybook/global": "npm:^5.0.0" - memoizerific: "npm:^1.11.3" - ts-dedent: "npm:^2.0.0" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/cb4793843140f6b454cb11bf9ef65f9d68ac001544538744b2a0564c30d6b82a144f788ede3d8e86ab23951e4b1fa157e4b78b74f8ab1cc461c8e696532eb8a6 - languageName: node - linkType: hard - -"@storybook/addon-controls@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-controls@npm:8.6.12" - dependencies: - "@storybook/global": "npm:^5.0.0" - dequal: "npm:^2.0.2" - ts-dedent: "npm:^2.0.0" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/2de79406c572f8706a7a31871f7c7bc6498cde48bcbcbc00984277e6861defcad9ba895d90a66ed0ffc526bcf3b950561ff6d51837192625fb7cb5a9bb763ce4 - languageName: node - linkType: hard - -"@storybook/addon-docs@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-docs@npm:8.6.12" - dependencies: - "@mdx-js/react": "npm:^3.0.0" - "@storybook/blocks": "npm:8.6.12" - "@storybook/csf-plugin": "npm:8.6.12" - "@storybook/react-dom-shim": "npm:8.6.12" - react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - ts-dedent: "npm:^2.0.0" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/df52e5c77a3e2b380864fdc5a6fcd0726e309f8c0556b9981cfe0899ea778531f286663be4f7988cd41ae0a813cb2de57f9fd09281ee0fadd8977354125b7f17 - languageName: node - linkType: hard - -"@storybook/addon-essentials@npm:^8.3.5": - version: 8.6.12 - resolution: "@storybook/addon-essentials@npm:8.6.12" - dependencies: - "@storybook/addon-actions": "npm:8.6.12" - "@storybook/addon-backgrounds": "npm:8.6.12" - "@storybook/addon-controls": "npm:8.6.12" - "@storybook/addon-docs": "npm:8.6.12" - "@storybook/addon-highlight": "npm:8.6.12" - "@storybook/addon-measure": "npm:8.6.12" - "@storybook/addon-outline": "npm:8.6.12" - "@storybook/addon-toolbars": "npm:8.6.12" - "@storybook/addon-viewport": "npm:8.6.12" - ts-dedent: "npm:^2.0.0" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/88cc2f1687186a5b4b0d509f65610d0ae89318740d4186020bb8879d5837ecdb5be75fed6cecf161614180eb91c75e4411f9c902f25c5452230121ecce00eae1 - languageName: node - linkType: hard - -"@storybook/addon-highlight@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-highlight@npm:8.6.12" - dependencies: - "@storybook/global": "npm:^5.0.0" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/04bdb057ed40e36af2b4a73a6220aa8c95f5322bb00c62f293118b174885476c8fb23019cfba53b9ffcfc28ff77ecbf38164c3a0fc6a717507f91d80e9b83655 - languageName: node - linkType: hard - -"@storybook/addon-interactions@npm:^8.3.5": - version: 8.6.12 - resolution: "@storybook/addon-interactions@npm:8.6.12" - dependencies: - "@storybook/global": "npm:^5.0.0" - "@storybook/instrumenter": "npm:8.6.12" - "@storybook/test": "npm:8.6.12" - polished: "npm:^4.2.2" - ts-dedent: "npm:^2.2.0" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/eb467f2976d670cf3e35d30d665366e1860e750c6d57c9418781418a1138c8ed3124f534f8fab0b358998957daca27d0c924e9dc61500646b2ff0ed04c7855b4 - languageName: node - linkType: hard - -"@storybook/addon-links@npm:^8.3.5": - version: 8.6.12 - resolution: "@storybook/addon-links@npm:8.6.12" - dependencies: - "@storybook/global": "npm:^5.0.0" - ts-dedent: "npm:^2.0.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.12 - peerDependenciesMeta: - react: - optional: true - checksum: 10/85ce71b11ba105516f229259b1ba58985f4f59c38f992d7d9060b1c0fd67953073d4075c5e2fd3a3b8ad6890168ca240b5bedfffc2d48e3ccdd58dad75c8e32f - languageName: node - linkType: hard - -"@storybook/addon-measure@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-measure@npm:8.6.12" - dependencies: - "@storybook/global": "npm:^5.0.0" - tiny-invariant: "npm:^1.3.1" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/ea4eced8d28d4cf9a7a5d3184d78030efcc31dd2ecab9096e2b17e5e690a711067fc3488677e2dde33405406c7d659aef542cb9a6dd6310971481c9119b24b1a - languageName: node - linkType: hard - -"@storybook/addon-outline@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-outline@npm:8.6.12" - dependencies: - "@storybook/global": "npm:^5.0.0" - ts-dedent: "npm:^2.0.0" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/57fcde63feae3c0b755afa8036ab832d8f414acaffa7c6e7ee0039caf8b5074e53182e8c61802071f5d858e9c64d4e02ed5680a5a28c34f824f118f65a0c7607 - languageName: node - linkType: hard - -"@storybook/addon-storysource@npm:^8.3.6": - version: 8.6.12 - resolution: "@storybook/addon-storysource@npm:8.6.12" - dependencies: - "@storybook/source-loader": "npm:8.6.12" - estraverse: "npm:^5.2.0" - tiny-invariant: "npm:^1.3.1" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/25d920416e31b49b598237707cdd12634001eb02c3ca8eb2cf8b9dc61b6deff89b8fed7ed4b05a836c5246af32bb8dec12a1da85d368b1c4aba28d5d94e9d7d2 - languageName: node - linkType: hard - -"@storybook/addon-themes@npm:^8.3.6": - version: 8.6.12 - resolution: "@storybook/addon-themes@npm:8.6.12" - dependencies: - ts-dedent: "npm:^2.0.0" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/2df945accd7c0546785f71e2adc4ed68d71a057bc54aaef53f16ce8fcbc5a494380d60f9dd58520fe7d93b021a1b09e3d73553766d4bf2a1d07a3d731132b939 - languageName: node - linkType: hard - -"@storybook/addon-toolbars@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-toolbars@npm:8.6.12" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/f94c3bcc8886ead315eb919cee18fb2143134ca9e59058e1d7a35c0e348e0bdcf51971b2a51ebd7a7250ec0e5873519dea72e699f83d771288b6bde873720e1f - languageName: node - linkType: hard - -"@storybook/addon-viewport@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-viewport@npm:8.6.12" - dependencies: - memoizerific: "npm:^1.11.3" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/b7b9fe1bc9d51b33b67ba35f5a219910db88c99323baad6e7e5ee9ec6ddd5b5f2f4124ff99729571c4306f35fcb36ee388bece437c648e9f2ce94cbfb44c4800 - languageName: node - linkType: hard - -"@storybook/blocks@npm:8.6.12, @storybook/blocks@npm:^8.3.5": - version: 8.6.12 - resolution: "@storybook/blocks@npm:8.6.12" - dependencies: - "@storybook/icons": "npm:^1.2.12" - ts-dedent: "npm:^2.0.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^8.6.12 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - checksum: 10/636172d0512f85913f1bed23eafcb3d827e4c924f65377c0c5d60ccce0241056caf9ab1fa3eb718cae9c02fb063f446294e32d1d5163377774d9225d97f210a0 - languageName: node - linkType: hard - -"@storybook/builder-vite@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/builder-vite@npm:8.6.12" - dependencies: - "@storybook/csf-plugin": "npm:8.6.12" - browser-assert: "npm:^1.2.1" - ts-dedent: "npm:^2.0.0" - peerDependencies: - storybook: ^8.6.12 - vite: ^4.0.0 || ^5.0.0 || ^6.0.0 - checksum: 10/f4119dbc08a399abbf660dd80ab6d4f80091c4f0588971e66da7a4537da805dfaf3df009d9a294889b9c2a9a250088997cecb77c970a00966818b0fa8c50c9f6 - languageName: node - linkType: hard - -"@storybook/components@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/components@npm:8.6.12" - peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/ccc6af275bdfbc66de8afb272f59b2b4b6b76bb2961903335cb62e7defae4a2368bd8f5a2008f8598dd8417e5368d824bce836ca96c3c6ff659dbf2f081ec0dd - languageName: node - linkType: hard - -"@storybook/core@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/core@npm:8.6.12" - dependencies: - "@storybook/theming": "npm:8.6.12" - better-opn: "npm:^3.0.2" - browser-assert: "npm:^1.2.1" - esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0" - esbuild-register: "npm:^3.5.0" - jsdoc-type-pratt-parser: "npm:^4.0.0" - process: "npm:^0.11.10" - recast: "npm:^0.23.5" - semver: "npm:^7.6.2" - util: "npm:^0.12.5" - ws: "npm:^8.2.3" - peerDependencies: - prettier: ^2 || ^3 - peerDependenciesMeta: - prettier: - optional: true - checksum: 10/78776f51b9eae00f9387421b33b646b1dc67ef833fd6272de03399daa7f0ffa248c65b5f24d5d2a9af923a029d06d84d5425e3455302ece542bf47c7a9ec0df6 - languageName: node - linkType: hard - -"@storybook/csf-plugin@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/csf-plugin@npm:8.6.12" - dependencies: - unplugin: "npm:^1.3.1" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/05dc3d5eb567c396f4773faed8283255526e60d7ed05452acd399edfb0d23beba886d9042cb705f76f8055108821eeae8dd2124635b5b47412f279b515affcc3 - languageName: node - linkType: hard - -"@storybook/csf@npm:^0.1.11": - version: 0.1.11 - resolution: "@storybook/csf@npm:0.1.11" - dependencies: - type-fest: "npm:^2.19.0" - checksum: 10/f6eeefe3b92ab206676587da9e22a775da026c055999681580d2ca23c98185736f965adc79039a0ae97ea625f0fbc7915cd4559e5db24229a4805784d0b78584 - languageName: node - linkType: hard - -"@storybook/global@npm:^5.0.0": - version: 5.0.0 - resolution: "@storybook/global@npm:5.0.0" - checksum: 10/0e7b495f4fe7f36447e793926f1c0460ec07fd66f0da68e3150da5878f6043c9eeb9b41614a45c5ec0d48d5d383c59ca8f88b6dc7882a2a784ac9b20375d8edb - languageName: node - linkType: hard - -"@storybook/icons@npm:^1.2.12": - version: 1.2.12 - resolution: "@storybook/icons@npm:1.2.12" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10/5df56f0856764ed7e4bb24ef7a08a8a9c93f8eedcb16dac062f1dfd3bd1fe6cb4a0aa5a0794083d95e31c04960d126a4d2028cfb4c53681bf05513bb38eae9d2 - languageName: node - linkType: hard - -"@storybook/instrumenter@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/instrumenter@npm:8.6.12" - dependencies: - "@storybook/global": "npm:^5.0.0" - "@vitest/utils": "npm:^2.1.1" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/11f608406a2d83a500a9270fda57bbec4aa1f97a3d95a4b52f44dce4efbfd6d198b33a0077cee2b48251af191722d2fdbfd1fe89255ded27656372214e37cb7c - languageName: node - linkType: hard - -"@storybook/manager-api@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/manager-api@npm:8.6.12" - peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/d1c44c6649a024c4007461c12a15337f5d13532dbaccc4c02f71bd99599fb973e2574eb8f1bc2d93e05da24e4ae43fa47ec637a7c4cccf5ffc67045cafbf087c - languageName: node - linkType: hard - -"@storybook/preview-api@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/preview-api@npm:8.6.12" - peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/d24f11e4e54e9e51297b0f87e1d462b3f14a974b4681f31d93b62b0706ce5b5ed4ffaaac521ec049dcb0e08e7aa7590f2e039aee4bbe9f85033d69474d982f23 - languageName: node - linkType: hard - -"@storybook/react-dom-shim@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/react-dom-shim@npm:8.6.12" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.12 - checksum: 10/7677b4fae978209af239471f9eb609db4318815bd4249a0f48b9875482d9ae910b93fbe4db5d7f794ecc2a1249edf40da26af9de673c941c48fccc4007819c96 - languageName: node - linkType: hard - -"@storybook/react-vite@npm:^8.3.5": - version: 8.6.12 - resolution: "@storybook/react-vite@npm:8.6.12" - dependencies: - "@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.5.0" - "@rollup/pluginutils": "npm:^5.0.2" - "@storybook/builder-vite": "npm:8.6.12" - "@storybook/react": "npm:8.6.12" - find-up: "npm:^5.0.0" - magic-string: "npm:^0.30.0" - react-docgen: "npm:^7.0.0" - resolve: "npm:^1.22.8" - tsconfig-paths: "npm:^4.2.0" - peerDependencies: - "@storybook/test": 8.6.12 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.12 - vite: ^4.0.0 || ^5.0.0 || ^6.0.0 - peerDependenciesMeta: - "@storybook/test": - optional: true - checksum: 10/79bce9d4c040405f532b6c86c25e52ab165872404448ed42ef46ad58e72b54ad71c771ec2448dc2984b5ee1b35d561110ded5e1d0d36df4c0616e71b0dad276f - languageName: node - linkType: hard - -"@storybook/react@npm:8.6.12, @storybook/react@npm:^8.3.5": - version: 8.6.12 - resolution: "@storybook/react@npm:8.6.12" - dependencies: - "@storybook/components": "npm:8.6.12" - "@storybook/global": "npm:^5.0.0" - "@storybook/manager-api": "npm:8.6.12" - "@storybook/preview-api": "npm:8.6.12" - "@storybook/react-dom-shim": "npm:8.6.12" - "@storybook/theming": "npm:8.6.12" - peerDependencies: - "@storybook/test": 8.6.12 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.12 - typescript: ">= 4.2.x" - peerDependenciesMeta: - "@storybook/test": - optional: true - typescript: - optional: true - checksum: 10/d8258c82743906f48a872a781f3e5a63e9ce3fda2ba3b911e959cf62ebda43989b23746257d97905addb7142ac7e53d0089dc178bcbeea48ed4d37d025dd047b - languageName: node - linkType: hard - -"@storybook/source-loader@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/source-loader@npm:8.6.12" - dependencies: - es-toolkit: "npm:^1.22.0" - estraverse: "npm:^5.2.0" - prettier: "npm:^3.1.1" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/021dfbbca2059097bb434dfd1a99caa38829a30ed6121e62fa9ab9932ec7626d1f8bdf615be24947103820ed8486346ea5f4f1b60291607eddcd3401c5437ffa - languageName: node - linkType: hard - -"@storybook/test@npm:8.6.12, @storybook/test@npm:^8.3.5": - version: 8.6.12 - resolution: "@storybook/test@npm:8.6.12" - dependencies: - "@storybook/global": "npm:^5.0.0" - "@storybook/instrumenter": "npm:8.6.12" - "@testing-library/dom": "npm:10.4.0" - "@testing-library/jest-dom": "npm:6.5.0" - "@testing-library/user-event": "npm:14.5.2" - "@vitest/expect": "npm:2.0.5" - "@vitest/spy": "npm:2.0.5" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/495409d95a6c649c54afd7304d429f1d7ef29ef9ac40415550ce60115d3f4210a228d7ab927dcd3229f63954e5a282f407cb8bc5816c6cfc9a45fcbc8e30bae8 - languageName: node - linkType: hard - -"@storybook/theming@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/theming@npm:8.6.12" - peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/c811d9dbb9eaaa680b922111fca126a2985f2238dfb01c1cd23184323eea12899dc9f079063ac42c5e63b0c83de326bd9cc17241e4060ff04e860c57a55fb8b9 - languageName: node - linkType: hard - -"@storybook/types@npm:^8.3.5": - version: 8.6.12 - resolution: "@storybook/types@npm:8.6.12" - peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/5d99fc62de9716b339bf84c6939ac806ebb45a42cb0fb4cb81ee875f4c4d1dbf3bc09115971c52759e7e3f88147cf9172295349a8c4bae7708d0de0250c7621c - languageName: node - linkType: hard - -"@testing-library/dom@npm:10.4.0": - version: 10.4.0 - resolution: "@testing-library/dom@npm:10.4.0" - dependencies: - "@babel/code-frame": "npm:^7.10.4" - "@babel/runtime": "npm:^7.12.5" - "@types/aria-query": "npm:^5.0.1" - aria-query: "npm:5.3.0" - chalk: "npm:^4.1.0" - dom-accessibility-api: "npm:^0.5.9" - lz-string: "npm:^1.5.0" - pretty-format: "npm:^27.0.2" - checksum: 10/05825ee9a15b88cbdae12c137db7111c34069ed3c7a1bd03b6696cb1b37b29f6f2d2de581ebf03033e7df1ab7ebf08399310293f440a4845d95c02c0a9ecc899 - languageName: node - linkType: hard - -"@testing-library/jest-dom@npm:6.5.0": - version: 6.5.0 - resolution: "@testing-library/jest-dom@npm:6.5.0" - dependencies: - "@adobe/css-tools": "npm:^4.4.0" - aria-query: "npm:^5.0.0" - chalk: "npm:^3.0.0" - css.escape: "npm:^1.5.1" - dom-accessibility-api: "npm:^0.6.3" - lodash: "npm:^4.17.21" - redent: "npm:^3.0.0" - checksum: 10/3d2080888af5fd7306f57448beb5a23f55d965e265b5e53394fffc112dfb0678d616a5274ff0200c46c7618f293520f86fc8562eecd8bdbc0dbb3294d63ec431 - languageName: node - linkType: hard - -"@testing-library/user-event@npm:14.5.2": - version: 14.5.2 - resolution: "@testing-library/user-event@npm:14.5.2" - peerDependencies: - "@testing-library/dom": ">=7.21.4" - checksum: 10/49821459d81c6bc435d97128d6386ca24f1e4b3ba8e46cb5a96fe3643efa6e002d88c1b02b7f2ec58da593e805c59b78d7fdf0db565c1f02ba782f63ee984040 - languageName: node - linkType: hard - -"@types/aria-query@npm:^5.0.1": - version: 5.0.4 - resolution: "@types/aria-query@npm:5.0.4" - checksum: 10/c0084c389dc030daeaf0115a92ce43a3f4d42fc8fef2d0e22112d87a42798d4a15aac413019d4a63f868327d52ad6740ab99609462b442fe6b9286b172d2e82e - languageName: node - linkType: hard - -"@types/babel__core@npm:^7.18.0": - version: 7.20.5 - resolution: "@types/babel__core@npm:7.20.5" - dependencies: - "@babel/parser": "npm:^7.20.7" - "@babel/types": "npm:^7.20.7" - "@types/babel__generator": "npm:*" - "@types/babel__template": "npm:*" - "@types/babel__traverse": "npm:*" - checksum: 10/c32838d280b5ab59d62557f9e331d3831f8e547ee10b4f85cb78753d97d521270cebfc73ce501e9fb27fe71884d1ba75e18658692c2f4117543f0fc4e3e118b3 - languageName: node - linkType: hard - -"@types/babel__generator@npm:*": - version: 7.6.8 - resolution: "@types/babel__generator@npm:7.6.8" - dependencies: - "@babel/types": "npm:^7.0.0" - checksum: 10/b53c215e9074c69d212402990b0ca8fa57595d09e10d94bda3130aa22b55d796e50449199867879e4ea0ee968f3a2099e009cfb21a726a53324483abbf25cd30 - languageName: node - linkType: hard - -"@types/babel__template@npm:*": - version: 7.4.4 - resolution: "@types/babel__template@npm:7.4.4" - dependencies: - "@babel/parser": "npm:^7.1.0" - "@babel/types": "npm:^7.0.0" - checksum: 10/d7a02d2a9b67e822694d8e6a7ddb8f2b71a1d6962dfd266554d2513eefbb205b33ca71a0d163b1caea3981ccf849211f9964d8bd0727124d18ace45aa6c9ae29 - languageName: node - linkType: hard - -"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.18.0": - version: 7.20.6 - resolution: "@types/babel__traverse@npm:7.20.6" - dependencies: - "@babel/types": "npm:^7.20.7" - checksum: 10/63d13a3789aa1e783b87a8b03d9fb2c2c90078de7782422feff1631b8c2a25db626e63a63ac5a1465d47359201c73069dacb4b52149d17c568187625da3064ae - languageName: node - linkType: hard - -"@types/doctrine@npm:^0.0.9": - version: 0.0.9 - resolution: "@types/doctrine@npm:0.0.9" - checksum: 10/64ef06e6eea2f4f9684d259fedbcb8bf21c954630b96ea2e04875ca42763552b0ba3b01b3dd27ec0f9ea6f8b3b0dba4965d31d5a925cd4c6225fd13a93ae9354 - languageName: node - linkType: hard - -"@types/estree@npm:1.0.6, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6": - version: 1.0.6 - resolution: "@types/estree@npm:1.0.6" - checksum: 10/9d35d475095199c23e05b431bcdd1f6fec7380612aed068b14b2a08aa70494de8a9026765a5a91b1073f636fb0368f6d8973f518a31391d519e20c59388ed88d - languageName: node - linkType: hard - -"@types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.9": - version: 7.0.15 - resolution: "@types/json-schema@npm:7.0.15" - checksum: 10/1a3c3e06236e4c4aab89499c428d585527ce50c24fe8259e8b3926d3df4cfbbbcf306cfc73ddfb66cbafc973116efd15967020b0f738f63e09e64c7d260519e7 - languageName: node - linkType: hard - -"@types/mdx@npm:^2.0.0": - version: 2.0.13 - resolution: "@types/mdx@npm:2.0.13" - checksum: 10/b73ed5f08114879b9590dc6a9ee8b648643c57c708583cd24b2bc3cc8961361fc63139ac7e9291e7b3b6e6b45707749d01d6f9727ddec5533df75dc3b90871a4 - languageName: node - linkType: hard - -"@types/prop-types@npm:*": - version: 15.7.13 - resolution: "@types/prop-types@npm:15.7.13" - checksum: 10/8935cad87c683c665d09a055919d617fe951cb3b2d5c00544e3a913f861a2bd8d2145b51c9aa6d2457d19f3107ab40784c40205e757232f6a80cc8b1c815513c - languageName: node - linkType: hard - -"@types/react-dom@npm:^18.0.0": - version: 18.3.6 - resolution: "@types/react-dom@npm:18.3.6" - peerDependencies: - "@types/react": ^18.0.0 - checksum: 10/ae179355401c64423d39946eda22c5f7f74c94ce61c21505024d4d82c33853ec40bc9b370f75e4a7750b0524aba4d95a43fcc328d8d14684dc2abb41ba529de8 - languageName: node - linkType: hard - -"@types/react@npm:^18.0.0": - version: 18.3.20 - resolution: "@types/react@npm:18.3.20" - dependencies: - "@types/prop-types": "npm:*" - csstype: "npm:^3.0.2" - checksum: 10/020c51e63b60862e6d772f0cdea0b9441182eedab6289dabd8add0708ded62003834c4e7c6f23a1ccd3ca9486b46296057c3f881c34261a0483765351f8d0bc3 - languageName: node - linkType: hard - -"@types/resolve@npm:^1.20.2": - version: 1.20.6 - resolution: "@types/resolve@npm:1.20.6" - checksum: 10/dc35f5517606b6687cd971c0281ac58bdee2c50c051b030f04647d3991688be2259c304ee97e5b5d4b9936072c36767eb5933b54611a407d6557972bb6fea4f6 - languageName: node - linkType: hard - -"@types/semver@npm:^7.3.12": - version: 7.5.8 - resolution: "@types/semver@npm:7.5.8" - checksum: 10/3496808818ddb36deabfe4974fd343a78101fa242c4690044ccdc3b95dcf8785b494f5d628f2f47f38a702f8db9c53c67f47d7818f2be1b79f2efb09692e1178 - languageName: node - linkType: hard - -"@types/uuid@npm:^9.0.1": - version: 9.0.8 - resolution: "@types/uuid@npm:9.0.8" - checksum: 10/b8c60b7ba8250356b5088302583d1704a4e1a13558d143c549c408bf8920535602ffc12394ede77f8a8083511b023704bc66d1345792714002bfa261b17c5275 - languageName: node - linkType: hard - -"@typescript-eslint/eslint-plugin@npm:8.26.0": - version: 8.26.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.26.0" - dependencies: - "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.26.0" - "@typescript-eslint/type-utils": "npm:8.26.0" - "@typescript-eslint/utils": "npm:8.26.0" - "@typescript-eslint/visitor-keys": "npm:8.26.0" - graphemer: "npm:^1.4.0" - ignore: "npm:^5.3.1" - natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^2.0.1" - peerDependencies: - "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10/5a3d2445178b815398aa9707e112492ce15c1709e7760fc2d68e64fce609901f4145de923007f50c3bbd6d11ef9f6c7843f1df40ab93c99f8a6610bcf34aa5c2 - languageName: node - linkType: hard - -"@typescript-eslint/eslint-plugin@npm:^5.0.0": - version: 5.62.0 - resolution: "@typescript-eslint/eslint-plugin@npm:5.62.0" - dependencies: - "@eslint-community/regexpp": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:5.62.0" - "@typescript-eslint/type-utils": "npm:5.62.0" - "@typescript-eslint/utils": "npm:5.62.0" - debug: "npm:^4.3.4" - graphemer: "npm:^1.4.0" - ignore: "npm:^5.2.0" - natural-compare-lite: "npm:^1.4.0" - semver: "npm:^7.3.7" - tsutils: "npm:^3.21.0" - peerDependencies: - "@typescript-eslint/parser": ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10/9cc8319c6fd8a21938f5b69476974a7e778c283a55ef9fad183c850995b9adcb0087d57cea7b2ac6b9449570eee983aad39491d14cdd2e52d6b4b0485e7b2482 - languageName: node - linkType: hard - -"@typescript-eslint/parser@npm:8.26.0": - version: 8.26.0 - resolution: "@typescript-eslint/parser@npm:8.26.0" - dependencies: - "@typescript-eslint/scope-manager": "npm:8.26.0" - "@typescript-eslint/types": "npm:8.26.0" - "@typescript-eslint/typescript-estree": "npm:8.26.0" - "@typescript-eslint/visitor-keys": "npm:8.26.0" - debug: "npm:^4.3.4" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10/86843d488b58d47d4bd45fed25a5afbb033bd844b4517f6401ae2f9af0fdeaedaf5c9dd30e74a7bf5b6029cff10fec0e33ca073b1ffe4795df7403b58aaac58c - languageName: node - linkType: hard - -"@typescript-eslint/parser@npm:^5.0.0": - version: 5.62.0 - resolution: "@typescript-eslint/parser@npm:5.62.0" - dependencies: - "@typescript-eslint/scope-manager": "npm:5.62.0" - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/typescript-estree": "npm:5.62.0" - debug: "npm:^4.3.4" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10/b6ca629d8f4e6283ff124501731cc886703eb4ce2c7d38b3e4110322ea21452b9d9392faf25be6bd72f54b89de7ffc72a40d9b159083ac54345a3d04b4fa5394 - languageName: node - linkType: hard - -"@typescript-eslint/scope-manager@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/scope-manager@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/visitor-keys": "npm:5.62.0" - checksum: 10/e827770baa202223bc0387e2fd24f630690809e460435b7dc9af336c77322290a770d62bd5284260fa881c86074d6a9fd6c97b07382520b115f6786b8ed499da - languageName: node - linkType: hard - -"@typescript-eslint/scope-manager@npm:8.26.0": - version: 8.26.0 - resolution: "@typescript-eslint/scope-manager@npm:8.26.0" - dependencies: - "@typescript-eslint/types": "npm:8.26.0" - "@typescript-eslint/visitor-keys": "npm:8.26.0" - checksum: 10/beccc5c0a815f20d8ccd5f8c4365175df39b62d0eeaf4893ef9b25e2fd96d26ac20e667b91d258584d33b970a471240b1b5bee73b14dac6630a63b5ce0b9ecd4 - languageName: node - linkType: hard - -"@typescript-eslint/type-utils@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/type-utils@npm:5.62.0" - dependencies: - "@typescript-eslint/typescript-estree": "npm:5.62.0" - "@typescript-eslint/utils": "npm:5.62.0" - debug: "npm:^4.3.4" - tsutils: "npm:^3.21.0" - peerDependencies: - eslint: "*" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10/f9a4398d6d2aae09e3e765eff04cf4ab364376a87868031ac5c6a64c9bbb555cb1a7f99b07b3d1017e7422725b5f0bbee537f13b82ab2d930f161c987b3dece0 - languageName: node - linkType: hard - -"@typescript-eslint/type-utils@npm:8.26.0": - version: 8.26.0 - resolution: "@typescript-eslint/type-utils@npm:8.26.0" - dependencies: - "@typescript-eslint/typescript-estree": "npm:8.26.0" - "@typescript-eslint/utils": "npm:8.26.0" - debug: "npm:^4.3.4" - ts-api-utils: "npm:^2.0.1" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10/cc383418bd208b5787ec93923a5ecb46f424b5f9a5aeb81f51382aa440671b6c85d1fe27527f2e0d711dfaff593d42ca6b57c10c839db800aa4d965d01ac8461 - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/types@npm:5.62.0" - checksum: 10/24e8443177be84823242d6729d56af2c4b47bfc664dd411a1d730506abf2150d6c31bdefbbc6d97c8f91043e3a50e0c698239dcb145b79bb6b0c34469aaf6c45 - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:8.26.0": - version: 8.26.0 - resolution: "@typescript-eslint/types@npm:8.26.0" - checksum: 10/2fcd2eed0550bc7f95ccf54cf44aae50a38b531deae92c6a616890fff7f335eb2c030553062518fa1bde9e29009b2c92ed59489c2ef9d4e35e9df55f95a6992b - languageName: node - linkType: hard - -"@typescript-eslint/typescript-estree@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/visitor-keys": "npm:5.62.0" - debug: "npm:^4.3.4" - globby: "npm:^11.1.0" - is-glob: "npm:^4.0.3" - semver: "npm:^7.3.7" - tsutils: "npm:^3.21.0" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10/06c975eb5f44b43bd19fadc2e1023c50cf87038fe4c0dd989d4331c67b3ff509b17fa60a3251896668ab4d7322bdc56162a9926971218d2e1a1874d2bef9a52e - languageName: node - linkType: hard - -"@typescript-eslint/typescript-estree@npm:8.26.0": - version: 8.26.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.26.0" - dependencies: - "@typescript-eslint/types": "npm:8.26.0" - "@typescript-eslint/visitor-keys": "npm:8.26.0" - debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - is-glob: "npm:^4.0.3" - minimatch: "npm:^9.0.4" - semver: "npm:^7.6.0" - ts-api-utils: "npm:^2.0.1" - peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10/f50101c138a545d0286b4c20be6e873c380cd2f3abac0bef7ce120e8c8297bad2e7ccb4ed4152ab455f2fb2761089d5d75e9d2ba277c3beef3019c99a9067c24 - languageName: node - linkType: hard - -"@typescript-eslint/utils@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/utils@npm:5.62.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" - "@types/json-schema": "npm:^7.0.9" - "@types/semver": "npm:^7.3.12" - "@typescript-eslint/scope-manager": "npm:5.62.0" - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/typescript-estree": "npm:5.62.0" - eslint-scope: "npm:^5.1.1" - semver: "npm:^7.3.7" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 10/15ef13e43998a082b15f85db979f8d3ceb1f9ce4467b8016c267b1738d5e7cdb12aa90faf4b4e6dd6486c236cf9d33c463200465cf25ff997dbc0f12358550a1 - languageName: node - linkType: hard - -"@typescript-eslint/utils@npm:8.26.0, @typescript-eslint/utils@npm:^8.8.1": - version: 8.26.0 - resolution: "@typescript-eslint/utils@npm:8.26.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:8.26.0" - "@typescript-eslint/types": "npm:8.26.0" - "@typescript-eslint/typescript-estree": "npm:8.26.0" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10/69b5ace76c27db4c6d9ce5e4d76aa17c712d90cbe61f3e8603c16a75d8ea38d27c54c4f70937bcd16f6352b26be79ee200f62af60d52b4fc6fe7e88fcaf93fe5 - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - eslint-visitor-keys: "npm:^3.3.0" - checksum: 10/dc613ab7569df9bbe0b2ca677635eb91839dfb2ca2c6fa47870a5da4f160db0b436f7ec0764362e756d4164e9445d49d5eb1ff0b87f4c058946ae9d8c92eb388 - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:8.26.0": - version: 8.26.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.26.0" - dependencies: - "@typescript-eslint/types": "npm:8.26.0" - eslint-visitor-keys: "npm:^4.2.0" - checksum: 10/8800c84d711682949e27d72e65b501dbdc5de0009b7d74e289f5f9125aa21107dc55c6ef3dc970431acebe92e19e907d1622de2d2092a79eb8d29ac96670ea75 - languageName: node - linkType: hard - -"@vitest/expect@npm:2.0.5": - version: 2.0.5 - resolution: "@vitest/expect@npm:2.0.5" - dependencies: - "@vitest/spy": "npm:2.0.5" - "@vitest/utils": "npm:2.0.5" - chai: "npm:^5.1.1" - tinyrainbow: "npm:^1.2.0" - checksum: 10/ca9a218f50254b2259fd16166b2d8c9ccc8ee2cc068905e6b3d6281da10967b1590cc7d34b5fa9d429297f97e740450233745583b4cc12272ff11705faf70a37 - languageName: node - linkType: hard - -"@vitest/pretty-format@npm:2.0.5": - version: 2.0.5 - resolution: "@vitest/pretty-format@npm:2.0.5" - dependencies: - tinyrainbow: "npm:^1.2.0" - checksum: 10/70bf452dd0b8525e658795125b3f11110bd6baadfaa38c5bb91ca763bded35ec6dc80e27964ad4e91b91be6544d35e18ea7748c1997693988f975a7283c3e9a0 - languageName: node - linkType: hard - -"@vitest/pretty-format@npm:2.1.4": - version: 2.1.4 - resolution: "@vitest/pretty-format@npm:2.1.4" - dependencies: - tinyrainbow: "npm:^1.2.0" - checksum: 10/434e6a7903f72a3796f26516ad728aca92724909e18fd3f2cd4b9b8b0ae2cc7b4cd86e92ab9f2ac7bc005c7a7ef0bcb9d768c0264b4b0625f1f0748cc615f1f6 - languageName: node - linkType: hard - -"@vitest/spy@npm:2.0.5": - version: 2.0.5 - resolution: "@vitest/spy@npm:2.0.5" - dependencies: - tinyspy: "npm:^3.0.0" - checksum: 10/ed19f4c3bb4d3853241e8070979615138e24403ce4c137fa48c903b3af2c8b3ada2cc26aca9c1aa323bb314a457a8130a29acbb18dafd4e42737deefb2abf1ca - languageName: node - linkType: hard - -"@vitest/utils@npm:2.0.5": - version: 2.0.5 - resolution: "@vitest/utils@npm:2.0.5" - dependencies: - "@vitest/pretty-format": "npm:2.0.5" - estree-walker: "npm:^3.0.3" - loupe: "npm:^3.1.1" - tinyrainbow: "npm:^1.2.0" - checksum: 10/d631d56d29c33bc8de631166b2b6691c470187a345469dfef7048befe6027e1c6ff9552f2ee11c8a247522c325c4a64bfcc73f8f0f0c525da39cb9f190f119f8 - languageName: node - linkType: hard - -"@vitest/utils@npm:^2.1.1": - version: 2.1.4 - resolution: "@vitest/utils@npm:2.1.4" - dependencies: - "@vitest/pretty-format": "npm:2.1.4" - loupe: "npm:^3.1.2" - tinyrainbow: "npm:^1.2.0" - checksum: 10/aaaf5310943abca0f0080d9638e67838f7e519d5670ec32e61184915efdfa5ec61d9b495cad6cb7dc492e8caeed14593e78dda77c8ea59c1671a231661f57142 - languageName: node - linkType: hard - -"abbrev@npm:^2.0.0": - version: 2.0.0 - resolution: "abbrev@npm:2.0.0" - checksum: 10/ca0a54e35bea4ece0ecb68a47b312e1a9a6f772408d5bcb9051230aaa94b0460671c5b5c9cb3240eb5b7bc94c52476550eb221f65a0bbd0145bdc9f3113a6707 - languageName: node - linkType: hard - -"acorn-jsx@npm:^5.3.2": - version: 5.3.2 - resolution: "acorn-jsx@npm:5.3.2" - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 10/d4371eaef7995530b5b5ca4183ff6f062ca17901a6d3f673c9ac011b01ede37e7a1f7f61f8f5cfe709e88054757bb8f3277dc4061087cdf4f2a1f90ccbcdb977 - languageName: node - linkType: hard - -"acorn@npm:^8.12.1, acorn@npm:^8.14.0": - version: 8.14.0 - resolution: "acorn@npm:8.14.0" - bin: - acorn: bin/acorn - checksum: 10/6df29c35556782ca9e632db461a7f97947772c6c1d5438a81f0c873a3da3a792487e83e404d1c6c25f70513e91aa18745f6eafb1fcc3a43ecd1920b21dd173d2 - languageName: node - linkType: hard - -"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": - version: 7.1.1 - resolution: "agent-base@npm:7.1.1" - dependencies: - debug: "npm:^4.3.4" - checksum: 10/c478fec8f79953f118704d007a38f2a185458853f5c45579b9669372bd0e12602e88dc2ad0233077831504f7cd6fcc8251c383375bba5eaaf563b102938bda26 - languageName: node - linkType: hard - -"aggregate-error@npm:^3.0.0": - version: 3.1.0 - resolution: "aggregate-error@npm:3.1.0" - dependencies: - clean-stack: "npm:^2.0.0" - indent-string: "npm:^4.0.0" - checksum: 10/1101a33f21baa27a2fa8e04b698271e64616b886795fd43c31068c07533c7b3facfcaf4e9e0cab3624bd88f729a592f1c901a1a229c9e490eafce411a8644b79 - languageName: node - linkType: hard - -"ajv@npm:^6.12.4": - version: 6.12.6 - resolution: "ajv@npm:6.12.6" - dependencies: - fast-deep-equal: "npm:^3.1.1" - fast-json-stable-stringify: "npm:^2.0.0" - json-schema-traverse: "npm:^0.4.1" - uri-js: "npm:^4.2.2" - checksum: 10/48d6ad21138d12eb4d16d878d630079a2bda25a04e745c07846a4ad768319533031e28872a9b3c5790fa1ec41aabdf2abed30a56e5a03ebc2cf92184b8ee306c - languageName: node - linkType: hard - -"ansi-regex@npm:^5.0.1": - version: 5.0.1 - resolution: "ansi-regex@npm:5.0.1" - checksum: 10/2aa4bb54caf2d622f1afdad09441695af2a83aa3fe8b8afa581d205e57ed4261c183c4d3877cee25794443fde5876417d859c108078ab788d6af7e4fe52eb66b - languageName: node - linkType: hard - -"ansi-regex@npm:^6.0.1": - version: 6.1.0 - resolution: "ansi-regex@npm:6.1.0" - checksum: 10/495834a53b0856c02acd40446f7130cb0f8284f4a39afdab20d5dc42b2e198b1196119fe887beed8f9055c4ff2055e3b2f6d4641d0be018cdfb64fedf6fc1aac - languageName: node - linkType: hard - -"ansi-styles@npm:^3.2.1": - version: 3.2.1 - resolution: "ansi-styles@npm:3.2.1" - dependencies: - color-convert: "npm:^1.9.0" - checksum: 10/d85ade01c10e5dd77b6c89f34ed7531da5830d2cb5882c645f330079975b716438cd7ebb81d0d6e6b4f9c577f19ae41ab55f07f19786b02f9dfd9e0377395665 - languageName: node - linkType: hard - -"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": - version: 4.3.0 - resolution: "ansi-styles@npm:4.3.0" - dependencies: - color-convert: "npm:^2.0.1" - checksum: 10/b4494dfbfc7e4591b4711a396bd27e540f8153914123dccb4cdbbcb514015ada63a3809f362b9d8d4f6b17a706f1d7bea3c6f974b15fa5ae76b5b502070889ff - languageName: node - linkType: hard - -"ansi-styles@npm:^5.0.0": - version: 5.2.0 - resolution: "ansi-styles@npm:5.2.0" - checksum: 10/d7f4e97ce0623aea6bc0d90dcd28881ee04cba06c570b97fd3391bd7a268eedfd9d5e2dd4fdcbdd82b8105df5faf6f24aaedc08eaf3da898e702db5948f63469 - languageName: node - linkType: hard - -"ansi-styles@npm:^6.1.0": - version: 6.2.1 - resolution: "ansi-styles@npm:6.2.1" - checksum: 10/70fdf883b704d17a5dfc9cde206e698c16bcd74e7f196ab821511651aee4f9f76c9514bdfa6ca3a27b5e49138b89cb222a28caf3afe4567570139577f991df32 - languageName: node - linkType: hard - -"argparse@npm:^2.0.1": - version: 2.0.1 - resolution: "argparse@npm:2.0.1" - checksum: 10/18640244e641a417ec75a9bd38b0b2b6b95af5199aa241b131d4b2fb206f334d7ecc600bd194861610a5579084978bfcbb02baa399dbe442d56d0ae5e60dbaef - languageName: node - linkType: hard - -"aria-query@npm:5.3.0": - version: 5.3.0 - resolution: "aria-query@npm:5.3.0" - dependencies: - dequal: "npm:^2.0.3" - checksum: 10/c3e1ed127cc6886fea4732e97dd6d3c3938e64180803acfb9df8955517c4943760746ffaf4020ce8f7ffaa7556a3b5f85c3769a1f5ca74a1288e02d042f9ae4e - languageName: node - linkType: hard - -"aria-query@npm:^5.0.0": - version: 5.3.2 - resolution: "aria-query@npm:5.3.2" - checksum: 10/b2fe9bc98bd401bc322ccb99717c1ae2aaf53ea0d468d6e7aebdc02fac736e4a99b46971ee05b783b08ade23c675b2d8b60e4a1222a95f6e27bc4d2a0bfdcc03 - languageName: node - linkType: hard - -"array-union@npm:^2.1.0": - version: 2.1.0 - resolution: "array-union@npm:2.1.0" - checksum: 10/5bee12395cba82da674931df6d0fea23c4aa4660cb3b338ced9f828782a65caa232573e6bf3968f23e0c5eb301764a382cef2f128b170a9dc59de0e36c39f98d - languageName: node - linkType: hard - -"assertion-error@npm:^2.0.1": - version: 2.0.1 - resolution: "assertion-error@npm:2.0.1" - checksum: 10/a0789dd882211b87116e81e2648ccb7f60340b34f19877dd020b39ebb4714e475eb943e14ba3e22201c221ef6645b7bfe10297e76b6ac95b48a9898c1211ce66 - languageName: node - linkType: hard - -"ast-types@npm:^0.16.1": - version: 0.16.1 - resolution: "ast-types@npm:0.16.1" - dependencies: - tslib: "npm:^2.0.1" - checksum: 10/f569b475eb1c8cb93888cb6e7b7e36dc43fa19a77e4eb132cbff6e3eb1598ca60f850db6e60b070e5a0ee8c1559fca921dac0916e576f2f104e198793b0bdd8d - languageName: node - linkType: hard - -"available-typed-arrays@npm:^1.0.7": - version: 1.0.7 - resolution: "available-typed-arrays@npm:1.0.7" - dependencies: - possible-typed-array-names: "npm:^1.0.0" - checksum: 10/6c9da3a66caddd83c875010a1ca8ef11eac02ba15fb592dc9418b2b5e7b77b645fa7729380a92d9835c2f05f2ca1b6251f39b993e0feb3f1517c74fa1af02cab - languageName: node - linkType: hard - -"backstage-storybook@workspace:.": - version: 0.0.0-use.local - resolution: "backstage-storybook@workspace:." - dependencies: - "@chromatic-com/storybook": "npm:^1.9.0" - "@storybook/addon-essentials": "npm:^8.3.5" - "@storybook/addon-interactions": "npm:^8.3.5" - "@storybook/addon-links": "npm:^8.3.5" - "@storybook/addon-storysource": "npm:^8.3.6" - "@storybook/addon-themes": "npm:^8.3.6" - "@storybook/blocks": "npm:^8.3.5" - "@storybook/react": "npm:^8.3.5" - "@storybook/react-vite": "npm:^8.3.5" - "@storybook/test": "npm:^8.3.5" - "@storybook/types": "npm:^8.3.5" - "@types/react": "npm:^18.0.0" - "@types/react-dom": "npm:^18.0.0" - "@typescript-eslint/eslint-plugin": "npm:^5.0.0" - "@typescript-eslint/parser": "npm:^5.0.0" - eslint: "npm:^9.11.1" - eslint-plugin-storybook: "npm:^0.11.0" - globals: "npm:^15.9.0" - react: "npm:^18.0.2" - react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" - storybook: "npm:^8.3.5" - typescript: "npm:^5.5.3" - typescript-eslint: "npm:^8.7.0" - vite: "npm:^5.4.8" - languageName: unknown - linkType: soft - -"balanced-match@npm:^1.0.0": - version: 1.0.2 - resolution: "balanced-match@npm:1.0.2" - checksum: 10/9706c088a283058a8a99e0bf91b0a2f75497f185980d9ffa8b304de1d9e58ebda7c72c07ebf01dadedaac5b2907b2c6f566f660d62bd336c3468e960403b9d65 - languageName: node - linkType: hard - -"better-opn@npm:^3.0.2": - version: 3.0.2 - resolution: "better-opn@npm:3.0.2" - dependencies: - open: "npm:^8.0.4" - checksum: 10/24668e5a837d0d2c0edf17ad5ebcfeb00a8a5578a5eb09f7a409e1a60617cdfea40b8ebfc95e5f12d9568157930d033e6805788fcf0780413ac982c95d3745d1 - languageName: node - linkType: hard - -"brace-expansion@npm:^1.1.7": - version: 1.1.11 - resolution: "brace-expansion@npm:1.1.11" - dependencies: - balanced-match: "npm:^1.0.0" - concat-map: "npm:0.0.1" - checksum: 10/faf34a7bb0c3fcf4b59c7808bc5d2a96a40988addf2e7e09dfbb67a2251800e0d14cd2bfc1aa79174f2f5095c54ff27f46fb1289fe2d77dac755b5eb3434cc07 - languageName: node - linkType: hard - -"brace-expansion@npm:^2.0.1": - version: 2.0.1 - resolution: "brace-expansion@npm:2.0.1" - dependencies: - balanced-match: "npm:^1.0.0" - checksum: 10/a61e7cd2e8a8505e9f0036b3b6108ba5e926b4b55089eeb5550cd04a471fe216c96d4fe7e4c7f995c728c554ae20ddfc4244cad10aef255e72b62930afd233d1 - languageName: node - linkType: hard - -"braces@npm:^3.0.3": - version: 3.0.3 - resolution: "braces@npm:3.0.3" - dependencies: - fill-range: "npm:^7.1.1" - checksum: 10/fad11a0d4697a27162840b02b1fad249c1683cbc510cd5bf1a471f2f8085c046d41094308c577a50a03a579dd99d5a6b3724c4b5e8b14df2c4443844cfcda2c6 - languageName: node - linkType: hard - -"browser-assert@npm:^1.2.1": - version: 1.2.1 - resolution: "browser-assert@npm:1.2.1" - checksum: 10/8b2407cd04c1ed592cf892dec35942b7d72635829221e0788c9a16c4d2afa8b7156bc9705b1c4b32c30d88136c576fda3cbcb8f494d6f865264c706ea8798d92 - languageName: node - linkType: hard - -"browserslist@npm:^4.24.0": - version: 4.24.0 - resolution: "browserslist@npm:4.24.0" - dependencies: - caniuse-lite: "npm:^1.0.30001663" - electron-to-chromium: "npm:^1.5.28" - node-releases: "npm:^2.0.18" - update-browserslist-db: "npm:^1.1.0" - bin: - browserslist: cli.js - checksum: 10/26c1b8ba257a0b51b102080ba9d42945af2abaa8c4cf6da21cd47b3f123fc1e81640203b293214356c2c17d9d265bb3a5ed428b6d302f383576dd6ce8fd5036c - languageName: node - linkType: hard - -"cacache@npm:^18.0.0": - version: 18.0.4 - resolution: "cacache@npm:18.0.4" - dependencies: - "@npmcli/fs": "npm:^3.1.0" - fs-minipass: "npm:^3.0.0" - glob: "npm:^10.2.2" - lru-cache: "npm:^10.0.1" - minipass: "npm:^7.0.3" - minipass-collect: "npm:^2.0.1" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - p-map: "npm:^4.0.0" - ssri: "npm:^10.0.0" - tar: "npm:^6.1.11" - unique-filename: "npm:^3.0.0" - checksum: 10/ca2f7b2d3003f84d362da9580b5561058ccaecd46cba661cbcff0375c90734b610520d46b472a339fd032d91597ad6ed12dde8af81571197f3c9772b5d35b104 - languageName: node - linkType: hard - -"call-bind@npm:^1.0.2, call-bind@npm:^1.0.7": - version: 1.0.7 - resolution: "call-bind@npm:1.0.7" - dependencies: - es-define-property: "npm:^1.0.0" - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.4" - set-function-length: "npm:^1.2.1" - checksum: 10/cd6fe658e007af80985da5185bff7b55e12ef4c2b6f41829a26ed1eef254b1f1c12e3dfd5b2b068c6ba8b86aba62390842d81752e67dcbaec4f6f76e7113b6b7 - languageName: node - linkType: hard - -"callsites@npm:^3.0.0": - version: 3.1.0 - resolution: "callsites@npm:3.1.0" - checksum: 10/072d17b6abb459c2ba96598918b55868af677154bec7e73d222ef95a8fdb9bbf7dae96a8421085cdad8cd190d86653b5b6dc55a4484f2e5b2e27d5e0c3fc15b3 - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001663": - version: 1.0.30001669 - resolution: "caniuse-lite@npm:1.0.30001669" - checksum: 10/cd0b481bb997703cb7651e55666b4aa4e7b4ecf9784796e2393179a15e55c71a6abc6ff865c922bbd3bbfa4a4bf0530d8da13989b97ff8c7850c8a5bd4e00491 - languageName: node - linkType: hard - -"chai@npm:^5.1.1": - version: 5.1.1 - resolution: "chai@npm:5.1.1" - dependencies: - assertion-error: "npm:^2.0.1" - check-error: "npm:^2.1.1" - deep-eql: "npm:^5.0.1" - loupe: "npm:^3.1.0" - pathval: "npm:^2.0.0" - checksum: 10/ee67279a5613bd36dc1dc13660042429ae2f1dc5a9030a6abcf381345866dfb5bce7bc10b9d74c8de86b6f656489f654bbbef3f3361e06925591e6a00c72afff - languageName: node - linkType: hard - -"chalk@npm:^2.4.2": - version: 2.4.2 - resolution: "chalk@npm:2.4.2" - dependencies: - ansi-styles: "npm:^3.2.1" - escape-string-regexp: "npm:^1.0.5" - supports-color: "npm:^5.3.0" - checksum: 10/3d1d103433166f6bfe82ac75724951b33769675252d8417317363ef9d54699b7c3b2d46671b772b893a8e50c3ece70c4b933c73c01e81bc60ea4df9b55afa303 - languageName: node - linkType: hard - -"chalk@npm:^3.0.0": - version: 3.0.0 - resolution: "chalk@npm:3.0.0" - dependencies: - ansi-styles: "npm:^4.1.0" - supports-color: "npm:^7.1.0" - checksum: 10/37f90b31fd655fb49c2bd8e2a68aebefddd64522655d001ef417e6f955def0ed9110a867ffc878a533f2dafea5f2032433a37c8a7614969baa7f8a1cd424ddfc - languageName: node - linkType: hard - -"chalk@npm:^4.0.0, chalk@npm:^4.1.0": - version: 4.1.2 - resolution: "chalk@npm:4.1.2" - dependencies: - ansi-styles: "npm:^4.1.0" - supports-color: "npm:^7.1.0" - checksum: 10/cb3f3e594913d63b1814d7ca7c9bafbf895f75fbf93b92991980610dfd7b48500af4e3a5d4e3a8f337990a96b168d7eb84ee55efdce965e2ee8efc20f8c8f139 - languageName: node - linkType: hard - -"check-error@npm:^2.1.1": - version: 2.1.1 - resolution: "check-error@npm:2.1.1" - checksum: 10/d785ed17b1d4a4796b6e75c765a9a290098cf52ff9728ce0756e8ffd4293d2e419dd30c67200aee34202463b474306913f2fcfaf1890641026d9fc6966fea27a - languageName: node - linkType: hard - -"chownr@npm:^2.0.0": - version: 2.0.0 - resolution: "chownr@npm:2.0.0" - checksum: 10/c57cf9dd0791e2f18a5ee9c1a299ae6e801ff58fee96dc8bfd0dcb4738a6ce58dd252a3605b1c93c6418fe4f9d5093b28ffbf4d66648cb2a9c67eaef9679be2f - languageName: node - linkType: hard - -"chromatic@npm:^11.4.0": - version: 11.12.6 - resolution: "chromatic@npm:11.12.6" - peerDependencies: - "@chromatic-com/cypress": ^0.*.* || ^1.0.0 - "@chromatic-com/playwright": ^0.*.* || ^1.0.0 - peerDependenciesMeta: - "@chromatic-com/cypress": - optional: true - "@chromatic-com/playwright": - optional: true - bin: - chroma: dist/bin.js - chromatic: dist/bin.js - chromatic-cli: dist/bin.js - checksum: 10/ce5f58bbbb2500a9a7fed9c192129aea6bc54e6a1fa655bb82866da8b94ebf4f4e4dec2bbb7f935b3c9b70547e4f975efd357936ea79c04236803088be6aee41 - languageName: node - linkType: hard - -"clean-stack@npm:^2.0.0": - version: 2.2.0 - resolution: "clean-stack@npm:2.2.0" - checksum: 10/2ac8cd2b2f5ec986a3c743935ec85b07bc174d5421a5efc8017e1f146a1cf5f781ae962618f416352103b32c9cd7e203276e8c28241bbe946160cab16149fb68 - languageName: node - linkType: hard - -"color-convert@npm:^1.9.0": - version: 1.9.3 - resolution: "color-convert@npm:1.9.3" - dependencies: - color-name: "npm:1.1.3" - checksum: 10/ffa319025045f2973919d155f25e7c00d08836b6b33ea2d205418c59bd63a665d713c52d9737a9e0fe467fb194b40fbef1d849bae80d674568ee220a31ef3d10 - languageName: node - linkType: hard - -"color-convert@npm:^2.0.1": - version: 2.0.1 - resolution: "color-convert@npm:2.0.1" - dependencies: - color-name: "npm:~1.1.4" - checksum: 10/fa00c91b4332b294de06b443923246bccebe9fab1b253f7fe1772d37b06a2269b4039a85e309abe1fe11b267b11c08d1d0473fda3badd6167f57313af2887a64 - languageName: node - linkType: hard - -"color-name@npm:1.1.3": - version: 1.1.3 - resolution: "color-name@npm:1.1.3" - checksum: 10/09c5d3e33d2105850153b14466501f2bfb30324a2f76568a408763a3b7433b0e50e5b4ab1947868e65cb101bb7cb75029553f2c333b6d4b8138a73fcc133d69d - languageName: node - linkType: hard - -"color-name@npm:~1.1.4": - version: 1.1.4 - resolution: "color-name@npm:1.1.4" - checksum: 10/b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 - languageName: node - linkType: hard - -"concat-map@npm:0.0.1": - version: 0.0.1 - resolution: "concat-map@npm:0.0.1" - checksum: 10/9680699c8e2b3af0ae22592cb764acaf973f292a7b71b8a06720233011853a58e256c89216a10cbe889727532fd77f8bcd49a760cedfde271b8e006c20e079f2 - languageName: node - linkType: hard - -"convert-source-map@npm:^2.0.0": - version: 2.0.0 - resolution: "convert-source-map@npm:2.0.0" - checksum: 10/c987be3ec061348cdb3c2bfb924bec86dea1eacad10550a85ca23edb0fe3556c3a61c7399114f3331ccb3499d7fd0285ab24566e5745929412983494c3926e15 - languageName: node - linkType: hard - -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.6": - version: 7.0.6 - resolution: "cross-spawn@npm:7.0.6" - dependencies: - path-key: "npm:^3.1.0" - shebang-command: "npm:^2.0.0" - which: "npm:^2.0.1" - checksum: 10/0d52657d7ae36eb130999dffff1168ec348687b48dd38e2ff59992ed916c88d328cf1d07ff4a4a10bc78de5e1c23f04b306d569e42f7a2293915c081e4dfee86 - languageName: node - linkType: hard - -"css.escape@npm:^1.5.1": - version: 1.5.1 - resolution: "css.escape@npm:1.5.1" - checksum: 10/f6d38088d870a961794a2580b2b2af1027731bb43261cfdce14f19238a88664b351cc8978abc20f06cc6bbde725699dec8deb6fe9816b139fc3f2af28719e774 - languageName: node - linkType: hard - -"csstype@npm:^3.0.2": - version: 3.1.3 - resolution: "csstype@npm:3.1.3" - checksum: 10/f593cce41ff5ade23f44e77521e3a1bcc2c64107041e1bf6c3c32adc5187d0d60983292fda326154d20b01079e24931aa5b08e4467cc488b60bb1e7f6d478ade - languageName: node - linkType: hard - -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": - version: 4.3.7 - resolution: "debug@npm:4.3.7" - dependencies: - ms: "npm:^2.1.3" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10/71168908b9a78227ab29d5d25fe03c5867750e31ce24bf2c44a86efc5af041758bb56569b0a3d48a9b5344c00a24a777e6f4100ed6dfd9534a42c1dde285125a - languageName: node - linkType: hard - -"deep-eql@npm:^5.0.1": - version: 5.0.2 - resolution: "deep-eql@npm:5.0.2" - checksum: 10/a529b81e2ef8821621d20a36959a0328873a3e49d393ad11f8efe8559f31239494c2eb889b80342808674c475802ba95b9d6c4c27641b9a029405104c1b59fcf - languageName: node - linkType: hard - -"deep-is@npm:^0.1.3": - version: 0.1.4 - resolution: "deep-is@npm:0.1.4" - checksum: 10/ec12d074aef5ae5e81fa470b9317c313142c9e8e2afe3f8efa124db309720db96d1d222b82b84c834e5f87e7a614b44a4684b6683583118b87c833b3be40d4d8 - languageName: node - linkType: hard - -"define-data-property@npm:^1.1.4": - version: 1.1.4 - resolution: "define-data-property@npm:1.1.4" - dependencies: - es-define-property: "npm:^1.0.0" - es-errors: "npm:^1.3.0" - gopd: "npm:^1.0.1" - checksum: 10/abdcb2505d80a53524ba871273e5da75e77e52af9e15b3aa65d8aad82b8a3a424dad7aee2cc0b71470ac7acf501e08defac362e8b6a73cdb4309f028061df4ae - languageName: node - linkType: hard - -"define-lazy-prop@npm:^2.0.0": - version: 2.0.0 - resolution: "define-lazy-prop@npm:2.0.0" - checksum: 10/0115fdb065e0490918ba271d7339c42453d209d4cb619dfe635870d906731eff3e1ade8028bb461ea27ce8264ec5e22c6980612d332895977e89c1bbc80fcee2 - languageName: node - linkType: hard - -"dequal@npm:^2.0.2, dequal@npm:^2.0.3": - version: 2.0.3 - resolution: "dequal@npm:2.0.3" - checksum: 10/6ff05a7561f33603df87c45e389c9ac0a95e3c056be3da1a0c4702149e3a7f6fe5ffbb294478687ba51a9e95f3a60e8b6b9005993acd79c292c7d15f71964b6b - languageName: node - linkType: hard - -"dir-glob@npm:^3.0.1": - version: 3.0.1 - resolution: "dir-glob@npm:3.0.1" - dependencies: - path-type: "npm:^4.0.0" - checksum: 10/fa05e18324510d7283f55862f3161c6759a3f2f8dbce491a2fc14c8324c498286c54282c1f0e933cb930da8419b30679389499b919122952a4f8592362ef4615 - languageName: node - linkType: hard - -"doctrine@npm:^3.0.0": - version: 3.0.0 - resolution: "doctrine@npm:3.0.0" - dependencies: - esutils: "npm:^2.0.2" - checksum: 10/b4b28f1df5c563f7d876e7461254a4597b8cabe915abe94d7c5d1633fed263fcf9a85e8d3836591fc2d040108e822b0d32758e5ec1fe31c590dc7e08086e3e48 - languageName: node - linkType: hard - -"dom-accessibility-api@npm:^0.5.9": - version: 0.5.16 - resolution: "dom-accessibility-api@npm:0.5.16" - checksum: 10/377b4a7f9eae0a5d72e1068c369c99e0e4ca17fdfd5219f3abd32a73a590749a267475a59d7b03a891f9b673c27429133a818c44b2e47e32fec024b34274e2ca - languageName: node - linkType: hard - -"dom-accessibility-api@npm:^0.6.3": - version: 0.6.3 - resolution: "dom-accessibility-api@npm:0.6.3" - checksum: 10/83d3371f8226487fbad36e160d44f1d9017fb26d46faba6a06fcad15f34633fc827b8c3e99d49f71d5f3253d866e2131826866fd0a3c86626f8eccfc361881ff - languageName: node - linkType: hard - -"eastasianwidth@npm:^0.2.0": - version: 0.2.0 - resolution: "eastasianwidth@npm:0.2.0" - checksum: 10/9b1d3e1baefeaf7d70799db8774149cef33b97183a6addceeba0cf6b85ba23ee2686f302f14482006df32df75d32b17c509c143a3689627929e4a8efaf483952 - languageName: node - linkType: hard - -"electron-to-chromium@npm:^1.5.28": - version: 1.5.41 - resolution: "electron-to-chromium@npm:1.5.41" - checksum: 10/74e1773d954ddbea82036715f91d2ef78246c9747ac58c00efa4eae3957a29a86b9ef41adfd1a33b5e563cecfc587ad90d0b513673fce65f505af6f2e90e1777 - languageName: node - linkType: hard - -"emoji-regex@npm:^8.0.0": - version: 8.0.0 - resolution: "emoji-regex@npm:8.0.0" - checksum: 10/c72d67a6821be15ec11997877c437491c313d924306b8da5d87d2a2bcc2cec9903cb5b04ee1a088460501d8e5b44f10df82fdc93c444101a7610b80c8b6938e1 - languageName: node - linkType: hard - -"emoji-regex@npm:^9.2.2": - version: 9.2.2 - resolution: "emoji-regex@npm:9.2.2" - checksum: 10/915acf859cea7131dac1b2b5c9c8e35c4849e325a1d114c30adb8cd615970f6dca0e27f64f3a4949d7d6ed86ecd79a1c5c63f02e697513cddd7b5835c90948b8 - languageName: node - linkType: hard - -"encoding@npm:^0.1.13": - version: 0.1.13 - resolution: "encoding@npm:0.1.13" - dependencies: - iconv-lite: "npm:^0.6.2" - checksum: 10/bb98632f8ffa823996e508ce6a58ffcf5856330fde839ae42c9e1f436cc3b5cc651d4aeae72222916545428e54fd0f6aa8862fd8d25bdbcc4589f1e3f3715e7f - languageName: node - linkType: hard - -"env-paths@npm:^2.2.0": - version: 2.2.1 - resolution: "env-paths@npm:2.2.1" - checksum: 10/65b5df55a8bab92229ab2b40dad3b387fad24613263d103a97f91c9fe43ceb21965cd3392b1ccb5d77088021e525c4e0481adb309625d0cb94ade1d1fb8dc17e - languageName: node - linkType: hard - -"err-code@npm:^2.0.2": - version: 2.0.3 - resolution: "err-code@npm:2.0.3" - checksum: 10/1d20d825cdcce8d811bfbe86340f4755c02655a7feb2f13f8c880566d9d72a3f6c92c192a6867632e490d6da67b678271f46e01044996a6443e870331100dfdd - languageName: node - linkType: hard - -"es-define-property@npm:^1.0.0": - version: 1.0.0 - resolution: "es-define-property@npm:1.0.0" - dependencies: - get-intrinsic: "npm:^1.2.4" - checksum: 10/f66ece0a887b6dca71848fa71f70461357c0e4e7249696f81bad0a1f347eed7b31262af4a29f5d726dc026426f085483b6b90301855e647aa8e21936f07293c6 - languageName: node - linkType: hard - -"es-errors@npm:^1.3.0": - version: 1.3.0 - resolution: "es-errors@npm:1.3.0" - checksum: 10/96e65d640156f91b707517e8cdc454dd7d47c32833aa3e85d79f24f9eb7ea85f39b63e36216ef0114996581969b59fe609a94e30316b08f5f4df1d44134cf8d5 - languageName: node - linkType: hard - -"es-toolkit@npm:^1.22.0": - version: 1.26.1 - resolution: "es-toolkit@npm:1.26.1" - dependenciesMeta: - "@trivago/prettier-plugin-sort-imports@4.3.0": - unplugged: true - prettier-plugin-sort-re-exports@0.0.1: - unplugged: true - checksum: 10/f6d8013c1bd96dd654a081bb5e8cd57f56ffa74262641fc3c45d3042ee57e608f05562438d4dac31c99ab1c38ae934830c58a6a2a158838171e0b8a23386aeb5 - languageName: node - linkType: hard - -"esbuild-register@npm:^3.5.0": - version: 3.6.0 - resolution: "esbuild-register@npm:3.6.0" - dependencies: - debug: "npm:^4.3.4" - peerDependencies: - esbuild: ">=0.12 <1" - checksum: 10/4ae1a016e3dad5b53c3d68cf07e31d8c1cec1a0b584038ece726097ac80bd33ab48fb224c766c9b341c04793837e652461eaca9327a116e7564f553b61ccca71 - languageName: node - linkType: hard - -"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0": - version: 0.25.0 - resolution: "esbuild@npm:0.25.0" - dependencies: - "@esbuild/aix-ppc64": "npm:0.25.0" - "@esbuild/android-arm": "npm:0.25.0" - "@esbuild/android-arm64": "npm:0.25.0" - "@esbuild/android-x64": "npm:0.25.0" - "@esbuild/darwin-arm64": "npm:0.25.0" - "@esbuild/darwin-x64": "npm:0.25.0" - "@esbuild/freebsd-arm64": "npm:0.25.0" - "@esbuild/freebsd-x64": "npm:0.25.0" - "@esbuild/linux-arm": "npm:0.25.0" - "@esbuild/linux-arm64": "npm:0.25.0" - "@esbuild/linux-ia32": "npm:0.25.0" - "@esbuild/linux-loong64": "npm:0.25.0" - "@esbuild/linux-mips64el": "npm:0.25.0" - "@esbuild/linux-ppc64": "npm:0.25.0" - "@esbuild/linux-riscv64": "npm:0.25.0" - "@esbuild/linux-s390x": "npm:0.25.0" - "@esbuild/linux-x64": "npm:0.25.0" - "@esbuild/netbsd-arm64": "npm:0.25.0" - "@esbuild/netbsd-x64": "npm:0.25.0" - "@esbuild/openbsd-arm64": "npm:0.25.0" - "@esbuild/openbsd-x64": "npm:0.25.0" - "@esbuild/sunos-x64": "npm:0.25.0" - "@esbuild/win32-arm64": "npm:0.25.0" - "@esbuild/win32-ia32": "npm:0.25.0" - "@esbuild/win32-x64": "npm:0.25.0" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-arm64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10/451daf6a442df29ec5d528587caa4ce783d41ff4acb93252da5a852b8d36c22e9f84d17f6721d4fbef9a1ba9855bc9fe1f167dd732c11665fe53032f2b89f114 - languageName: node - linkType: hard - -"esbuild@npm:^0.21.3": - version: 0.21.5 - resolution: "esbuild@npm:0.21.5" - dependencies: - "@esbuild/aix-ppc64": "npm:0.21.5" - "@esbuild/android-arm": "npm:0.21.5" - "@esbuild/android-arm64": "npm:0.21.5" - "@esbuild/android-x64": "npm:0.21.5" - "@esbuild/darwin-arm64": "npm:0.21.5" - "@esbuild/darwin-x64": "npm:0.21.5" - "@esbuild/freebsd-arm64": "npm:0.21.5" - "@esbuild/freebsd-x64": "npm:0.21.5" - "@esbuild/linux-arm": "npm:0.21.5" - "@esbuild/linux-arm64": "npm:0.21.5" - "@esbuild/linux-ia32": "npm:0.21.5" - "@esbuild/linux-loong64": "npm:0.21.5" - "@esbuild/linux-mips64el": "npm:0.21.5" - "@esbuild/linux-ppc64": "npm:0.21.5" - "@esbuild/linux-riscv64": "npm:0.21.5" - "@esbuild/linux-s390x": "npm:0.21.5" - "@esbuild/linux-x64": "npm:0.21.5" - "@esbuild/netbsd-x64": "npm:0.21.5" - "@esbuild/openbsd-x64": "npm:0.21.5" - "@esbuild/sunos-x64": "npm:0.21.5" - "@esbuild/win32-arm64": "npm:0.21.5" - "@esbuild/win32-ia32": "npm:0.21.5" - "@esbuild/win32-x64": "npm:0.21.5" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10/d2ff2ca84d30cce8e871517374d6c2290835380dc7cd413b2d49189ed170d45e407be14de2cb4794cf76f75cf89955c4714726ebd3de7444b3046f5cab23ab6b - languageName: node - linkType: hard - -"escalade@npm:^3.2.0": - version: 3.2.0 - resolution: "escalade@npm:3.2.0" - checksum: 10/9d7169e3965b2f9ae46971afa392f6e5a25545ea30f2e2dd99c9b0a95a3f52b5653681a84f5b2911a413ddad2d7a93d3514165072f349b5ffc59c75a899970d6 - languageName: node - linkType: hard - -"escape-string-regexp@npm:^1.0.5": - version: 1.0.5 - resolution: "escape-string-regexp@npm:1.0.5" - checksum: 10/6092fda75c63b110c706b6a9bfde8a612ad595b628f0bd2147eea1d3406723020810e591effc7db1da91d80a71a737a313567c5abb3813e8d9c71f4aa595b410 - languageName: node - linkType: hard - -"escape-string-regexp@npm:^4.0.0": - version: 4.0.0 - resolution: "escape-string-regexp@npm:4.0.0" - checksum: 10/98b48897d93060f2322108bf29db0feba7dd774be96cd069458d1453347b25ce8682ecc39859d4bca2203cc0ab19c237bcc71755eff49a0f8d90beadeeba5cc5 - languageName: node - linkType: hard - -"eslint-plugin-storybook@npm:^0.11.0": - version: 0.11.4 - resolution: "eslint-plugin-storybook@npm:0.11.4" - dependencies: - "@storybook/csf": "npm:^0.1.11" - "@typescript-eslint/utils": "npm:^8.8.1" - ts-dedent: "npm:^2.2.0" - peerDependencies: - eslint: ">=8" - typescript: ">=4.8.4 <5.8.0" - checksum: 10/f0fbeef2686e399861db644fd3ffa2759ea50e99a84dc25cf43ff6dd12ac8d12d34b90722e56139a43b58f65a4ea69c36d6919c5224943998a902d2b1d5d13cc - languageName: node - linkType: hard - -"eslint-scope@npm:^5.1.1": - version: 5.1.1 - resolution: "eslint-scope@npm:5.1.1" - dependencies: - esrecurse: "npm:^4.3.0" - estraverse: "npm:^4.1.1" - checksum: 10/c541ef384c92eb5c999b7d3443d80195fcafb3da335500946f6db76539b87d5826c8f2e1d23bf6afc3154ba8cd7c8e566f8dc00f1eea25fdf3afc8fb9c87b238 - languageName: node - linkType: hard - -"eslint-scope@npm:^8.3.0": - version: 8.3.0 - resolution: "eslint-scope@npm:8.3.0" - dependencies: - esrecurse: "npm:^4.3.0" - estraverse: "npm:^5.2.0" - checksum: 10/ee1ff009e949423639a8b53453c0cb189967d9142c5d94dc3752bed9880140a0760007148ac6b0bd03557d70ede9cd7c3b1e66f9a7f3427b2dbeca2a5be22c91 - languageName: node - linkType: hard - -"eslint-visitor-keys@npm:^3.3.0": - version: 3.4.3 - resolution: "eslint-visitor-keys@npm:3.4.3" - checksum: 10/3f357c554a9ea794b094a09bd4187e5eacd1bc0d0653c3adeb87962c548e6a1ab8f982b86963ae1337f5d976004146536dcee5d0e2806665b193fbfbf1a9231b - languageName: node - linkType: hard - -"eslint-visitor-keys@npm:^4.2.0": - version: 4.2.0 - resolution: "eslint-visitor-keys@npm:4.2.0" - checksum: 10/9651b3356b01760e586b4c631c5268c0e1a85236e3292bf754f0472f465bf9a856c0ddc261fceace155334118c0151778effafbab981413dbf9288349343fa25 - languageName: node - linkType: hard - -"eslint@npm:^9.11.1": - version: 9.25.1 - resolution: "eslint@npm:9.25.1" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" - "@eslint-community/regexpp": "npm:^4.12.1" - "@eslint/config-array": "npm:^0.20.0" - "@eslint/config-helpers": "npm:^0.2.1" - "@eslint/core": "npm:^0.13.0" - "@eslint/eslintrc": "npm:^3.3.1" - "@eslint/js": "npm:9.25.1" - "@eslint/plugin-kit": "npm:^0.2.8" - "@humanfs/node": "npm:^0.16.6" - "@humanwhocodes/module-importer": "npm:^1.0.1" - "@humanwhocodes/retry": "npm:^0.4.2" - "@types/estree": "npm:^1.0.6" - "@types/json-schema": "npm:^7.0.15" - ajv: "npm:^6.12.4" - chalk: "npm:^4.0.0" - cross-spawn: "npm:^7.0.6" - debug: "npm:^4.3.2" - escape-string-regexp: "npm:^4.0.0" - eslint-scope: "npm:^8.3.0" - eslint-visitor-keys: "npm:^4.2.0" - espree: "npm:^10.3.0" - esquery: "npm:^1.5.0" - esutils: "npm:^2.0.2" - fast-deep-equal: "npm:^3.1.3" - file-entry-cache: "npm:^8.0.0" - find-up: "npm:^5.0.0" - glob-parent: "npm:^6.0.2" - ignore: "npm:^5.2.0" - imurmurhash: "npm:^0.1.4" - is-glob: "npm:^4.0.0" - json-stable-stringify-without-jsonify: "npm:^1.0.1" - lodash.merge: "npm:^4.6.2" - minimatch: "npm:^3.1.2" - natural-compare: "npm:^1.4.0" - optionator: "npm:^0.9.3" - peerDependencies: - jiti: "*" - peerDependenciesMeta: - jiti: - optional: true - bin: - eslint: bin/eslint.js - checksum: 10/037bbdc5cba6f72199976dcdce115b1b479b9425ee1116c08bcaf25e0de4a74a0ffe696d48610ade79c91b04ef3e707a7215a42dfba9c7d3a0b85747d5902e67 - languageName: node - linkType: hard - -"espree@npm:^10.0.1, espree@npm:^10.3.0": - version: 10.3.0 - resolution: "espree@npm:10.3.0" - dependencies: - acorn: "npm:^8.14.0" - acorn-jsx: "npm:^5.3.2" - eslint-visitor-keys: "npm:^4.2.0" - checksum: 10/3412d44d4204c9e29d6b5dd0277400cfa0cd68495dc09eae1b9ce79d0c8985c1c5cc09cb9ba32a1cd963f48a49b0c46bdb7736afe395a300aa6bb1c0d86837e8 - languageName: node - linkType: hard - -"esprima@npm:~4.0.0": - version: 4.0.1 - resolution: "esprima@npm:4.0.1" - bin: - esparse: ./bin/esparse.js - esvalidate: ./bin/esvalidate.js - checksum: 10/f1d3c622ad992421362294f7acf866aa9409fbad4eb2e8fa230bd33944ce371d32279667b242d8b8907ec2b6ad7353a717f3c0e60e748873a34a7905174bc0eb - languageName: node - linkType: hard - -"esquery@npm:^1.5.0": - version: 1.6.0 - resolution: "esquery@npm:1.6.0" - dependencies: - estraverse: "npm:^5.1.0" - checksum: 10/c587fb8ec9ed83f2b1bc97cf2f6854cc30bf784a79d62ba08c6e358bf22280d69aee12827521cf38e69ae9761d23fb7fde593ce315610f85655c139d99b05e5a - languageName: node - linkType: hard - -"esrecurse@npm:^4.3.0": - version: 4.3.0 - resolution: "esrecurse@npm:4.3.0" - dependencies: - estraverse: "npm:^5.2.0" - checksum: 10/44ffcd89e714ea6b30143e7f119b104fc4d75e77ee913f34d59076b40ef2d21967f84e019f84e1fd0465b42cdbf725db449f232b5e47f29df29ed76194db8e16 - languageName: node - linkType: hard - -"estraverse@npm:^4.1.1": - version: 4.3.0 - resolution: "estraverse@npm:4.3.0" - checksum: 10/3f67ad02b6dbfaddd9ea459cf2b6ef4ecff9a6082a7af9d22e445b9abc082ad9ca47e1825557b293fcdae477f4714e561123e30bb6a5b2f184fb2bad4a9497eb - languageName: node - linkType: hard - -"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0": - version: 5.3.0 - resolution: "estraverse@npm:5.3.0" - checksum: 10/37cbe6e9a68014d34dbdc039f90d0baf72436809d02edffcc06ba3c2a12eb298048f877511353b130153e532aac8d68ba78430c0dd2f44806ebc7c014b01585e - languageName: node - linkType: hard - -"estree-walker@npm:^2.0.2": - version: 2.0.2 - resolution: "estree-walker@npm:2.0.2" - checksum: 10/b02109c5d46bc2ed47de4990eef770f7457b1159a229f0999a09224d2b85ffeed2d7679cffcff90aeb4448e94b0168feb5265b209cdec29aad50a3d6e93d21e2 - languageName: node - linkType: hard - -"estree-walker@npm:^3.0.3": - version: 3.0.3 - resolution: "estree-walker@npm:3.0.3" - dependencies: - "@types/estree": "npm:^1.0.0" - checksum: 10/a65728d5727b71de172c5df323385755a16c0fdab8234dc756c3854cfee343261ddfbb72a809a5660fac8c75d960bb3e21aa898c2d7e9b19bb298482ca58a3af - languageName: node - linkType: hard - -"esutils@npm:^2.0.2": - version: 2.0.3 - resolution: "esutils@npm:2.0.3" - checksum: 10/b23acd24791db11d8f65be5ea58fd9a6ce2df5120ae2da65c16cfc5331ff59d5ac4ef50af66cd4bde238881503ec839928a0135b99a036a9cdfa22d17fd56cdb - languageName: node - linkType: hard - -"exponential-backoff@npm:^3.1.1": - version: 3.1.1 - resolution: "exponential-backoff@npm:3.1.1" - checksum: 10/2d9bbb6473de7051f96790d5f9a678f32e60ed0aa70741dc7fdc96fec8d631124ec3374ac144387604f05afff9500f31a1d45bd9eee4cdc2e4f9ad2d9b9d5dbd - languageName: node - linkType: hard - -"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": - version: 3.1.3 - resolution: "fast-deep-equal@npm:3.1.3" - checksum: 10/e21a9d8d84f53493b6aa15efc9cfd53dd5b714a1f23f67fb5dc8f574af80df889b3bce25dc081887c6d25457cce704e636395333abad896ccdec03abaf1f3f9d - languageName: node - linkType: hard - -"fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2": - version: 3.3.2 - resolution: "fast-glob@npm:3.3.2" - dependencies: - "@nodelib/fs.stat": "npm:^2.0.2" - "@nodelib/fs.walk": "npm:^1.2.3" - glob-parent: "npm:^5.1.2" - merge2: "npm:^1.3.0" - micromatch: "npm:^4.0.4" - checksum: 10/222512e9315a0efca1276af9adb2127f02105d7288fa746145bf45e2716383fb79eb983c89601a72a399a56b7c18d38ce70457c5466218c5f13fad957cee16df - languageName: node - linkType: hard - -"fast-json-stable-stringify@npm:^2.0.0": - version: 2.1.0 - resolution: "fast-json-stable-stringify@npm:2.1.0" - checksum: 10/2c20055c1fa43c922428f16ca8bb29f2807de63e5c851f665f7ac9790176c01c3b40335257736b299764a8d383388dabc73c8083b8e1bc3d99f0a941444ec60e - languageName: node - linkType: hard - -"fast-levenshtein@npm:^2.0.6": - version: 2.0.6 - resolution: "fast-levenshtein@npm:2.0.6" - checksum: 10/eb7e220ecf2bab5159d157350b81d01f75726a4382f5a9266f42b9150c4523b9795f7f5d9fbbbeaeac09a441b2369f05ee02db48ea938584205530fe5693cfe1 - languageName: node - linkType: hard - -"fastq@npm:^1.6.0": - version: 1.17.1 - resolution: "fastq@npm:1.17.1" - dependencies: - reusify: "npm:^1.0.4" - checksum: 10/a443180068b527dd7b3a63dc7f2a47ceca2f3e97b9c00a1efe5538757e6cc4056a3526df94308075d7727561baf09ebaa5b67da8dcbddb913a021c5ae69d1f69 - languageName: node - linkType: hard - -"file-entry-cache@npm:^8.0.0": - version: 8.0.0 - resolution: "file-entry-cache@npm:8.0.0" - dependencies: - flat-cache: "npm:^4.0.0" - checksum: 10/afe55c4de4e0d226a23c1eae62a7219aafb390859122608a89fa4df6addf55c7fd3f1a2da6f5b41e7cdff496e4cf28bbd215d53eab5c817afa96d2b40c81bfb0 - languageName: node - linkType: hard - -"filesize@npm:^10.0.12": - version: 10.1.6 - resolution: "filesize@npm:10.1.6" - checksum: 10/e800837c4fc02303f1944d5a4c7b706df1c5cd95d745181852604fb00a1c2d55d2d3921252722bd2f0c86b59c94edaba23fa224776bbf977455d4034e7be1f45 - languageName: node - linkType: hard - -"fill-range@npm:^7.1.1": - version: 7.1.1 - resolution: "fill-range@npm:7.1.1" - dependencies: - to-regex-range: "npm:^5.0.1" - checksum: 10/a7095cb39e5bc32fada2aa7c7249d3f6b01bd1ce461a61b0adabacccabd9198500c6fb1f68a7c851a657e273fce2233ba869638897f3d7ed2e87a2d89b4436ea - languageName: node - linkType: hard - -"find-up@npm:^5.0.0": - version: 5.0.0 - resolution: "find-up@npm:5.0.0" - dependencies: - locate-path: "npm:^6.0.0" - path-exists: "npm:^4.0.0" - checksum: 10/07955e357348f34660bde7920783204ff5a26ac2cafcaa28bace494027158a97b9f56faaf2d89a6106211a8174db650dd9f503f9c0d526b1202d5554a00b9095 - languageName: node - linkType: hard - -"flat-cache@npm:^4.0.0": - version: 4.0.1 - resolution: "flat-cache@npm:4.0.1" - dependencies: - flatted: "npm:^3.2.9" - keyv: "npm:^4.5.4" - checksum: 10/58ce851d9045fffc7871ce2bd718bc485ad7e777bf748c054904b87c351ff1080c2c11da00788d78738bfb51b71e4d5ea12d13b98eb36e3358851ffe495b62dc - languageName: node - linkType: hard - -"flatted@npm:^3.2.9": - version: 3.3.1 - resolution: "flatted@npm:3.3.1" - checksum: 10/7b8376061d5be6e0d3658bbab8bde587647f68797cf6bfeae9dea0e5137d9f27547ab92aaff3512dd9d1299086a6d61be98e9d48a56d17531b634f77faadbc49 - languageName: node - linkType: hard - -"for-each@npm:^0.3.3": - version: 0.3.3 - resolution: "for-each@npm:0.3.3" - dependencies: - is-callable: "npm:^1.1.3" - checksum: 10/fdac0cde1be35610bd635ae958422e8ce0cc1313e8d32ea6d34cfda7b60850940c1fd07c36456ad76bd9c24aef6ff5e03b02beb58c83af5ef6c968a64eada676 - languageName: node - linkType: hard - -"foreground-child@npm:^3.1.0": - version: 3.3.0 - resolution: "foreground-child@npm:3.3.0" - dependencies: - cross-spawn: "npm:^7.0.0" - signal-exit: "npm:^4.0.1" - checksum: 10/e3a60480f3a09b12273ce2c5fcb9514d98dd0e528f58656a1b04680225f918d60a2f81f6a368f2f3b937fcee9cfc0cbf16f1ad9a0bc6a3a6e103a84c9a90087e - languageName: node - linkType: hard - -"fs-minipass@npm:^2.0.0": - version: 2.1.0 - resolution: "fs-minipass@npm:2.1.0" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10/03191781e94bc9a54bd376d3146f90fe8e082627c502185dbf7b9b3032f66b0b142c1115f3b2cc5936575fc1b44845ce903dd4c21bec2a8d69f3bd56f9cee9ec - languageName: node - linkType: hard - -"fs-minipass@npm:^3.0.0": - version: 3.0.3 - resolution: "fs-minipass@npm:3.0.3" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10/af143246cf6884fe26fa281621d45cfe111d34b30535a475bfa38dafe343dadb466c047a924ffc7d6b7b18265df4110224ce3803806dbb07173bf2087b648d7f - languageName: node - linkType: hard - -"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": - version: 2.3.3 - resolution: "fsevents@npm:2.3.3" - dependencies: - node-gyp: "npm:latest" - checksum: 10/4c1ade961ded57cdbfbb5cac5106ec17bc8bccd62e16343c569a0ceeca83b9dfef87550b4dc5cbb89642da412b20c5071f304c8c464b80415446e8e155a038c0 - conditions: os=darwin - languageName: node - linkType: hard - -"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin<compat/fsevents>, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin<compat/fsevents>": - version: 2.3.3 - resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin<compat/fsevents>::version=2.3.3&hash=df0bf1" - dependencies: - node-gyp: "npm:latest" - conditions: os=darwin - languageName: node - linkType: hard - -"function-bind@npm:^1.1.2": - version: 1.1.2 - resolution: "function-bind@npm:1.1.2" - checksum: 10/185e20d20f10c8d661d59aac0f3b63b31132d492e1b11fcc2a93cb2c47257ebaee7407c38513efd2b35cafdf972d9beb2ea4593c1e0f3bf8f2744836928d7454 - languageName: node - linkType: hard - -"gensync@npm:^1.0.0-beta.2": - version: 1.0.0-beta.2 - resolution: "gensync@npm:1.0.0-beta.2" - checksum: 10/17d8333460204fbf1f9160d067e1e77f908a5447febb49424b8ab043026049835c9ef3974445c57dbd39161f4d2b04356d7de12b2eecaa27a7a7ea7d871cbedd - languageName: node - linkType: hard - -"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.4": - version: 1.2.4 - resolution: "get-intrinsic@npm:1.2.4" - dependencies: - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - has-proto: "npm:^1.0.1" - has-symbols: "npm:^1.0.3" - hasown: "npm:^2.0.0" - checksum: 10/85bbf4b234c3940edf8a41f4ecbd4e25ce78e5e6ad4e24ca2f77037d983b9ef943fd72f00f3ee97a49ec622a506b67db49c36246150377efcda1c9eb03e5f06d - languageName: node - linkType: hard - -"glob-parent@npm:^5.1.2": - version: 5.1.2 - resolution: "glob-parent@npm:5.1.2" - dependencies: - is-glob: "npm:^4.0.1" - checksum: 10/32cd106ce8c0d83731966d31517adb766d02c3812de49c30cfe0675c7c0ae6630c11214c54a5ae67aca882cf738d27fd7768f21aa19118b9245950554be07247 - languageName: node - linkType: hard - -"glob-parent@npm:^6.0.2": - version: 6.0.2 - resolution: "glob-parent@npm:6.0.2" - dependencies: - is-glob: "npm:^4.0.3" - checksum: 10/c13ee97978bef4f55106b71e66428eb1512e71a7466ba49025fc2aec59a5bfb0954d5abd58fc5ee6c9b076eef4e1f6d3375c2e964b88466ca390da4419a786a8 - languageName: node - linkType: hard - -"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.3.10": - version: 10.4.5 - resolution: "glob@npm:10.4.5" - dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^3.1.2" - minimatch: "npm:^9.0.4" - minipass: "npm:^7.1.2" - package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^1.11.1" - bin: - glob: dist/esm/bin.mjs - checksum: 10/698dfe11828b7efd0514cd11e573eaed26b2dff611f0400907281ce3eab0c1e56143ef9b35adc7c77ecc71fba74717b510c7c223d34ca8a98ec81777b293d4ac - languageName: node - linkType: hard - -"globals@npm:^11.1.0": - version: 11.12.0 - resolution: "globals@npm:11.12.0" - checksum: 10/9f054fa38ff8de8fa356502eb9d2dae0c928217b8b5c8de1f09f5c9b6c8a96d8b9bd3afc49acbcd384a98a81fea713c859e1b09e214c60509517bb8fc2bc13c2 - languageName: node - linkType: hard - -"globals@npm:^14.0.0": - version: 14.0.0 - resolution: "globals@npm:14.0.0" - checksum: 10/03939c8af95c6df5014b137cac83aa909090c3a3985caef06ee9a5a669790877af8698ab38007e4c0186873adc14c0b13764acc754b16a754c216cc56aa5f021 - languageName: node - linkType: hard - -"globals@npm:^15.9.0": - version: 15.15.0 - resolution: "globals@npm:15.15.0" - checksum: 10/7f561c87b2fd381b27fc2db7df8a4ea7a9bb378667b8a7193e61fd2ca3a876479174e2a303a74345fbea6e1242e16db48915c1fd3bf35adcf4060a795b425e18 - languageName: node - linkType: hard - -"globby@npm:^11.1.0": - version: 11.1.0 - resolution: "globby@npm:11.1.0" - dependencies: - array-union: "npm:^2.1.0" - dir-glob: "npm:^3.0.1" - fast-glob: "npm:^3.2.9" - ignore: "npm:^5.2.0" - merge2: "npm:^1.4.1" - slash: "npm:^3.0.0" - checksum: 10/288e95e310227bbe037076ea81b7c2598ccbc3122d87abc6dab39e1eec309aa14f0e366a98cdc45237ffcfcbad3db597778c0068217dcb1950fef6249104e1b1 - languageName: node - linkType: hard - -"gopd@npm:^1.0.1": - version: 1.0.1 - resolution: "gopd@npm:1.0.1" - dependencies: - get-intrinsic: "npm:^1.1.3" - checksum: 10/5fbc7ad57b368ae4cd2f41214bd947b045c1a4be2f194a7be1778d71f8af9dbf4004221f3b6f23e30820eb0d052b4f819fe6ebe8221e2a3c6f0ee4ef173421ca - languageName: node - linkType: hard - -"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.6": - version: 4.2.11 - resolution: "graceful-fs@npm:4.2.11" - checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 - languageName: node - linkType: hard - -"graphemer@npm:^1.4.0": - version: 1.4.0 - resolution: "graphemer@npm:1.4.0" - checksum: 10/6dd60dba97007b21e3a829fab3f771803cc1292977fe610e240ea72afd67e5690ac9eeaafc4a99710e78962e5936ab5a460787c2a1180f1cb0ccfac37d29f897 - languageName: node - linkType: hard - -"has-flag@npm:^3.0.0": - version: 3.0.0 - resolution: "has-flag@npm:3.0.0" - checksum: 10/4a15638b454bf086c8148979aae044dd6e39d63904cd452d970374fa6a87623423da485dfb814e7be882e05c096a7ccf1ebd48e7e7501d0208d8384ff4dea73b - languageName: node - linkType: hard - -"has-flag@npm:^4.0.0": - version: 4.0.0 - resolution: "has-flag@npm:4.0.0" - checksum: 10/261a1357037ead75e338156b1f9452c016a37dcd3283a972a30d9e4a87441ba372c8b81f818cd0fbcd9c0354b4ae7e18b9e1afa1971164aef6d18c2b6095a8ad - languageName: node - linkType: hard - -"has-property-descriptors@npm:^1.0.2": - version: 1.0.2 - resolution: "has-property-descriptors@npm:1.0.2" - dependencies: - es-define-property: "npm:^1.0.0" - checksum: 10/2d8c9ab8cebb572e3362f7d06139a4592105983d4317e68f7adba320fe6ddfc8874581e0971e899e633fd5f72e262830edce36d5a0bc863dad17ad20572484b2 - languageName: node - linkType: hard - -"has-proto@npm:^1.0.1": - version: 1.0.3 - resolution: "has-proto@npm:1.0.3" - checksum: 10/0b67c2c94e3bea37db3e412e3c41f79d59259875e636ba471e94c009cdfb1fa82bf045deeffafc7dbb9c148e36cae6b467055aaa5d9fad4316e11b41e3ba551a - languageName: node - linkType: hard - -"has-symbols@npm:^1.0.3": - version: 1.0.3 - resolution: "has-symbols@npm:1.0.3" - checksum: 10/464f97a8202a7690dadd026e6d73b1ceeddd60fe6acfd06151106f050303eaa75855aaa94969df8015c11ff7c505f196114d22f7386b4a471038da5874cf5e9b - languageName: node - linkType: hard - -"has-tostringtag@npm:^1.0.0, has-tostringtag@npm:^1.0.2": - version: 1.0.2 - resolution: "has-tostringtag@npm:1.0.2" - dependencies: - has-symbols: "npm:^1.0.3" - checksum: 10/c74c5f5ceee3c8a5b8bc37719840dc3749f5b0306d818974141dda2471a1a2ca6c8e46b9d6ac222c5345df7a901c9b6f350b1e6d62763fec877e26609a401bfe - languageName: node - linkType: hard - -"hasown@npm:^2.0.0, hasown@npm:^2.0.2": - version: 2.0.2 - resolution: "hasown@npm:2.0.2" - dependencies: - function-bind: "npm:^1.1.2" - checksum: 10/7898a9c1788b2862cf0f9c345a6bec77ba4a0c0983c7f19d610c382343d4f98fa260686b225dfb1f88393a66679d2ec58ee310c1d6868c081eda7918f32cc70a - languageName: node - linkType: hard - -"http-cache-semantics@npm:^4.1.1": - version: 4.1.1 - resolution: "http-cache-semantics@npm:4.1.1" - checksum: 10/362d5ed66b12ceb9c0a328fb31200b590ab1b02f4a254a697dc796850cc4385603e75f53ec59f768b2dad3bfa1464bd229f7de278d2899a0e3beffc634b6683f - languageName: node - linkType: hard - -"http-proxy-agent@npm:^7.0.0": - version: 7.0.2 - resolution: "http-proxy-agent@npm:7.0.2" - dependencies: - agent-base: "npm:^7.1.0" - debug: "npm:^4.3.4" - checksum: 10/d062acfa0cb82beeb558f1043c6ba770ea892b5fb7b28654dbc70ea2aeea55226dd34c02a294f6c1ca179a5aa483c4ea641846821b182edbd9cc5d89b54c6848 - languageName: node - linkType: hard - -"https-proxy-agent@npm:^7.0.1": - version: 7.0.5 - resolution: "https-proxy-agent@npm:7.0.5" - dependencies: - agent-base: "npm:^7.0.2" - debug: "npm:4" - checksum: 10/6679d46159ab3f9a5509ee80c3a3fc83fba3a920a5e18d32176c3327852c3c00ad640c0c4210a8fd70ea3c4a6d3a1b375bf01942516e7df80e2646bdc77658ab - languageName: node - linkType: hard - -"iconv-lite@npm:^0.6.2": - version: 0.6.3 - resolution: "iconv-lite@npm:0.6.3" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10/24e3292dd3dadaa81d065c6f8c41b274a47098150d444b96e5f53b4638a9a71482921ea6a91a1f59bb71d9796de25e04afd05919fa64c360347ba65d3766f10f - languageName: node - linkType: hard - -"ignore@npm:^5.2.0, ignore@npm:^5.3.1": - version: 5.3.2 - resolution: "ignore@npm:5.3.2" - checksum: 10/cceb6a457000f8f6a50e1196429750d782afce5680dd878aa4221bd79972d68b3a55b4b1458fc682be978f4d3c6a249046aa0880637367216444ab7b014cfc98 - languageName: node - linkType: hard - -"import-fresh@npm:^3.2.1": - version: 3.3.0 - resolution: "import-fresh@npm:3.3.0" - dependencies: - parent-module: "npm:^1.0.0" - resolve-from: "npm:^4.0.0" - checksum: 10/2cacfad06e652b1edc50be650f7ec3be08c5e5a6f6d12d035c440a42a8cc028e60a5b99ca08a77ab4d6b1346da7d971915828f33cdab730d3d42f08242d09baa - languageName: node - linkType: hard - -"imurmurhash@npm:^0.1.4": - version: 0.1.4 - resolution: "imurmurhash@npm:0.1.4" - checksum: 10/2d30b157a91fe1c1d7c6f653cbf263f039be6c5bfa959245a16d4ee191fc0f2af86c08545b6e6beeb041c56b574d2d5b9f95343d378ab49c0f37394d541e7fc8 - languageName: node - linkType: hard - -"indent-string@npm:^4.0.0": - version: 4.0.0 - resolution: "indent-string@npm:4.0.0" - checksum: 10/cd3f5cbc9ca2d624c6a1f53f12e6b341659aba0e2d3254ae2b4464aaea8b4294cdb09616abbc59458f980531f2429784ed6a420d48d245bcad0811980c9efae9 - languageName: node - linkType: hard - -"inherits@npm:^2.0.3": - version: 2.0.4 - resolution: "inherits@npm:2.0.4" - checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 - languageName: node - linkType: hard - -"ip-address@npm:^9.0.5": - version: 9.0.5 - resolution: "ip-address@npm:9.0.5" - dependencies: - jsbn: "npm:1.1.0" - sprintf-js: "npm:^1.1.3" - checksum: 10/1ed81e06721af012306329b31f532b5e24e00cb537be18ddc905a84f19fe8f83a09a1699862bf3a1ec4b9dea93c55a3fa5faf8b5ea380431469df540f38b092c - languageName: node - linkType: hard - -"is-arguments@npm:^1.0.4": - version: 1.1.1 - resolution: "is-arguments@npm:1.1.1" - dependencies: - call-bind: "npm:^1.0.2" - has-tostringtag: "npm:^1.0.0" - checksum: 10/a170c7e26082e10de9be6e96d32ae3db4d5906194051b792e85fae3393b53cf2cb5b3557863e5c8ccbab55e2fd8f2f75aa643d437613f72052cf0356615c34be - languageName: node - linkType: hard - -"is-callable@npm:^1.1.3": - version: 1.2.7 - resolution: "is-callable@npm:1.2.7" - checksum: 10/48a9297fb92c99e9df48706241a189da362bff3003354aea4048bd5f7b2eb0d823cd16d0a383cece3d76166ba16d85d9659165ac6fcce1ac12e6c649d66dbdb9 - languageName: node - linkType: hard - -"is-core-module@npm:^2.13.0": - version: 2.15.1 - resolution: "is-core-module@npm:2.15.1" - dependencies: - hasown: "npm:^2.0.2" - checksum: 10/77316d5891d5743854bcef2cd2f24c5458fb69fbc9705c12ca17d54a2017a67d0693bbf1ba8c77af376c0eef6bf6d1b27a4ab08e4db4e69914c3789bdf2ceec5 - languageName: node - linkType: hard - -"is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": - version: 2.2.1 - resolution: "is-docker@npm:2.2.1" - bin: - is-docker: cli.js - checksum: 10/3fef7ddbf0be25958e8991ad941901bf5922ab2753c46980b60b05c1bf9c9c2402d35e6dc32e4380b980ef5e1970a5d9d5e5aa2e02d77727c3b6b5e918474c56 - languageName: node - linkType: hard - -"is-extglob@npm:^2.1.1": - version: 2.1.1 - resolution: "is-extglob@npm:2.1.1" - checksum: 10/df033653d06d0eb567461e58a7a8c9f940bd8c22274b94bf7671ab36df5719791aae15eef6d83bbb5e23283967f2f984b8914559d4449efda578c775c4be6f85 - languageName: node - linkType: hard - -"is-fullwidth-code-point@npm:^3.0.0": - version: 3.0.0 - resolution: "is-fullwidth-code-point@npm:3.0.0" - checksum: 10/44a30c29457c7fb8f00297bce733f0a64cd22eca270f83e58c105e0d015e45c019491a4ab2faef91ab51d4738c670daff901c799f6a700e27f7314029e99e348 - languageName: node - linkType: hard - -"is-generator-function@npm:^1.0.7": - version: 1.0.10 - resolution: "is-generator-function@npm:1.0.10" - dependencies: - has-tostringtag: "npm:^1.0.0" - checksum: 10/499a3ce6361064c3bd27fbff5c8000212d48506ebe1977842bbd7b3e708832d0deb1f4cc69186ece3640770e8c4f1287b24d99588a0b8058b2dbdd344bc1f47f - languageName: node - linkType: hard - -"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3": - version: 4.0.3 - resolution: "is-glob@npm:4.0.3" - dependencies: - is-extglob: "npm:^2.1.1" - checksum: 10/3ed74f2b0cdf4f401f38edb0442ddfde3092d79d7d35c9919c86641efdbcbb32e45aa3c0f70ce5eecc946896cd5a0f26e4188b9f2b881876f7cb6c505b82da11 - languageName: node - linkType: hard - -"is-lambda@npm:^1.0.1": - version: 1.0.1 - resolution: "is-lambda@npm:1.0.1" - checksum: 10/93a32f01940220532e5948538699ad610d5924ac86093fcee83022252b363eb0cc99ba53ab084a04e4fb62bf7b5731f55496257a4c38adf87af9c4d352c71c35 - languageName: node - linkType: hard - -"is-number@npm:^7.0.0": - version: 7.0.0 - resolution: "is-number@npm:7.0.0" - checksum: 10/6a6c3383f68afa1e05b286af866017c78f1226d43ac8cb064e115ff9ed85eb33f5c4f7216c96a71e4dfea289ef52c5da3aef5bbfade8ffe47a0465d70c0c8e86 - languageName: node - linkType: hard - -"is-typed-array@npm:^1.1.3": - version: 1.1.13 - resolution: "is-typed-array@npm:1.1.13" - dependencies: - which-typed-array: "npm:^1.1.14" - checksum: 10/f850ba08286358b9a11aee6d93d371a45e3c59b5953549ee1c1a9a55ba5c1dd1bd9952488ae194ad8f32a9cf5e79c8fa5f0cc4d78c00720aa0bbcf238b38062d - languageName: node - linkType: hard - -"is-wsl@npm:^2.2.0": - version: 2.2.0 - resolution: "is-wsl@npm:2.2.0" - dependencies: - is-docker: "npm:^2.0.0" - checksum: 10/20849846ae414997d290b75e16868e5261e86ff5047f104027026fd61d8b5a9b0b3ade16239f35e1a067b3c7cc02f70183cb661010ed16f4b6c7c93dad1b19d8 - languageName: node - linkType: hard - -"isexe@npm:^2.0.0": - version: 2.0.0 - resolution: "isexe@npm:2.0.0" - checksum: 10/7c9f715c03aff08f35e98b1fadae1b9267b38f0615d501824f9743f3aab99ef10e303ce7db3f186763a0b70a19de5791ebfc854ff884d5a8c4d92211f642ec92 - languageName: node - linkType: hard - -"isexe@npm:^3.1.1": - version: 3.1.1 - resolution: "isexe@npm:3.1.1" - checksum: 10/7fe1931ee4e88eb5aa524cd3ceb8c882537bc3a81b02e438b240e47012eef49c86904d0f0e593ea7c3a9996d18d0f1f3be8d3eaa92333977b0c3a9d353d5563e - languageName: node - linkType: hard - -"jackspeak@npm:^3.1.2": - version: 3.4.3 - resolution: "jackspeak@npm:3.4.3" - dependencies: - "@isaacs/cliui": "npm:^8.0.2" - "@pkgjs/parseargs": "npm:^0.11.0" - dependenciesMeta: - "@pkgjs/parseargs": - optional: true - checksum: 10/96f8786eaab98e4bf5b2a5d6d9588ea46c4d06bbc4f2eb861fdd7b6b182b16f71d8a70e79820f335d52653b16d4843b29dd9cdcf38ae80406756db9199497cf3 - languageName: node - linkType: hard - -"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": - version: 4.0.0 - resolution: "js-tokens@npm:4.0.0" - checksum: 10/af37d0d913fb56aec6dc0074c163cc71cd23c0b8aad5c2350747b6721d37ba118af35abdd8b33c47ec2800de07dedb16a527ca9c530ee004093e04958bd0cbf2 - languageName: node - linkType: hard - -"js-yaml@npm:^4.1.0": - version: 4.1.0 - resolution: "js-yaml@npm:4.1.0" - dependencies: - argparse: "npm:^2.0.1" - bin: - js-yaml: bin/js-yaml.js - checksum: 10/c138a34a3fd0d08ebaf71273ad4465569a483b8a639e0b118ff65698d257c2791d3199e3f303631f2cb98213fa7b5f5d6a4621fd0fff819421b990d30d967140 - languageName: node - linkType: hard - -"jsbn@npm:1.1.0": - version: 1.1.0 - resolution: "jsbn@npm:1.1.0" - checksum: 10/bebe7ae829bbd586ce8cbe83501dd8cb8c282c8902a8aeeed0a073a89dc37e8103b1244f3c6acd60278bcbfe12d93a3f83c9ac396868a3b3bbc3c5e5e3b648ef - languageName: node - linkType: hard - -"jsdoc-type-pratt-parser@npm:^4.0.0": - version: 4.1.0 - resolution: "jsdoc-type-pratt-parser@npm:4.1.0" - checksum: 10/30d88f95f6cbb4a1aa6d4b0d0ae46eb1096e606235ecaf9bab7a3ed5da860516b5d1cd967182765002f292c627526db918f3e56d34637bcf810e6ef84d403f3f - languageName: node - linkType: hard - -"jsesc@npm:^3.0.2": - version: 3.0.2 - resolution: "jsesc@npm:3.0.2" - bin: - jsesc: bin/jsesc - checksum: 10/8e5a7de6b70a8bd71f9cb0b5a7ade6a73ae6ab55e697c74cc997cede97417a3a65ed86c36f7dd6125fe49766e8386c845023d9e213916ca92c9dfdd56e2babf3 - languageName: node - linkType: hard - -"json-buffer@npm:3.0.1": - version: 3.0.1 - resolution: "json-buffer@npm:3.0.1" - checksum: 10/82876154521b7b68ba71c4f969b91572d1beabadd87bd3a6b236f85fbc7dc4695089191ed60bb59f9340993c51b33d479f45b6ba9f3548beb519705281c32c3c - languageName: node - linkType: hard - -"json-schema-traverse@npm:^0.4.1": - version: 0.4.1 - resolution: "json-schema-traverse@npm:0.4.1" - checksum: 10/7486074d3ba247769fda17d5181b345c9fb7d12e0da98b22d1d71a5db9698d8b4bd900a3ec1a4ffdd60846fc2556274a5c894d0c48795f14cb03aeae7b55260b - languageName: node - linkType: hard - -"json-stable-stringify-without-jsonify@npm:^1.0.1": - version: 1.0.1 - resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" - checksum: 10/12786c2e2f22c27439e6db0532ba321f1d0617c27ad8cb1c352a0e9249a50182fd1ba8b52a18899291604b0c32eafa8afd09e51203f19109a0537f68db2b652d - languageName: node - linkType: hard - -"json5@npm:^2.2.2, json5@npm:^2.2.3": - version: 2.2.3 - resolution: "json5@npm:2.2.3" - bin: - json5: lib/cli.js - checksum: 10/1db67b853ff0de3534085d630691d3247de53a2ed1390ba0ddff681ea43e9b3e30ecbdb65c5e9aab49435e44059c23dbd6fee8ee619419ba37465bb0dd7135da - languageName: node - linkType: hard - -"jsonfile@npm:^6.1.0": - version: 6.1.0 - resolution: "jsonfile@npm:6.1.0" - dependencies: - graceful-fs: "npm:^4.1.6" - universalify: "npm:^2.0.0" - dependenciesMeta: - graceful-fs: - optional: true - checksum: 10/03014769e7dc77d4cf05fa0b534907270b60890085dd5e4d60a382ff09328580651da0b8b4cdf44d91e4c8ae64d91791d965f05707beff000ed494a38b6fec85 - languageName: node - linkType: hard - -"keyv@npm:^4.5.4": - version: 4.5.4 - resolution: "keyv@npm:4.5.4" - dependencies: - json-buffer: "npm:3.0.1" - checksum: 10/167eb6ef64cc84b6fa0780ee50c9de456b422a1e18802209234f7c2cf7eae648c7741f32e50d7e24ccb22b24c13154070b01563d642755b156c357431a191e75 - languageName: node - linkType: hard - -"levn@npm:^0.4.1": - version: 0.4.1 - resolution: "levn@npm:0.4.1" - dependencies: - prelude-ls: "npm:^1.2.1" - type-check: "npm:~0.4.0" - checksum: 10/2e4720ff79f21ae08d42374b0a5c2f664c5be8b6c8f565bb4e1315c96ed3a8acaa9de788ffed82d7f2378cf36958573de07ef92336cb5255ed74d08b8318c9ee - languageName: node - linkType: hard - -"locate-path@npm:^6.0.0": - version: 6.0.0 - resolution: "locate-path@npm:6.0.0" - dependencies: - p-locate: "npm:^5.0.0" - checksum: 10/72eb661788a0368c099a184c59d2fee760b3831c9c1c33955e8a19ae4a21b4116e53fa736dc086cdeb9fce9f7cc508f2f92d2d3aae516f133e16a2bb59a39f5a - languageName: node - linkType: hard - -"lodash.merge@npm:^4.6.2": - version: 4.6.2 - resolution: "lodash.merge@npm:4.6.2" - checksum: 10/d0ea2dd0097e6201be083865d50c3fb54fbfbdb247d9cc5950e086c991f448b7ab0cdab0d57eacccb43473d3f2acd21e134db39f22dac2d6c9ba6bf26978e3d6 - languageName: node - linkType: hard - -"lodash@npm:^4.17.21": - version: 4.17.21 - resolution: "lodash@npm:4.17.21" - checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532 - languageName: node - linkType: hard - -"loose-envify@npm:^1.1.0": - version: 1.4.0 - resolution: "loose-envify@npm:1.4.0" - dependencies: - js-tokens: "npm:^3.0.0 || ^4.0.0" - bin: - loose-envify: cli.js - checksum: 10/6517e24e0cad87ec9888f500c5b5947032cdfe6ef65e1c1936a0c48a524b81e65542c9c3edc91c97d5bddc806ee2a985dbc79be89215d613b1de5db6d1cfe6f4 - languageName: node - linkType: hard - -"loupe@npm:^3.1.0, loupe@npm:^3.1.1, loupe@npm:^3.1.2": - version: 3.1.2 - resolution: "loupe@npm:3.1.2" - checksum: 10/8f5734e53fb64cd914aa7d986e01b6d4c2e3c6c56dcbd5428d71c2703f0ab46b5ab9f9eeaaf2b485e8a1c43f865bdd16ec08ae1a661c8f55acdbd9f4d59c607a - languageName: node - linkType: hard - -"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": - version: 10.4.3 - resolution: "lru-cache@npm:10.4.3" - checksum: 10/e6e90267360476720fa8e83cc168aa2bf0311f3f2eea20a6ba78b90a885ae72071d9db132f40fda4129c803e7dcec3a6b6a6fbb44ca90b081630b810b5d6a41a - languageName: node - linkType: hard - -"lru-cache@npm:^5.1.1": - version: 5.1.1 - resolution: "lru-cache@npm:5.1.1" - dependencies: - yallist: "npm:^3.0.2" - checksum: 10/951d2673dcc64a7fb888bf3d13bc2fdf923faca97d89cdb405ba3dfff77e2b26e5798d405e78fcd7094c9e7b8b4dab2ddc5a4f8a11928af24a207b7c738ca3f8 - languageName: node - linkType: hard - -"lz-string@npm:^1.5.0": - version: 1.5.0 - resolution: "lz-string@npm:1.5.0" - bin: - lz-string: bin/bin.js - checksum: 10/e86f0280e99a8d8cd4eef24d8601ddae15ce54e43ac9990dfcb79e1e081c255ad24424a30d78d2ad8e51a8ce82a66a930047fed4b4aa38c6f0b392ff9300edfc - languageName: node - linkType: hard - -"magic-string@npm:^0.27.0": - version: 0.27.0 - resolution: "magic-string@npm:0.27.0" - dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.4.13" - checksum: 10/10a18a48d22fb14467d6cb4204aba58d6790ae7ba023835dc7a65e310cf216f042a17fab1155ba43e47117310a9b7c3fd3bb79f40be40f5124d6b1af9e96399b - languageName: node - linkType: hard - -"magic-string@npm:^0.30.0": - version: 0.30.12 - resolution: "magic-string@npm:0.30.12" - dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.0" - checksum: 10/98016180a52b28efc1362152b45671067facccdaead6b70c1c14c566cba98491bc2e1336474b0996397730dca24400e85649da84d3da62b2560ed03c067573e6 - languageName: node - linkType: hard - -"make-fetch-happen@npm:^13.0.0": - version: 13.0.1 - resolution: "make-fetch-happen@npm:13.0.1" - dependencies: - "@npmcli/agent": "npm:^2.0.0" - cacache: "npm:^18.0.0" - http-cache-semantics: "npm:^4.1.1" - is-lambda: "npm:^1.0.1" - minipass: "npm:^7.0.2" - minipass-fetch: "npm:^3.0.0" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - negotiator: "npm:^0.6.3" - proc-log: "npm:^4.2.0" - promise-retry: "npm:^2.0.1" - ssri: "npm:^10.0.0" - checksum: 10/11bae5ad6ac59b654dbd854f30782f9de052186c429dfce308eda42374528185a100ee40ac9ffdc36a2b6c821ecaba43913e4730a12f06f15e895ea9cb23fa59 - languageName: node - linkType: hard - -"map-or-similar@npm:^1.5.0": - version: 1.5.0 - resolution: "map-or-similar@npm:1.5.0" - checksum: 10/3cf43bcd0e7af41d7bade5f8b5be6bb9d021cc47e6008ad545d071cf3a709ba782884002f9eec6ccd51f572fc17841e07bf74628e0bc3694c33f4622b03e4b4c - languageName: node - linkType: hard - -"memoizerific@npm:^1.11.3": - version: 1.11.3 - resolution: "memoizerific@npm:1.11.3" - dependencies: - map-or-similar: "npm:^1.5.0" - checksum: 10/72b6b80699777d000f03db6e15fdabcd4afe77feb45be51fe195cb230c64a368fcfcfbb976375eac3283bd8193d6b1a67ac3081cae07f64fca73f1aa568d59e3 - languageName: node - linkType: hard - -"merge2@npm:^1.3.0, merge2@npm:^1.4.1": - version: 1.4.1 - resolution: "merge2@npm:1.4.1" - checksum: 10/7268db63ed5169466540b6fb947aec313200bcf6d40c5ab722c22e242f651994619bcd85601602972d3c85bd2cc45a358a4c61937e9f11a061919a1da569b0c2 - languageName: node - linkType: hard - -"micromatch@npm:^4.0.4": - version: 4.0.8 - resolution: "micromatch@npm:4.0.8" - dependencies: - braces: "npm:^3.0.3" - picomatch: "npm:^2.3.1" - checksum: 10/6bf2a01672e7965eb9941d1f02044fad2bd12486b5553dc1116ff24c09a8723157601dc992e74c911d896175918448762df3b3fd0a6b61037dd1a9766ddfbf58 - languageName: node - linkType: hard - -"min-indent@npm:^1.0.0, min-indent@npm:^1.0.1": - version: 1.0.1 - resolution: "min-indent@npm:1.0.1" - checksum: 10/bfc6dd03c5eaf623a4963ebd94d087f6f4bbbfd8c41329a7f09706b0cb66969c4ddd336abeb587bc44bc6f08e13bf90f0b374f9d71f9f01e04adc2cd6f083ef1 - languageName: node - linkType: hard - -"minimatch@npm:^3.1.2": - version: 3.1.2 - resolution: "minimatch@npm:3.1.2" - dependencies: - brace-expansion: "npm:^1.1.7" - checksum: 10/e0b25b04cd4ec6732830344e5739b13f8690f8a012d73445a4a19fbc623f5dd481ef7a5827fde25954cd6026fede7574cc54dc4643c99d6c6b653d6203f94634 - languageName: node - linkType: hard - -"minimatch@npm:^9.0.4": - version: 9.0.5 - resolution: "minimatch@npm:9.0.5" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10/dd6a8927b063aca6d910b119e1f2df6d2ce7d36eab91de83167dd136bb85e1ebff97b0d3de1cb08bd1f7e018ca170b4962479fefab5b2a69e2ae12cb2edc8348 - languageName: node - linkType: hard - -"minimist@npm:^1.2.6": - version: 1.2.8 - resolution: "minimist@npm:1.2.8" - checksum: 10/908491b6cc15a6c440ba5b22780a0ba89b9810e1aea684e253e43c4e3b8d56ec1dcdd7ea96dde119c29df59c936cde16062159eae4225c691e19c70b432b6e6f - languageName: node - linkType: hard - -"minipass-collect@npm:^2.0.1": - version: 2.0.1 - resolution: "minipass-collect@npm:2.0.1" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10/b251bceea62090f67a6cced7a446a36f4cd61ee2d5cea9aee7fff79ba8030e416327a1c5aa2908dc22629d06214b46d88fdab8c51ac76bacbf5703851b5ad342 - languageName: node - linkType: hard - -"minipass-fetch@npm:^3.0.0": - version: 3.0.5 - resolution: "minipass-fetch@npm:3.0.5" - dependencies: - encoding: "npm:^0.1.13" - minipass: "npm:^7.0.3" - minipass-sized: "npm:^1.0.3" - minizlib: "npm:^2.1.2" - dependenciesMeta: - encoding: - optional: true - checksum: 10/c669948bec1373313aaa8f104b962a3ced9f45c49b26366a4b0ae27ccdfa9c5740d72c8a84d3f8623d7a61c5fc7afdfda44789008c078f61a62441142efc4a97 - languageName: node - linkType: hard - -"minipass-flush@npm:^1.0.5": - version: 1.0.5 - resolution: "minipass-flush@npm:1.0.5" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10/56269a0b22bad756a08a94b1ffc36b7c9c5de0735a4dd1ab2b06c066d795cfd1f0ac44a0fcae13eece5589b908ecddc867f04c745c7009be0b566421ea0944cf - languageName: node - linkType: hard - -"minipass-pipeline@npm:^1.2.4": - version: 1.2.4 - resolution: "minipass-pipeline@npm:1.2.4" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10/b14240dac0d29823c3d5911c286069e36d0b81173d7bdf07a7e4a91ecdef92cdff4baaf31ea3746f1c61e0957f652e641223970870e2353593f382112257971b - languageName: node - linkType: hard - -"minipass-sized@npm:^1.0.3": - version: 1.0.3 - resolution: "minipass-sized@npm:1.0.3" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10/40982d8d836a52b0f37049a0a7e5d0f089637298e6d9b45df9c115d4f0520682a78258905e5c8b180fb41b593b0a82cc1361d2c74b45f7ada66334f84d1ecfdd - languageName: node - linkType: hard - -"minipass@npm:^3.0.0": - version: 3.3.6 - resolution: "minipass@npm:3.3.6" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10/a5c6ef069f70d9a524d3428af39f2b117ff8cd84172e19b754e7264a33df460873e6eb3d6e55758531580970de50ae950c496256bb4ad3691a2974cddff189f0 - languageName: node - linkType: hard - -"minipass@npm:^5.0.0": - version: 5.0.0 - resolution: "minipass@npm:5.0.0" - checksum: 10/61682162d29f45d3152b78b08bab7fb32ca10899bc5991ffe98afc18c9e9543bd1e3be94f8b8373ba6262497db63607079dc242ea62e43e7b2270837b7347c93 - languageName: node - linkType: hard - -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.1.2": - version: 7.1.2 - resolution: "minipass@npm:7.1.2" - checksum: 10/c25f0ee8196d8e6036661104bacd743785b2599a21de5c516b32b3fa2b83113ac89a2358465bc04956baab37ffb956ae43be679b2262bf7be15fce467ccd7950 - languageName: node - linkType: hard - -"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": - version: 2.1.2 - resolution: "minizlib@npm:2.1.2" - dependencies: - minipass: "npm:^3.0.0" - yallist: "npm:^4.0.0" - checksum: 10/ae0f45436fb51344dcb87938446a32fbebb540d0e191d63b35e1c773d47512e17307bf54aa88326cc6d176594d00e4423563a091f7266c2f9a6872cdc1e234d1 - languageName: node - linkType: hard - -"mkdirp@npm:^1.0.3": - version: 1.0.4 - resolution: "mkdirp@npm:1.0.4" - bin: - mkdirp: bin/cmd.js - checksum: 10/d71b8dcd4b5af2fe13ecf3bd24070263489404fe216488c5ba7e38ece1f54daf219e72a833a3a2dc404331e870e9f44963a33399589490956bff003a3404d3b2 - languageName: node - linkType: hard - -"ms@npm:^2.1.3": - version: 2.1.3 - resolution: "ms@npm:2.1.3" - checksum: 10/aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d - languageName: node - linkType: hard - -"nanoid@npm:^3.3.7": - version: 3.3.7 - resolution: "nanoid@npm:3.3.7" - bin: - nanoid: bin/nanoid.cjs - checksum: 10/ac1eb60f615b272bccb0e2b9cd933720dad30bf9708424f691b8113826bb91aca7e9d14ef5d9415a6ba15c266b37817256f58d8ce980c82b0ba3185352565679 - languageName: node - linkType: hard - -"natural-compare-lite@npm:^1.4.0": - version: 1.4.0 - resolution: "natural-compare-lite@npm:1.4.0" - checksum: 10/5222ac3986a2b78dd6069ac62cbb52a7bf8ffc90d972ab76dfe7b01892485d229530ed20d0c62e79a6b363a663b273db3bde195a1358ce9e5f779d4453887225 - languageName: node - linkType: hard - -"natural-compare@npm:^1.4.0": - version: 1.4.0 - resolution: "natural-compare@npm:1.4.0" - checksum: 10/23ad088b08f898fc9b53011d7bb78ec48e79de7627e01ab5518e806033861bef68d5b0cd0e2205c2f36690ac9571ff6bcb05eb777ced2eeda8d4ac5b44592c3d - languageName: node - linkType: hard - -"negotiator@npm:^0.6.3": - version: 0.6.4 - resolution: "negotiator@npm:0.6.4" - checksum: 10/d98c04a136583afd055746168f1067d58ce4bfe6e4c73ca1d339567f81ea1f7e665b5bd1e81f4771c67b6c2ea89b21cb2adaea2b16058c7dc31317778f931dab - languageName: node - linkType: hard - -"node-gyp@npm:latest": - version: 10.2.0 - resolution: "node-gyp@npm:10.2.0" - dependencies: - env-paths: "npm:^2.2.0" - exponential-backoff: "npm:^3.1.1" - glob: "npm:^10.3.10" - graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^13.0.0" - nopt: "npm:^7.0.0" - proc-log: "npm:^4.1.0" - semver: "npm:^7.3.5" - tar: "npm:^6.2.1" - which: "npm:^4.0.0" - bin: - node-gyp: bin/node-gyp.js - checksum: 10/41773093b1275751dec942b985982fd4e7a69b88cae719b868babcef3880ee6168aaec8dcaa8cd0b9fa7c84873e36cc549c6cac6a124ee65ba4ce1f1cc108cfe - languageName: node - linkType: hard - -"node-releases@npm:^2.0.18": - version: 2.0.18 - resolution: "node-releases@npm:2.0.18" - checksum: 10/241e5fa9556f1c12bafb83c6c3e94f8cf3d8f2f8f904906ecef6e10bcaa1d59aa61212d4651bec70052015fc54bd3fdcdbe7fc0f638a17e6685aa586c076ec4e - languageName: node - linkType: hard - -"nopt@npm:^7.0.0": - version: 7.2.1 - resolution: "nopt@npm:7.2.1" - dependencies: - abbrev: "npm:^2.0.0" - bin: - nopt: bin/nopt.js - checksum: 10/95a1f6dec8a81cd18cdc2fed93e6f0b4e02cf6bdb4501c848752c6e34f9883d9942f036a5e3b21a699047d8a448562d891e67492df68ec9c373e6198133337ae - languageName: node - linkType: hard - -"open@npm:^8.0.4": - version: 8.4.2 - resolution: "open@npm:8.4.2" - dependencies: - define-lazy-prop: "npm:^2.0.0" - is-docker: "npm:^2.1.1" - is-wsl: "npm:^2.2.0" - checksum: 10/acd81a1d19879c818acb3af2d2e8e9d81d17b5367561e623248133deb7dd3aefaed527531df2677d3e6aaf0199f84df57b6b2262babff8bf46ea0029aac536c9 - languageName: node - linkType: hard - -"optionator@npm:^0.9.3": - version: 0.9.4 - resolution: "optionator@npm:0.9.4" - dependencies: - deep-is: "npm:^0.1.3" - fast-levenshtein: "npm:^2.0.6" - levn: "npm:^0.4.1" - prelude-ls: "npm:^1.2.1" - type-check: "npm:^0.4.0" - word-wrap: "npm:^1.2.5" - checksum: 10/a8398559c60aef88d7f353a4f98dcdff6090a4e70f874c827302bf1213d9106a1c4d5fcb68dacb1feb3c30a04c4102f41047aa55d4c576b863d6fc876e001af6 - languageName: node - linkType: hard - -"p-limit@npm:^3.0.2": - version: 3.1.0 - resolution: "p-limit@npm:3.1.0" - dependencies: - yocto-queue: "npm:^0.1.0" - checksum: 10/7c3690c4dbf62ef625671e20b7bdf1cbc9534e83352a2780f165b0d3ceba21907e77ad63401708145ca4e25bfc51636588d89a8c0aeb715e6c37d1c066430360 - languageName: node - linkType: hard - -"p-locate@npm:^5.0.0": - version: 5.0.0 - resolution: "p-locate@npm:5.0.0" - dependencies: - p-limit: "npm:^3.0.2" - checksum: 10/1623088f36cf1cbca58e9b61c4e62bf0c60a07af5ae1ca99a720837356b5b6c5ba3eb1b2127e47a06865fee59dd0453cad7cc844cda9d5a62ac1a5a51b7c86d3 - languageName: node - linkType: hard - -"p-map@npm:^4.0.0": - version: 4.0.0 - resolution: "p-map@npm:4.0.0" - dependencies: - aggregate-error: "npm:^3.0.0" - checksum: 10/7ba4a2b1e24c05e1fc14bbaea0fc6d85cf005ae7e9c9425d4575550f37e2e584b1af97bcde78eacd7559208f20995988d52881334db16cf77bc1bcf68e48ed7c - languageName: node - linkType: hard - -"package-json-from-dist@npm:^1.0.0": - version: 1.0.1 - resolution: "package-json-from-dist@npm:1.0.1" - checksum: 10/58ee9538f2f762988433da00e26acc788036914d57c71c246bf0be1b60cdbd77dd60b6a3e1a30465f0b248aeb80079e0b34cb6050b1dfa18c06953bb1cbc7602 - languageName: node - linkType: hard - -"parent-module@npm:^1.0.0": - version: 1.0.1 - resolution: "parent-module@npm:1.0.1" - dependencies: - callsites: "npm:^3.0.0" - checksum: 10/6ba8b255145cae9470cf5551eb74be2d22281587af787a2626683a6c20fbb464978784661478dd2a3f1dad74d1e802d403e1b03c1a31fab310259eec8ac560ff - languageName: node - linkType: hard - -"path-exists@npm:^4.0.0": - version: 4.0.0 - resolution: "path-exists@npm:4.0.0" - checksum: 10/505807199dfb7c50737b057dd8d351b82c033029ab94cb10a657609e00c1bc53b951cfdbccab8de04c5584d5eff31128ce6afd3db79281874a5ef2adbba55ed1 - languageName: node - linkType: hard - -"path-key@npm:^3.1.0": - version: 3.1.1 - resolution: "path-key@npm:3.1.1" - checksum: 10/55cd7a9dd4b343412a8386a743f9c746ef196e57c823d90ca3ab917f90ab9f13dd0ded27252ba49dbdfcab2b091d998bc446f6220cd3cea65db407502a740020 - languageName: node - linkType: hard - -"path-parse@npm:^1.0.7": - version: 1.0.7 - resolution: "path-parse@npm:1.0.7" - checksum: 10/49abf3d81115642938a8700ec580da6e830dde670be21893c62f4e10bd7dd4c3742ddc603fe24f898cba7eb0c6bc1777f8d9ac14185d34540c6d4d80cd9cae8a - languageName: node - linkType: hard - -"path-scurry@npm:^1.11.1": - version: 1.11.1 - resolution: "path-scurry@npm:1.11.1" - dependencies: - lru-cache: "npm:^10.2.0" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - checksum: 10/5e8845c159261adda6f09814d7725683257fcc85a18f329880ab4d7cc1d12830967eae5d5894e453f341710d5484b8fdbbd4d75181b4d6e1eb2f4dc7aeadc434 - languageName: node - linkType: hard - -"path-type@npm:^4.0.0": - version: 4.0.0 - resolution: "path-type@npm:4.0.0" - checksum: 10/5b1e2daa247062061325b8fdbfd1fb56dde0a448fb1455453276ea18c60685bdad23a445dc148cf87bc216be1573357509b7d4060494a6fd768c7efad833ee45 - languageName: node - linkType: hard - -"pathval@npm:^2.0.0": - version: 2.0.0 - resolution: "pathval@npm:2.0.0" - checksum: 10/b91575bf9cdf01757afd7b5e521eb8a0b874a49bc972d08e0047cfea0cd3c019f5614521d4bc83d2855e3fcc331db6817dfd533dd8f3d90b16bc76fad2450fc1 - languageName: node - linkType: hard - -"picocolors@npm:^1.0.0, picocolors@npm:^1.1.0": - version: 1.1.1 - resolution: "picocolors@npm:1.1.1" - checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 - languageName: node - linkType: hard - -"picomatch@npm:^2.3.1": - version: 2.3.1 - resolution: "picomatch@npm:2.3.1" - checksum: 10/60c2595003b05e4535394d1da94850f5372c9427ca4413b71210f437f7b2ca091dbd611c45e8b37d10036fa8eade25c1b8951654f9d3973bfa66a2ff4d3b08bc - languageName: node - linkType: hard - -"polished@npm:^4.2.2": - version: 4.3.1 - resolution: "polished@npm:4.3.1" - dependencies: - "@babel/runtime": "npm:^7.17.8" - checksum: 10/0902fe2eb16aecde1587a00efee7db8081b1331ac7bcfb6e61214d266388723a84858d732ad9395028e0aecd2bb8d0c39cc03d14b4c24c22329a0e40c38141eb - languageName: node - linkType: hard - -"possible-typed-array-names@npm:^1.0.0": - version: 1.0.0 - resolution: "possible-typed-array-names@npm:1.0.0" - checksum: 10/8ed3e96dfeea1c5880c1f4c9cb707e5fb26e8be22f14f82ef92df20fd2004e635c62ba47fbe8f2bb63bfd80dac1474be2fb39798da8c2feba2815435d1f749af - languageName: node - linkType: hard - -"postcss@npm:^8.4.43": - version: 8.4.47 - resolution: "postcss@npm:8.4.47" - dependencies: - nanoid: "npm:^3.3.7" - picocolors: "npm:^1.1.0" - source-map-js: "npm:^1.2.1" - checksum: 10/f2b50ba9b6fcb795232b6bb20de7cdc538c0025989a8ed9c4438d1960196ba3b7eaff41fdb1a5c701b3504651ea87aeb685577707f0ae4d6ce6f3eae5df79a81 - languageName: node - linkType: hard - -"prelude-ls@npm:^1.2.1": - version: 1.2.1 - resolution: "prelude-ls@npm:1.2.1" - checksum: 10/0b9d2c76801ca652a7f64892dd37b7e3fab149a37d2424920099bf894acccc62abb4424af2155ab36dea8744843060a2d8ddc983518d0b1e22265a22324b72ed - languageName: node - linkType: hard - -"prettier@npm:^3.1.1": - version: 3.3.3 - resolution: "prettier@npm:3.3.3" - bin: - prettier: bin/prettier.cjs - checksum: 10/5beac1f30b5b40162532b8e2f7c3a4eb650910a2695e9c8512a62ffdc09dae93190c29db9107fa7f26d1b6c71aad3628ecb9b5de1ecb0911191099be109434d7 - languageName: node - linkType: hard - -"pretty-format@npm:^27.0.2": - version: 27.5.1 - resolution: "pretty-format@npm:27.5.1" - dependencies: - ansi-regex: "npm:^5.0.1" - ansi-styles: "npm:^5.0.0" - react-is: "npm:^17.0.1" - checksum: 10/248990cbef9e96fb36a3e1ae6b903c551ca4ddd733f8d0912b9cc5141d3d0b3f9f8dfb4d799fb1c6723382c9c2083ffbfa4ad43ff9a0e7535d32d41fd5f01da6 - languageName: node - linkType: hard - -"proc-log@npm:^4.1.0, proc-log@npm:^4.2.0": - version: 4.2.0 - resolution: "proc-log@npm:4.2.0" - checksum: 10/4e1394491b717f6c1ade15c570ecd4c2b681698474d3ae2d303c1e4b6ab9455bd5a81566211e82890d5a5ae9859718cc6954d5150bb18b09b72ecb297beae90a - languageName: node - linkType: hard - -"process@npm:^0.11.10": - version: 0.11.10 - resolution: "process@npm:0.11.10" - checksum: 10/dbaa7e8d1d5cf375c36963ff43116772a989ef2bb47c9bdee20f38fd8fc061119cf38140631cf90c781aca4d3f0f0d2c834711952b728953f04fd7d238f59f5b - languageName: node - linkType: hard - -"promise-retry@npm:^2.0.1": - version: 2.0.1 - resolution: "promise-retry@npm:2.0.1" - dependencies: - err-code: "npm:^2.0.2" - retry: "npm:^0.12.0" - checksum: 10/96e1a82453c6c96eef53a37a1d6134c9f2482f94068f98a59145d0986ca4e497bf110a410adf73857e588165eab3899f0ebcf7b3890c1b3ce802abc0d65967d4 - languageName: node - linkType: hard - -"punycode@npm:^2.1.0": - version: 2.3.1 - resolution: "punycode@npm:2.3.1" - checksum: 10/febdc4362bead22f9e2608ff0171713230b57aff9dddc1c273aa2a651fbd366f94b7d6a71d78342a7c0819906750351ca7f2edd26ea41b626d87d6a13d1bd059 - languageName: node - linkType: hard - -"queue-microtask@npm:^1.2.2": - version: 1.2.3 - resolution: "queue-microtask@npm:1.2.3" - checksum: 10/72900df0616e473e824202113c3df6abae59150dfb73ed13273503127235320e9c8ca4aaaaccfd58cf417c6ca92a6e68ee9a5c3182886ae949a768639b388a7b - languageName: node - linkType: hard - -"react-confetti@npm:^6.1.0": - version: 6.1.0 - resolution: "react-confetti@npm:6.1.0" - dependencies: - tween-functions: "npm:^1.2.0" - peerDependencies: - react: ^16.3.0 || ^17.0.1 || ^18.0.0 - checksum: 10/33423767c42362b29e0c8fd09f60bcf40d64aeb721fd63564f85c61357706a6080563ec40f33435d210d5b5b1af7277aef4545deccf771d08785a3cb19749470 - languageName: node - linkType: hard - -"react-docgen-typescript@npm:^2.2.2": - version: 2.2.2 - resolution: "react-docgen-typescript@npm:2.2.2" - peerDependencies: - typescript: ">= 4.3.x" - checksum: 10/081fc3a876f53b9eeffcff357e5b6c190db799d50edcf11b187857d8cb8cce28000ed777ed16dd52a1c955f332612ef6b1f02cf8adcbcb084b8da9ff1ae5fd13 - languageName: node - linkType: hard - -"react-docgen@npm:^7.0.0": - version: 7.1.0 - resolution: "react-docgen@npm:7.1.0" - dependencies: - "@babel/core": "npm:^7.18.9" - "@babel/traverse": "npm:^7.18.9" - "@babel/types": "npm:^7.18.9" - "@types/babel__core": "npm:^7.18.0" - "@types/babel__traverse": "npm:^7.18.0" - "@types/doctrine": "npm:^0.0.9" - "@types/resolve": "npm:^1.20.2" - doctrine: "npm:^3.0.0" - resolve: "npm:^1.22.1" - strip-indent: "npm:^4.0.0" - checksum: 10/ce1f6e4394375307ecf268ef0b3e41fd75a6b050e70053c6e64b72f108d7606a4b8de6c0f1dab2759bc80864db01be38a9245e2d71024d2ed3d3e0179bd02140 - languageName: node - linkType: hard - -"react-dom@npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0": - version: 19.0.0 - resolution: "react-dom@npm:19.0.0" - dependencies: - scheduler: "npm:^0.25.0" - peerDependencies: - react: ^19.0.0 - checksum: 10/aa64a2f1991042f516260e8b0eca0ae777b6c8f1aa2b5ae096e80bbb6ac9b005aef2bca697969841d34f7e1819556263476bdfea36c35092e8d9aefde3de2d9a - languageName: node - linkType: hard - -"react-dom@npm:^18.0.2": - version: 18.3.1 - resolution: "react-dom@npm:18.3.1" - dependencies: - loose-envify: "npm:^1.1.0" - scheduler: "npm:^0.23.2" - peerDependencies: - react: ^18.3.1 - checksum: 10/3f4b73a3aa083091173b29812b10394dd06f4ac06aff410b74702cfb3aa29d7b0ced208aab92d5272919b612e5cda21aeb1d54191848cf6e46e9e354f3541f81 - languageName: node - linkType: hard - -"react-is@npm:^17.0.1": - version: 17.0.2 - resolution: "react-is@npm:17.0.2" - checksum: 10/73b36281e58eeb27c9cc6031301b6ae19ecdc9f18ae2d518bdb39b0ac564e65c5779405d623f1df9abf378a13858b79442480244bd579968afc1faf9a2ce5e05 - languageName: node - linkType: hard - -"react-router-dom@npm:^6.3.0": - version: 6.28.2 - resolution: "react-router-dom@npm:6.28.2" - dependencies: - "@remix-run/router": "npm:1.21.1" - react-router: "npm:6.28.2" - peerDependencies: - react: ">=16.8" - react-dom: ">=16.8" - checksum: 10/4775cb484c497be5833ef5e048378d685f970a72c75a595c8b74fce147eda9e705d885b71d888b1090a8f22e7630adb851766d34e771c3a649f73171f0fa4c2b - languageName: node - linkType: hard - -"react-router@npm:6.28.2": - version: 6.28.2 - resolution: "react-router@npm:6.28.2" - dependencies: - "@remix-run/router": "npm:1.21.1" - peerDependencies: - react: ">=16.8" - checksum: 10/4cf150e3762acff8a087d6b474861fdb73efdf829ce0619bc980f3d8fc5d9e45e67333ab7d62af5b775fba8efe8f8d342f089bec75f9b41f3162e139c0187efd - languageName: node - linkType: hard - -"react@npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0": - version: 19.0.0 - resolution: "react@npm:19.0.0" - checksum: 10/2490969c503f644703c88990d20e4011fa6119ddeca451e9de48f6d7ab058d670d2852a5fcd3aa3cd90a923ab2815d532637bd4a814add402ae5c0d4f129ee71 - languageName: node - linkType: hard - -"react@npm:^18.0.2": - version: 18.3.1 - resolution: "react@npm:18.3.1" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10/261137d3f3993eaa2368a83110466fc0e558bc2c7f7ae7ca52d94f03aac945f45146bd85e5f481044db1758a1dbb57879e2fcdd33924e2dde1bdc550ce73f7bf - languageName: node - linkType: hard - -"recast@npm:^0.23.5": - version: 0.23.9 - resolution: "recast@npm:0.23.9" - dependencies: - ast-types: "npm:^0.16.1" - esprima: "npm:~4.0.0" - source-map: "npm:~0.6.1" - tiny-invariant: "npm:^1.3.3" - tslib: "npm:^2.0.1" - checksum: 10/d60584be179d81a82fbe58b5bbe009aa42831ee114a21a3e3a22bda91334e0b8a1a4610dca8ecb7f9ea1426da4febc08134d3003085ad6ecee478d1808eb8796 - languageName: node - linkType: hard - -"redent@npm:^3.0.0": - version: 3.0.0 - resolution: "redent@npm:3.0.0" - dependencies: - indent-string: "npm:^4.0.0" - strip-indent: "npm:^3.0.0" - checksum: 10/fa1ef20404a2d399235e83cc80bd55a956642e37dd197b4b612ba7327bf87fa32745aeb4a1634b2bab25467164ab4ed9c15be2c307923dd08b0fe7c52431ae6b - languageName: node - linkType: hard - -"regenerator-runtime@npm:^0.14.0": - version: 0.14.1 - resolution: "regenerator-runtime@npm:0.14.1" - checksum: 10/5db3161abb311eef8c45bcf6565f4f378f785900ed3945acf740a9888c792f75b98ecb77f0775f3bf95502ff423529d23e94f41d80c8256e8fa05ed4b07cf471 - languageName: node - linkType: hard - -"resolve-from@npm:^4.0.0": - version: 4.0.0 - resolution: "resolve-from@npm:4.0.0" - checksum: 10/91eb76ce83621eea7bbdd9b55121a5c1c4a39e54a9ce04a9ad4517f102f8b5131c2cf07622c738a6683991bf54f2ce178f5a42803ecbd527ddc5105f362cc9e3 - languageName: node - linkType: hard - -"resolve@npm:^1.22.1, resolve@npm:^1.22.8": - version: 1.22.8 - resolution: "resolve@npm:1.22.8" - dependencies: - is-core-module: "npm:^2.13.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10/c473506ee01eb45cbcfefb68652ae5759e092e6b0fb64547feadf9736a6394f258fbc6f88e00c5ca36d5477fbb65388b272432a3600fa223062e54333c156753 - languageName: node - linkType: hard - -"resolve@patch:resolve@npm%3A^1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin<compat/resolve>": - version: 1.22.8 - resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin<compat/resolve>::version=1.22.8&hash=c3c19d" - dependencies: - is-core-module: "npm:^2.13.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10/f345cd37f56a2c0275e3fe062517c650bb673815d885e7507566df589375d165bbbf4bdb6aa95600a9bc55f4744b81f452b5a63f95b9f10a72787dba3c90890a - languageName: node - linkType: hard - -"retry@npm:^0.12.0": - version: 0.12.0 - resolution: "retry@npm:0.12.0" - checksum: 10/1f914879f97e7ee931ad05fe3afa629bd55270fc6cf1c1e589b6a99fab96d15daad0fa1a52a00c729ec0078045fe3e399bd4fd0c93bcc906957bdc17f89cb8e6 - languageName: node - linkType: hard - -"reusify@npm:^1.0.4": - version: 1.0.4 - resolution: "reusify@npm:1.0.4" - checksum: 10/14222c9e1d3f9ae01480c50d96057228a8524706db79cdeb5a2ce5bb7070dd9f409a6f84a02cbef8cdc80d39aef86f2dd03d155188a1300c599b05437dcd2ffb - languageName: node - linkType: hard - -"rollup@npm:^4.20.0": - version: 4.24.0 - resolution: "rollup@npm:4.24.0" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.24.0" - "@rollup/rollup-android-arm64": "npm:4.24.0" - "@rollup/rollup-darwin-arm64": "npm:4.24.0" - "@rollup/rollup-darwin-x64": "npm:4.24.0" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.24.0" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.24.0" - "@rollup/rollup-linux-arm64-gnu": "npm:4.24.0" - "@rollup/rollup-linux-arm64-musl": "npm:4.24.0" - "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.24.0" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.24.0" - "@rollup/rollup-linux-s390x-gnu": "npm:4.24.0" - "@rollup/rollup-linux-x64-gnu": "npm:4.24.0" - "@rollup/rollup-linux-x64-musl": "npm:4.24.0" - "@rollup/rollup-win32-arm64-msvc": "npm:4.24.0" - "@rollup/rollup-win32-ia32-msvc": "npm:4.24.0" - "@rollup/rollup-win32-x64-msvc": "npm:4.24.0" - "@types/estree": "npm:1.0.6" - fsevents: "npm:~2.3.2" - dependenciesMeta: - "@rollup/rollup-android-arm-eabi": - optional: true - "@rollup/rollup-android-arm64": - optional: true - "@rollup/rollup-darwin-arm64": - optional: true - "@rollup/rollup-darwin-x64": - optional: true - "@rollup/rollup-linux-arm-gnueabihf": - optional: true - "@rollup/rollup-linux-arm-musleabihf": - optional: true - "@rollup/rollup-linux-arm64-gnu": - optional: true - "@rollup/rollup-linux-arm64-musl": - optional: true - "@rollup/rollup-linux-powerpc64le-gnu": - optional: true - "@rollup/rollup-linux-riscv64-gnu": - optional: true - "@rollup/rollup-linux-s390x-gnu": - optional: true - "@rollup/rollup-linux-x64-gnu": - optional: true - "@rollup/rollup-linux-x64-musl": - optional: true - "@rollup/rollup-win32-arm64-msvc": - optional: true - "@rollup/rollup-win32-ia32-msvc": - optional: true - "@rollup/rollup-win32-x64-msvc": - optional: true - fsevents: - optional: true - bin: - rollup: dist/bin/rollup - checksum: 10/291dce8f180628a73d6749119a3e50aa917c416075302bc6f6ac655affc7f0ce9d7f025bef7318d424d0c5623dcb83e360f9ea0125273b6a2285c232172800cc - languageName: node - linkType: hard - -"run-parallel@npm:^1.1.9": - version: 1.2.0 - resolution: "run-parallel@npm:1.2.0" - dependencies: - queue-microtask: "npm:^1.2.2" - checksum: 10/cb4f97ad25a75ebc11a8ef4e33bb962f8af8516bb2001082ceabd8902e15b98f4b84b4f8a9b222e5d57fc3bd1379c483886ed4619367a7680dad65316993021d - languageName: node - linkType: hard - -"safer-buffer@npm:>= 2.1.2 < 3.0.0": - version: 2.1.2 - resolution: "safer-buffer@npm:2.1.2" - checksum: 10/7eaf7a0cf37cc27b42fb3ef6a9b1df6e93a1c6d98c6c6702b02fe262d5fcbd89db63320793b99b21cb5348097d0a53de81bd5f4e8b86e20cc9412e3f1cfb4e83 - languageName: node - linkType: hard - -"scheduler@npm:^0.23.2": - version: 0.23.2 - resolution: "scheduler@npm:0.23.2" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10/e8d68b89d18d5b028223edf090092846868a765a591944760942b77ea1f69b17235f7e956696efbb62c8130ab90af7e0949bfb8eba7896335507317236966bc9 - languageName: node - linkType: hard - -"scheduler@npm:^0.25.0": - version: 0.25.0 - resolution: "scheduler@npm:0.25.0" - checksum: 10/e661e38503ab29a153429a99203fefa764f28b35c079719eb5efdd2c1c1086522f6653d8ffce388209682c23891a6d1d32fa6badf53c35fb5b9cd0c55ace42de - languageName: node - linkType: hard - -"semver@npm:^6.3.1": - version: 6.3.1 - resolution: "semver@npm:6.3.1" - bin: - semver: bin/semver.js - checksum: 10/1ef3a85bd02a760c6ef76a45b8c1ce18226de40831e02a00bad78485390b98b6ccaa31046245fc63bba4a47a6a592b6c7eedc65cc47126e60489f9cc1ce3ed7e - languageName: node - linkType: hard - -"semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.6.0, semver@npm:^7.6.2": - version: 7.6.3 - resolution: "semver@npm:7.6.3" - bin: - semver: bin/semver.js - checksum: 10/36b1fbe1a2b6f873559cd57b238f1094a053dbfd997ceeb8757d79d1d2089c56d1321b9f1069ce263dc64cfa922fa1d2ad566b39426fe1ac6c723c1487589e10 - languageName: node - linkType: hard - -"set-function-length@npm:^1.2.1": - version: 1.2.2 - resolution: "set-function-length@npm:1.2.2" - dependencies: - define-data-property: "npm:^1.1.4" - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.4" - gopd: "npm:^1.0.1" - has-property-descriptors: "npm:^1.0.2" - checksum: 10/505d62b8e088468917ca4e3f8f39d0e29f9a563b97dbebf92f4bd2c3172ccfb3c5b8e4566d5fcd00784a00433900e7cb8fbc404e2dbd8c3818ba05bb9d4a8a6d - languageName: node - linkType: hard - -"shebang-command@npm:^2.0.0": - version: 2.0.0 - resolution: "shebang-command@npm:2.0.0" - dependencies: - shebang-regex: "npm:^3.0.0" - checksum: 10/6b52fe87271c12968f6a054e60f6bde5f0f3d2db483a1e5c3e12d657c488a15474121a1d55cd958f6df026a54374ec38a4a963988c213b7570e1d51575cea7fa - languageName: node - linkType: hard - -"shebang-regex@npm:^3.0.0": - version: 3.0.0 - resolution: "shebang-regex@npm:3.0.0" - checksum: 10/1a2bcae50de99034fcd92ad4212d8e01eedf52c7ec7830eedcf886622804fe36884278f2be8be0ea5fde3fd1c23911643a4e0f726c8685b61871c8908af01222 - languageName: node - linkType: hard - -"signal-exit@npm:^4.0.1": - version: 4.1.0 - resolution: "signal-exit@npm:4.1.0" - checksum: 10/c9fa63bbbd7431066174a48ba2dd9986dfd930c3a8b59de9c29d7b6854ec1c12a80d15310869ea5166d413b99f041bfa3dd80a7947bcd44ea8e6eb3ffeabfa1f - languageName: node - linkType: hard - -"slash@npm:^3.0.0": - version: 3.0.0 - resolution: "slash@npm:3.0.0" - checksum: 10/94a93fff615f25a999ad4b83c9d5e257a7280c90a32a7cb8b4a87996e4babf322e469c42b7f649fd5796edd8687652f3fb452a86dc97a816f01113183393f11c - languageName: node - linkType: hard - -"smart-buffer@npm:^4.2.0": - version: 4.2.0 - resolution: "smart-buffer@npm:4.2.0" - checksum: 10/927484aa0b1640fd9473cee3e0a0bcad6fce93fd7bbc18bac9ad0c33686f5d2e2c422fba24b5899c184524af01e11dd2bd051c2bf2b07e47aff8ca72cbfc60d2 - languageName: node - linkType: hard - -"socks-proxy-agent@npm:^8.0.3": - version: 8.0.4 - resolution: "socks-proxy-agent@npm:8.0.4" - dependencies: - agent-base: "npm:^7.1.1" - debug: "npm:^4.3.4" - socks: "npm:^2.8.3" - checksum: 10/c8e7c2b398338b49a0a0f4d2bae5c0602aeeca6b478b99415927b6c5db349ca258448f2c87c6958ebf83eea17d42cbc5d1af0bfecb276cac10b9658b0f07f7d7 - languageName: node - linkType: hard - -"socks@npm:^2.8.3": - version: 2.8.3 - resolution: "socks@npm:2.8.3" - dependencies: - ip-address: "npm:^9.0.5" - smart-buffer: "npm:^4.2.0" - checksum: 10/ffcb622c22481dfcd7589aae71fbfd71ca34334064d181df64bf8b7feaeee19706aba4cffd1de35cc7bbaeeaa0af96be2d7f40fcbc7bc0ab69533a7ae9ffc4fb - languageName: node - linkType: hard - -"source-map-js@npm:^1.2.1": - version: 1.2.1 - resolution: "source-map-js@npm:1.2.1" - checksum: 10/ff9d8c8bf096d534a5b7707e0382ef827b4dd360a577d3f34d2b9f48e12c9d230b5747974ee7c607f0df65113732711bb701fe9ece3c7edbd43cb2294d707df3 - languageName: node - linkType: hard - -"source-map@npm:~0.6.1": - version: 0.6.1 - resolution: "source-map@npm:0.6.1" - checksum: 10/59ef7462f1c29d502b3057e822cdbdae0b0e565302c4dd1a95e11e793d8d9d62006cdc10e0fd99163ca33ff2071360cf50ee13f90440806e7ed57d81cba2f7ff - languageName: node - linkType: hard - -"sprintf-js@npm:^1.1.3": - version: 1.1.3 - resolution: "sprintf-js@npm:1.1.3" - checksum: 10/e7587128c423f7e43cc625fe2f87e6affdf5ca51c1cc468e910d8aaca46bb44a7fbcfa552f787b1d3987f7043aeb4527d1b99559e6621e01b42b3f45e5a24cbb - languageName: node - linkType: hard - -"ssri@npm:^10.0.0": - version: 10.0.6 - resolution: "ssri@npm:10.0.6" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10/f92c1b3cc9bfd0a925417412d07d999935917bc87049f43ebec41074661d64cf720315661844106a77da9f8204b6d55ae29f9514e673083cae39464343af2a8b - languageName: node - linkType: hard - -"storybook@npm:^8.3.5": - version: 8.6.12 - resolution: "storybook@npm:8.6.12" - dependencies: - "@storybook/core": "npm:8.6.12" - peerDependencies: - prettier: ^2 || ^3 - peerDependenciesMeta: - prettier: - optional: true - bin: - getstorybook: ./bin/index.cjs - sb: ./bin/index.cjs - storybook: ./bin/index.cjs - checksum: 10/babd1d086eb02ba25ee659e02e619f7797a6b91028ad74d2da0ab77e72021cd5c2ac4f239668f15156aabf00bd97066a774370dceadf178b1e649bf971160a26 - languageName: node - linkType: hard - -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0": - version: 4.2.3 - resolution: "string-width@npm:4.2.3" - dependencies: - emoji-regex: "npm:^8.0.0" - is-fullwidth-code-point: "npm:^3.0.0" - strip-ansi: "npm:^6.0.1" - checksum: 10/e52c10dc3fbfcd6c3a15f159f54a90024241d0f149cf8aed2982a2d801d2e64df0bf1dc351cf8e95c3319323f9f220c16e740b06faecd53e2462df1d2b5443fb - languageName: node - linkType: hard - -"string-width@npm:^5.0.1, string-width@npm:^5.1.2": - version: 5.1.2 - resolution: "string-width@npm:5.1.2" - dependencies: - eastasianwidth: "npm:^0.2.0" - emoji-regex: "npm:^9.2.2" - strip-ansi: "npm:^7.0.1" - checksum: 10/7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193 - languageName: node - linkType: hard - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": - version: 6.0.1 - resolution: "strip-ansi@npm:6.0.1" - dependencies: - ansi-regex: "npm:^5.0.1" - checksum: 10/ae3b5436d34fadeb6096367626ce987057713c566e1e7768818797e00ac5d62023d0f198c4e681eae9e20701721980b26a64a8f5b91238869592a9c6800719a2 - languageName: node - linkType: hard - -"strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0": - version: 7.1.0 - resolution: "strip-ansi@npm:7.1.0" - dependencies: - ansi-regex: "npm:^6.0.1" - checksum: 10/475f53e9c44375d6e72807284024ac5d668ee1d06010740dec0b9744f2ddf47de8d7151f80e5f6190fc8f384e802fdf9504b76a7e9020c9faee7103623338be2 - languageName: node - linkType: hard - -"strip-bom@npm:^3.0.0": - version: 3.0.0 - resolution: "strip-bom@npm:3.0.0" - checksum: 10/8d50ff27b7ebe5ecc78f1fe1e00fcdff7af014e73cf724b46fb81ef889eeb1015fc5184b64e81a2efe002180f3ba431bdd77e300da5c6685d702780fbf0c8d5b - languageName: node - linkType: hard - -"strip-indent@npm:^3.0.0": - version: 3.0.0 - resolution: "strip-indent@npm:3.0.0" - dependencies: - min-indent: "npm:^1.0.0" - checksum: 10/18f045d57d9d0d90cd16f72b2313d6364fd2cb4bf85b9f593523ad431c8720011a4d5f08b6591c9d580f446e78855c5334a30fb91aa1560f5d9f95ed1b4a0530 - languageName: node - linkType: hard - -"strip-indent@npm:^4.0.0": - version: 4.0.0 - resolution: "strip-indent@npm:4.0.0" - dependencies: - min-indent: "npm:^1.0.1" - checksum: 10/06cbcd93da721c46bc13caeb1c00af93a9b18146a1c95927672d2decab6a25ad83662772417cea9317a2507fb143253ecc23c4415b64f5828cef9b638a744598 - languageName: node - linkType: hard - -"strip-json-comments@npm:^3.1.1": - version: 3.1.1 - resolution: "strip-json-comments@npm:3.1.1" - checksum: 10/492f73e27268f9b1c122733f28ecb0e7e8d8a531a6662efbd08e22cccb3f9475e90a1b82cab06a392f6afae6d2de636f977e231296400d0ec5304ba70f166443 - languageName: node - linkType: hard - -"supports-color@npm:^5.3.0": - version: 5.5.0 - resolution: "supports-color@npm:5.5.0" - dependencies: - has-flag: "npm:^3.0.0" - checksum: 10/5f505c6fa3c6e05873b43af096ddeb22159831597649881aeb8572d6fe3b81e798cc10840d0c9735e0026b250368851b7f77b65e84f4e4daa820a4f69947f55b - languageName: node - linkType: hard - -"supports-color@npm:^7.1.0": - version: 7.2.0 - resolution: "supports-color@npm:7.2.0" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10/c8bb7afd564e3b26b50ca6ee47572c217526a1389fe018d00345856d4a9b08ffbd61fadaf283a87368d94c3dcdb8f5ffe2650a5a65863e21ad2730ca0f05210a - languageName: node - linkType: hard - -"supports-preserve-symlinks-flag@npm:^1.0.0": - version: 1.0.0 - resolution: "supports-preserve-symlinks-flag@npm:1.0.0" - checksum: 10/a9dc19ae2220c952bd2231d08ddeecb1b0328b61e72071ff4000c8384e145cc07c1c0bdb3b5a1cb06e186a7b2790f1dee793418b332f6ddf320de25d9125be7e - languageName: node - linkType: hard - -"tar@npm:^6.1.11, tar@npm:^6.2.1": - version: 6.2.1 - resolution: "tar@npm:6.2.1" - dependencies: - chownr: "npm:^2.0.0" - fs-minipass: "npm:^2.0.0" - minipass: "npm:^5.0.0" - minizlib: "npm:^2.1.1" - mkdirp: "npm:^1.0.3" - yallist: "npm:^4.0.0" - checksum: 10/bfbfbb2861888077fc1130b84029cdc2721efb93d1d1fb80f22a7ac3a98ec6f8972f29e564103bbebf5e97be67ebc356d37fa48dbc4960600a1eb7230fbd1ea0 - languageName: node - linkType: hard - -"tiny-invariant@npm:^1.3.1, tiny-invariant@npm:^1.3.3": - version: 1.3.3 - resolution: "tiny-invariant@npm:1.3.3" - checksum: 10/5e185c8cc2266967984ce3b352a4e57cb89dad5a8abb0dea21468a6ecaa67cd5bb47a3b7a85d08041008644af4f667fb8b6575ba38ba5fb00b3b5068306e59fe - languageName: node - linkType: hard - -"tinyrainbow@npm:^1.2.0": - version: 1.2.0 - resolution: "tinyrainbow@npm:1.2.0" - checksum: 10/2924444db6804355e5ba2b6e586c7f77329d93abdd7257a069a0f4530dff9f16de484e80479094e3f39273462541b003a65ee3a6afc2d12555aa745132deba5d - languageName: node - linkType: hard - -"tinyspy@npm:^3.0.0": - version: 3.0.2 - resolution: "tinyspy@npm:3.0.2" - checksum: 10/5db671b2ff5cd309de650c8c4761ca945459d7204afb1776db9a04fb4efa28a75f08517a8620c01ee32a577748802231ad92f7d5b194dc003ee7f987a2a06337 - languageName: node - linkType: hard - -"to-fast-properties@npm:^2.0.0": - version: 2.0.0 - resolution: "to-fast-properties@npm:2.0.0" - checksum: 10/be2de62fe58ead94e3e592680052683b1ec986c72d589e7b21e5697f8744cdbf48c266fa72f6c15932894c10187b5f54573a3bcf7da0bfd964d5caf23d436168 - languageName: node - linkType: hard - -"to-regex-range@npm:^5.0.1": - version: 5.0.1 - resolution: "to-regex-range@npm:5.0.1" - dependencies: - is-number: "npm:^7.0.0" - checksum: 10/10dda13571e1f5ad37546827e9b6d4252d2e0bc176c24a101252153ef435d83696e2557fe128c4678e4e78f5f01e83711c703eef9814eb12dab028580d45980a - languageName: node - linkType: hard - -"ts-api-utils@npm:^2.0.1": - version: 2.0.1 - resolution: "ts-api-utils@npm:2.0.1" - peerDependencies: - typescript: ">=4.8.4" - checksum: 10/2e68938cd5acad6b5157744215ce10cd097f9f667fd36b5fdd5efdd4b0c51063e855459d835f94f6777bb8a0f334916b6eb5c1eedab8c325feb34baa39238898 - languageName: node - linkType: hard - -"ts-dedent@npm:^2.0.0, ts-dedent@npm:^2.2.0": - version: 2.2.0 - resolution: "ts-dedent@npm:2.2.0" - checksum: 10/93ed8f7878b6d5ed3c08d99b740010eede6bccfe64bce61c5a4da06a2c17d6ddbb80a8c49c2d15251de7594a4f93ffa21dd10e7be75ef66a4dc9951b4a94e2af - languageName: node - linkType: hard - -"tsconfig-paths@npm:^4.2.0": - version: 4.2.0 - resolution: "tsconfig-paths@npm:4.2.0" - dependencies: - json5: "npm:^2.2.2" - minimist: "npm:^1.2.6" - strip-bom: "npm:^3.0.0" - checksum: 10/5e55cc2fb6b800eb72011522e10edefccb45b1f9af055681a51354c9b597d1390c6fa9cc356b8c7529f195ac8a90a78190d563159f3a1eed10e01bbd4d01a8ab - languageName: node - linkType: hard - -"tslib@npm:^1.8.1": - version: 1.14.1 - resolution: "tslib@npm:1.14.1" - checksum: 10/7dbf34e6f55c6492637adb81b555af5e3b4f9cc6b998fb440dac82d3b42bdc91560a35a5fb75e20e24a076c651438234da6743d139e4feabf0783f3cdfe1dddb - languageName: node - linkType: hard - -"tslib@npm:^2.0.1": - version: 2.8.0 - resolution: "tslib@npm:2.8.0" - checksum: 10/1bc7c43937477059b4d26f2dbde7e49ef0fb4f38f3014e0603eaea76d6a885742c8b1762af45949145e5e7408a736d20ded949da99dabc8ccba1fc5531d2d927 - languageName: node - linkType: hard - -"tsutils@npm:^3.21.0": - version: 3.21.0 - resolution: "tsutils@npm:3.21.0" - dependencies: - tslib: "npm:^1.8.1" - peerDependencies: - typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - checksum: 10/ea036bec1dd024e309939ffd49fda7a351c0e87a1b8eb049570dd119d447250e2c56e0e6c00554e8205760e7417793fdebff752a46e573fbe07d4f375502a5b2 - languageName: node - linkType: hard - -"tween-functions@npm:^1.2.0": - version: 1.2.0 - resolution: "tween-functions@npm:1.2.0" - checksum: 10/f145f39187aacfe6e3c6bfe8452be4061a569b8e1e75c28169c55b7cdf519daa1877c79a8a2cdc902b68f49b67b8478f34818ff02529d27ae5aa0545e7fbdc06 - languageName: node - linkType: hard - -"type-check@npm:^0.4.0, type-check@npm:~0.4.0": - version: 0.4.0 - resolution: "type-check@npm:0.4.0" - dependencies: - prelude-ls: "npm:^1.2.1" - checksum: 10/14687776479d048e3c1dbfe58a2409e00367810d6960c0f619b33793271ff2a27f81b52461f14a162f1f89a9b1d8da1b237fc7c99b0e1fdcec28ec63a86b1fec - languageName: node - linkType: hard - -"type-fest@npm:^2.19.0": - version: 2.19.0 - resolution: "type-fest@npm:2.19.0" - checksum: 10/7bf9e8fdf34f92c8bb364c0af14ca875fac7e0183f2985498b77be129dc1b3b1ad0a6b3281580f19e48c6105c037fb966ad9934520c69c6434d17fd0af4eed78 - languageName: node - linkType: hard - -"typescript-eslint@npm:^8.7.0": - version: 8.26.0 - resolution: "typescript-eslint@npm:8.26.0" - dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.26.0" - "@typescript-eslint/parser": "npm:8.26.0" - "@typescript-eslint/utils": "npm:8.26.0" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10/e9bcaf43932baed4e1af52715def5a6508af9eaac271c3dd8638734aed8e8e0681eb647447ae2565682d6d0bcc52b06722934eafa090259f2feff38d35f2e66c - languageName: node - linkType: hard - -"typescript@npm:^5.5.3": - version: 5.8.3 - resolution: "typescript@npm:5.8.3" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10/65c40944c51b513b0172c6710ee62e951b70af6f75d5a5da745cb7fab132c09ae27ffdf7838996e3ed603bb015dadd099006658046941bd0ba30340cc563ae92 - languageName: node - linkType: hard - -"typescript@patch:typescript@npm%3A^5.5.3#optional!builtin<compat/typescript>": - version: 5.8.3 - resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin<compat/typescript>::version=5.8.3&hash=5786d5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10/b9b1e73dabac5dc730c041325dbd9c99467c1b0d239f1b74ec3b90d831384af3e2ba973946232df670519147eb51a2c20f6f96163cea2b359f03de1e2091cc4f - languageName: node - linkType: hard - -"unique-filename@npm:^3.0.0": - version: 3.0.0 - resolution: "unique-filename@npm:3.0.0" - dependencies: - unique-slug: "npm:^4.0.0" - checksum: 10/8e2f59b356cb2e54aab14ff98a51ac6c45781d15ceaab6d4f1c2228b780193dc70fae4463ce9e1df4479cb9d3304d7c2043a3fb905bdeca71cc7e8ce27e063df - languageName: node - linkType: hard - -"unique-slug@npm:^4.0.0": - version: 4.0.0 - resolution: "unique-slug@npm:4.0.0" - dependencies: - imurmurhash: "npm:^0.1.4" - checksum: 10/40912a8963fc02fb8b600cf50197df4a275c602c60de4cac4f75879d3c48558cfac48de08a25cc10df8112161f7180b3bbb4d662aadb711568602f9eddee54f0 - languageName: node - linkType: hard - -"universalify@npm:^2.0.0": - version: 2.0.1 - resolution: "universalify@npm:2.0.1" - checksum: 10/ecd8469fe0db28e7de9e5289d32bd1b6ba8f7183db34f3bfc4ca53c49891c2d6aa05f3fb3936a81285a905cc509fb641a0c3fc131ec786167eff41236ae32e60 - languageName: node - linkType: hard - -"unplugin@npm:^1.3.1": - version: 1.14.1 - resolution: "unplugin@npm:1.14.1" - dependencies: - acorn: "npm:^8.12.1" - webpack-virtual-modules: "npm:^0.6.2" - peerDependencies: - webpack-sources: ^3 - peerDependenciesMeta: - webpack-sources: - optional: true - checksum: 10/ad82ec5b8de5ae4fb7d24f8ed7d71071e15855d335365d7ab6f2e074d5d666589dd52e9f2a16017da19d7c43f60e50e09bc529420bf9f29ac7c90cc3cf13ef28 - languageName: node - linkType: hard - -"update-browserslist-db@npm:^1.1.0": - version: 1.1.1 - resolution: "update-browserslist-db@npm:1.1.1" - dependencies: - escalade: "npm:^3.2.0" - picocolors: "npm:^1.1.0" - peerDependencies: - browserslist: ">= 4.21.0" - bin: - update-browserslist-db: cli.js - checksum: 10/7678dd8609750588d01aa7460e8eddf2ff9d16c2a52fb1811190e0d056390f1fdffd94db3cf8fb209cf634ab4fa9407886338711c71cc6ccade5eeb22b093734 - languageName: node - linkType: hard - -"uri-js@npm:^4.2.2": - version: 4.4.1 - resolution: "uri-js@npm:4.4.1" - dependencies: - punycode: "npm:^2.1.0" - checksum: 10/b271ca7e3d46b7160222e3afa3e531505161c9a4e097febae9664e4b59912f4cbe94861361a4175edac3a03fee99d91e44b6a58c17a634bc5a664b19fc76fbcb - languageName: node - linkType: hard - -"util@npm:^0.12.5": - version: 0.12.5 - resolution: "util@npm:0.12.5" - dependencies: - inherits: "npm:^2.0.3" - is-arguments: "npm:^1.0.4" - is-generator-function: "npm:^1.0.7" - is-typed-array: "npm:^1.1.3" - which-typed-array: "npm:^1.1.2" - checksum: 10/61a10de7753353dd4d744c917f74cdd7d21b8b46379c1e48e1c4fd8e83f8190e6bd9978fc4e5102ab6a10ebda6019d1b36572fa4a325e175ec8b789a121f6147 - languageName: node - linkType: hard - -"uuid@npm:^9.0.0": - version: 9.0.1 - resolution: "uuid@npm:9.0.1" - bin: - uuid: dist/bin/uuid - checksum: 10/9d0b6adb72b736e36f2b1b53da0d559125ba3e39d913b6072f6f033e0c87835b414f0836b45bcfaf2bdf698f92297fea1c3cc19b0b258bc182c9c43cc0fab9f2 - languageName: node - linkType: hard - -"vite@npm:^5.4.8": - version: 5.4.19 - resolution: "vite@npm:5.4.19" - dependencies: - esbuild: "npm:^0.21.3" - fsevents: "npm:~2.3.3" - postcss: "npm:^8.4.43" - rollup: "npm:^4.20.0" - peerDependencies: - "@types/node": ^18.0.0 || >=20.0.0 - less: "*" - lightningcss: ^1.21.0 - sass: "*" - sass-embedded: "*" - stylus: "*" - sugarss: "*" - terser: ^5.4.0 - dependenciesMeta: - fsevents: - optional: true - peerDependenciesMeta: - "@types/node": - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - bin: - vite: bin/vite.js - checksum: 10/27900c87ec6f84967ba12bd4a24c2b9182c3ddad278a13a1c7736ccc4ac7e325f3fbdc11836e2906857140cc89c55121cb0746d4100046e797e21e1e7570d9c4 - languageName: node - linkType: hard - -"webpack-virtual-modules@npm:^0.6.2": - version: 0.6.2 - resolution: "webpack-virtual-modules@npm:0.6.2" - checksum: 10/d9a0d035f7ec0c7f1055aaf88bfe48b7f96458043916a1b2926d9012fd61de3810a6b768e31a8cd4b3c84a9b6d55824361a9dd20aaf9f5ccfb6f017af216a178 - languageName: node - linkType: hard - -"which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.2": - version: 1.1.15 - resolution: "which-typed-array@npm:1.1.15" - dependencies: - available-typed-arrays: "npm:^1.0.7" - call-bind: "npm:^1.0.7" - for-each: "npm:^0.3.3" - gopd: "npm:^1.0.1" - has-tostringtag: "npm:^1.0.2" - checksum: 10/c3b6a99beadc971baa53c3ee5b749f2b9bdfa3b3b9a70650dd8511a48b61d877288b498d424712e9991d16019633086bd8b5923369460d93463c5825fa36c448 - languageName: node - linkType: hard - -"which@npm:^2.0.1": - version: 2.0.2 - resolution: "which@npm:2.0.2" - dependencies: - isexe: "npm:^2.0.0" - bin: - node-which: ./bin/node-which - checksum: 10/4782f8a1d6b8fc12c65e968fea49f59752bf6302dc43036c3bf87da718a80710f61a062516e9764c70008b487929a73546125570acea95c5b5dcc8ac3052c70f - languageName: node - linkType: hard - -"which@npm:^4.0.0": - version: 4.0.0 - resolution: "which@npm:4.0.0" - dependencies: - isexe: "npm:^3.1.1" - bin: - node-which: bin/which.js - checksum: 10/f17e84c042592c21e23c8195108cff18c64050b9efb8459589116999ea9da6dd1509e6a1bac3aeebefd137be00fabbb61b5c2bc0aa0f8526f32b58ee2f545651 - languageName: node - linkType: hard - -"word-wrap@npm:^1.2.5": - version: 1.2.5 - resolution: "word-wrap@npm:1.2.5" - checksum: 10/1ec6f6089f205f83037be10d0c4b34c9183b0b63fca0834a5b3cee55dd321429d73d40bb44c8fc8471b5203d6e8f8275717f49a8ff4b2b0ab41d7e1b563e0854 - languageName: node - linkType: hard - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version: 7.0.0 - resolution: "wrap-ansi@npm:7.0.0" - dependencies: - ansi-styles: "npm:^4.0.0" - string-width: "npm:^4.1.0" - strip-ansi: "npm:^6.0.0" - checksum: 10/cebdaeca3a6880da410f75209e68cd05428580de5ad24535f22696d7d9cab134d1f8498599f344c3cf0fb37c1715807a183778d8c648d6cc0cb5ff2bb4236540 - languageName: node - linkType: hard - -"wrap-ansi@npm:^8.1.0": - version: 8.1.0 - resolution: "wrap-ansi@npm:8.1.0" - dependencies: - ansi-styles: "npm:^6.1.0" - string-width: "npm:^5.0.1" - strip-ansi: "npm:^7.0.1" - checksum: 10/7b1e4b35e9bb2312d2ee9ee7dc95b8cb5f8b4b5a89f7dde5543fe66c1e3715663094defa50d75454ac900bd210f702d575f15f3f17fa9ec0291806d2578d1ddf - languageName: node - linkType: hard - -"ws@npm:^8.2.3": - version: 8.18.0 - resolution: "ws@npm:8.18.0" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ">=5.0.2" - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 10/70dfe53f23ff4368d46e4c0b1d4ca734db2c4149c6f68bc62cb16fc21f753c47b35fcc6e582f3bdfba0eaeb1c488cddab3c2255755a5c3eecb251431e42b3ff6 - languageName: node - linkType: hard - -"yallist@npm:^3.0.2": - version: 3.1.1 - resolution: "yallist@npm:3.1.1" - checksum: 10/9af0a4329c3c6b779ac4736c69fae4190ac03029fa27c1aef4e6bcc92119b73dea6fe5db5fe881fb0ce2a0e9539a42cdf60c7c21eda04d1a0b8c082e38509efb - languageName: node - linkType: hard - -"yallist@npm:^4.0.0": - version: 4.0.0 - resolution: "yallist@npm:4.0.0" - checksum: 10/4cb02b42b8a93b5cf50caf5d8e9beb409400a8a4d85e83bb0685c1457e9ac0b7a00819e9f5991ac25ffabb56a78e2f017c1acc010b3a1babfe6de690ba531abd - languageName: node - linkType: hard - -"yocto-queue@npm:^0.1.0": - version: 0.1.0 - resolution: "yocto-queue@npm:0.1.0" - checksum: 10/f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 - languageName: node - linkType: hard diff --git a/yarn.lock b/yarn.lock index 98518c53d6..a3334417ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -203,14 +203,24 @@ __metadata: languageName: node linkType: hard -"@apidevtools/json-schema-ref-parser@npm:^11.0.0, @apidevtools/json-schema-ref-parser@npm:^11.7.0": - version: 11.7.3 - resolution: "@apidevtools/json-schema-ref-parser@npm:11.7.3" +"@apidevtools/json-schema-ref-parser@npm:^11.0.0": + version: 11.9.3 + resolution: "@apidevtools/json-schema-ref-parser@npm:11.9.3" dependencies: "@jsdevtools/ono": "npm:^7.1.3" "@types/json-schema": "npm:^7.0.15" js-yaml: "npm:^4.1.0" - checksum: 10/ab496d84f8deca4e5e3ab4adb28825fb1e90655561cf7b70b6dc7b8cf95e49ea9fa3440c265d1d115a43e673b94a79005af05b5d76f77a8934ed1c2fae6762c9 + checksum: 10/3d3618dbb611d1296b99bdee4ff0dde664dad47632d30e0310c6d10de8081f6378ccb58329ea4e03103eca9347d5143671d03f0527b1c3f0916d95f8c09215e2 + languageName: node + linkType: hard + +"@apidevtools/json-schema-ref-parser@npm:^14.0.3": + version: 14.1.1 + resolution: "@apidevtools/json-schema-ref-parser@npm:14.1.1" + dependencies: + "@types/json-schema": "npm:^7.0.15" + js-yaml: "npm:^4.1.0" + checksum: 10/c4332faf164c19764838e33cd8a7ef7c233ecaf3348e7c4470ef92d0c8c1c7ec2dbb1ce535d569bba52a4b1ef104d3f747da11f7a0f0bafdaee11b54ec826b49 languageName: node linkType: hard @@ -324,25 +334,25 @@ __metadata: languageName: node linkType: hard -"@asyncapi/protobuf-schema-parser@npm:^3.4.0": - version: 3.4.0 - resolution: "@asyncapi/protobuf-schema-parser@npm:3.4.0" +"@asyncapi/protobuf-schema-parser@npm:^3.5.1": + version: 3.5.1 + resolution: "@asyncapi/protobuf-schema-parser@npm:3.5.1" dependencies: "@asyncapi/parser": "npm:^3.4.0" "@types/protocol-buffers-schema": "npm:^3.4.3" protobufjs: "npm:^7.4.0" - checksum: 10/e04ab346e0145d735a4d4238d7adc74ea469b588be10507363e6e216822b3501b786f2c33d0451ace20ac9e6dace411458b6b8045388bed0300278e7fa2e3822 + checksum: 10/dbef0c14080f0894e2d2ca1f5f233485e3cce3f37bf82e3412be50322bd16366812ab933f35e38c35ee453a29ae20e2d8a811a6921fc5631cb5caf0b59fd839a languageName: node linkType: hard "@asyncapi/react-component@npm:^2.3.3": - version: 2.5.1 - resolution: "@asyncapi/react-component@npm:2.5.1" + version: 2.6.3 + resolution: "@asyncapi/react-component@npm:2.6.3" dependencies: "@asyncapi/avro-schema-parser": "npm:^3.0.24" "@asyncapi/openapi-schema-parser": "npm:^3.0.24" "@asyncapi/parser": "npm:^3.3.0" - "@asyncapi/protobuf-schema-parser": "npm:^3.4.0" + "@asyncapi/protobuf-schema-parser": "npm:^3.5.1" highlight.js: "npm:^10.7.2" isomorphic-dompurify: "npm:^2.14.0" marked: "npm:^4.0.14" @@ -352,7 +362,7 @@ __metadata: peerDependencies: react: ">=18.0.0" react-dom: ">=18.0.0" - checksum: 10/590b9e8e4326a803149bd5c99662cd75acf4e469cb3c829588f2b8a2b2b85bf3d342a81daccf3f974d9fbdb89e30a6bfed2d870826abaf7366b1017389dbd5c5 + checksum: 10/7105385f8f806200638f10b799ff1a5d1838041d20d14c6e64b1e1a411933727b91cd3957c2057bc7946524be01c8ab7bb03946886534b15f4767c277da38445 languageName: node linkType: hard @@ -1886,61 +1896,61 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.24.2, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0, @babel/code-frame@npm:^7.26.2, @babel/code-frame@npm:^7.8.3": - version: 7.26.2 - resolution: "@babel/code-frame@npm:7.26.2" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.24.2, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.8.3": + version: 7.27.1 + resolution: "@babel/code-frame@npm:7.27.1" dependencies: - "@babel/helper-validator-identifier": "npm:^7.25.9" + "@babel/helper-validator-identifier": "npm:^7.27.1" js-tokens: "npm:^4.0.0" - picocolors: "npm:^1.0.0" - checksum: 10/db2c2122af79d31ca916755331bb4bac96feb2b334cdaca5097a6b467fdd41963b89b14b6836a14f083de7ff887fc78fa1b3c10b14e743d33e12dbfe5ee3d223 + picocolors: "npm:^1.1.1" + checksum: 10/721b8a6e360a1fa0f1c9fe7351ae6c874828e119183688b533c477aa378f1010f37cc9afbfc4722c686d1f5cdd00da02eab4ba7278a0c504fa0d7a321dcd4fdf languageName: node linkType: hard -"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.22.9, @babel/compat-data@npm:^7.25.9": - version: 7.26.2 - resolution: "@babel/compat-data@npm:7.26.2" - checksum: 10/ed9eed6b62ce803ef4a320b1dac76b0302abbb29c49dddf96f3e3207d9717eb34e299a8651bb1582e9c3346ead74b6d595ffced5b3dae718afa08b18741f8402 +"@babel/compat-data@npm:^7.27.2": + version: 7.28.0 + resolution: "@babel/compat-data@npm:7.28.0" + checksum: 10/1a56a5e48c7259f72cc4329adeca38e72fd650ea09de267ea4aa070e3da91e5c265313b6656823fff77d64a8bab9554f276c66dade9355fdc0d8604deea015aa languageName: node linkType: hard -"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.19.6, @babel/core@npm:^7.24.7": - version: 7.26.0 - resolution: "@babel/core@npm:7.26.0" +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.24.7": + version: 7.28.0 + resolution: "@babel/core@npm:7.28.0" dependencies: "@ampproject/remapping": "npm:^2.2.0" - "@babel/code-frame": "npm:^7.26.0" - "@babel/generator": "npm:^7.26.0" - "@babel/helper-compilation-targets": "npm:^7.25.9" - "@babel/helper-module-transforms": "npm:^7.26.0" - "@babel/helpers": "npm:^7.26.0" - "@babel/parser": "npm:^7.26.0" - "@babel/template": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" - "@babel/types": "npm:^7.26.0" + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.0" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-module-transforms": "npm:^7.27.3" + "@babel/helpers": "npm:^7.27.6" + "@babel/parser": "npm:^7.28.0" + "@babel/template": "npm:^7.27.2" + "@babel/traverse": "npm:^7.28.0" + "@babel/types": "npm:^7.28.0" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10/65767bfdb1f02e80d3af4f138066670ef8fdd12293de85ef151758a901c191c797e86d2e99b11c4cdfca33c72385ecaf38bbd7fa692791ec44c77763496b9b93 + checksum: 10/1c86eec8d76053f7b1c5f65296d51d7b8ac00f80d169ff76d3cd2e7d85ab222eb100d40cc3314f41b96c8cc06e9abab21c63d246161f0f3f70ef14c958419c33 languageName: node linkType: hard -"@babel/generator@npm:^7.25.9, @babel/generator@npm:^7.26.0, @babel/generator@npm:^7.7.2": - version: 7.26.2 - resolution: "@babel/generator@npm:7.26.2" +"@babel/generator@npm:^7.28.0, @babel/generator@npm:^7.7.2": + version: 7.28.0 + resolution: "@babel/generator@npm:7.28.0" dependencies: - "@babel/parser": "npm:^7.26.2" - "@babel/types": "npm:^7.26.0" - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.25" + "@babel/parser": "npm:^7.28.0" + "@babel/types": "npm:^7.28.0" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" jsesc: "npm:^3.0.2" - checksum: 10/71ace82b5b07a554846a003624bfab93275ccf73cdb9f1a37a4c1094bf9dc94bb677c67e8b8c939dbd6c5f0eda2e8f268aa2b0d9c3b9511072565660e717e045 + checksum: 10/064c5ba4c07ecd7600377bd0022d5f6bdb3b35e9ff78d9378f6bd1e656467ca902c091647222ab2f0d2967f6d6c0ca33157d37dd9b1c51926c9b0e1527ab9b92 languageName: node linkType: hard -"@babel/helper-annotate-as-pure@npm:^7.16.0, @babel/helper-annotate-as-pure@npm:^7.22.5, @babel/helper-annotate-as-pure@npm:^7.24.7": +"@babel/helper-annotate-as-pure@npm:^7.16.0, @babel/helper-annotate-as-pure@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-annotate-as-pure@npm:7.24.7" dependencies: @@ -1949,29 +1959,20 @@ __metadata: languageName: node linkType: hard -"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.22.5": - version: 7.22.10 - resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.22.10" +"@babel/helper-compilation-targets@npm:^7.27.2": + version: 7.27.2 + resolution: "@babel/helper-compilation-targets@npm:7.27.2" dependencies: - "@babel/types": "npm:^7.22.10" - checksum: 10/6de4a1f30e6244f9a1efdfcbe89df39923df3d165be606da5ad11319f8a11c12c72c60d9dc5fb696363281e2d6f741444c1af51f525fc7cf1d2a90fe23370bd9 - languageName: node - linkType: hard - -"@babel/helper-compilation-targets@npm:^7.22.10, @babel/helper-compilation-targets@npm:^7.22.5, @babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-compilation-targets@npm:7.25.9" - dependencies: - "@babel/compat-data": "npm:^7.25.9" - "@babel/helper-validator-option": "npm:^7.25.9" + "@babel/compat-data": "npm:^7.27.2" + "@babel/helper-validator-option": "npm:^7.27.1" browserslist: "npm:^4.24.0" lru-cache: "npm:^5.1.1" semver: "npm:^6.3.1" - checksum: 10/8053fbfc21e8297ab55c8e7f9f119e4809fa7e505268691e1bedc2cf5e7a5a7de8c60ad13da2515378621b7601c42e101d2d679904da395fa3806a1edef6b92e + checksum: 10/bd53c30a7477049db04b655d11f4c3500aea3bcbc2497cf02161de2ecf994fec7c098aabbcebe210ffabc2ecbdb1e3ffad23fb4d3f18723b814f423ea1749fe8 languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.22.11, @babel/helper-create-class-features-plugin@npm:^7.25.0, @babel/helper-create-class-features-plugin@npm:^7.25.4": +"@babel/helper-create-class-features-plugin@npm:^7.25.0, @babel/helper-create-class-features-plugin@npm:^7.25.4": version: 7.25.4 resolution: "@babel/helper-create-class-features-plugin@npm:7.25.4" dependencies: @@ -1988,59 +1989,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.22.5": - version: 7.22.9 - resolution: "@babel/helper-create-regexp-features-plugin@npm:7.22.9" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.22.5" - regexpu-core: "npm:^5.3.1" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/6f3475a7661bc34527201c07eeeec3077c8adab0ed74bff728dc479da6c74bb393b6121ddf590ef1671f3f508fab3c7792a5ada65672665d84db4556daebd210 - languageName: node - linkType: hard - -"@babel/helper-define-polyfill-provider@npm:^0.4.2": - version: 0.4.2 - resolution: "@babel/helper-define-polyfill-provider@npm:0.4.2" - dependencies: - "@babel/helper-compilation-targets": "npm:^7.22.6" - "@babel/helper-plugin-utils": "npm:^7.22.5" - debug: "npm:^4.1.1" - lodash.debounce: "npm:^4.0.8" - resolve: "npm:^1.14.2" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/6383a34af4048957e46366fa7e6228b61e140955a707f8af7b69c26b2b780880db164d08b6de9420f6ec5a0ee01eb23aa5d78a4b141f2b65b3670e71906471bf - languageName: node - linkType: hard - -"@babel/helper-environment-visitor@npm:^7.22.5": - version: 7.24.7 - resolution: "@babel/helper-environment-visitor@npm:7.24.7" - dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10/079d86e65701b29ebc10baf6ed548d17c19b808a07aa6885cc141b690a78581b180ee92b580d755361dc3b16adf975b2d2058b8ce6c86675fcaf43cf22f2f7c6 - languageName: node - linkType: hard - -"@babel/helper-function-name@npm:^7.22.5": - version: 7.24.7 - resolution: "@babel/helper-function-name@npm:7.24.7" - dependencies: - "@babel/template": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10/2ceb3d9b2b35a0fc4100fc06ed7be3bc38f03ff0bf128ff0edbc0cc7dd842967b1496fc70b5c616c747d7711c2b87e7d025c8888f48740631d6148a9d3614f85 - languageName: node - linkType: hard - -"@babel/helper-hoist-variables@npm:^7.22.5": - version: 7.24.7 - resolution: "@babel/helper-hoist-variables@npm:7.24.7" - dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10/6cfdcf2289cd12185dcdbdf2435fa8d3447b797ac75851166de9fc8503e2fd0021db6baf8dfbecad3753e582c08e6a3f805c8d00cbed756060a877d705bd8d8d +"@babel/helper-globals@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/helper-globals@npm:7.28.0" + checksum: 10/91445f7edfde9b65dcac47f4f858f68dc1661bf73332060ab67ad7cc7b313421099a2bfc4bda30c3db3842cfa1e86fffbb0d7b2c5205a177d91b22c8d7d9cb47 languageName: node linkType: hard @@ -2054,30 +2006,30 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.0.0, @babel/helper-module-imports@npm:^7.16.0, @babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.22.5, @babel/helper-module-imports@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-module-imports@npm:7.25.9" +"@babel/helper-module-imports@npm:^7.0.0, @babel/helper-module-imports@npm:^7.16.0, @babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-module-imports@npm:7.27.1" dependencies: - "@babel/traverse": "npm:^7.25.9" - "@babel/types": "npm:^7.25.9" - checksum: 10/e090be5dee94dda6cd769972231b21ddfae988acd76b703a480ac0c96f3334557d70a965bf41245d6ee43891e7571a8b400ccf2b2be5803351375d0f4e5bcf08 + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10/58e792ea5d4ae71676e0d03d9fef33e886a09602addc3bd01388a98d87df9fcfd192968feb40ac4aedb7e287ec3d0c17b33e3ecefe002592041a91d8a1998a8d languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.22.5, @babel/helper-module-transforms@npm:^7.22.9, @babel/helper-module-transforms@npm:^7.24.8, @babel/helper-module-transforms@npm:^7.26.0": - version: 7.26.0 - resolution: "@babel/helper-module-transforms@npm:7.26.0" +"@babel/helper-module-transforms@npm:^7.24.8, @babel/helper-module-transforms@npm:^7.27.3": + version: 7.27.3 + resolution: "@babel/helper-module-transforms@npm:7.27.3" dependencies: - "@babel/helper-module-imports": "npm:^7.25.9" - "@babel/helper-validator-identifier": "npm:^7.25.9" - "@babel/traverse": "npm:^7.25.9" + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/traverse": "npm:^7.27.3" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/9841d2a62f61ad52b66a72d08264f23052d533afc4ce07aec2a6202adac0bfe43014c312f94feacb3291f4c5aafe681955610041ece2c276271adce3f570f2f5 + checksum: 10/47abc90ceb181b4bdea9bf1717adf536d1b5e5acb6f6d8a7a4524080318b5ca8a99e6d58677268c596bad71077d1d98834d2c3815f2443e6d3f287962300f15d languageName: node linkType: hard -"@babel/helper-optimise-call-expression@npm:^7.22.5, @babel/helper-optimise-call-expression@npm:^7.24.7": +"@babel/helper-optimise-call-expression@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-optimise-call-expression@npm:7.24.7" dependencies: @@ -2086,27 +2038,14 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.24.8, @babel/helper-plugin-utils@npm:^7.25.9, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.24.8, @babel/helper-plugin-utils@npm:^7.25.9, @babel/helper-plugin-utils@npm:^7.8.0": version: 7.25.9 resolution: "@babel/helper-plugin-utils@npm:7.25.9" checksum: 10/e347d87728b1ab10b6976d46403941c8f9008c045ea6d99997a7ffca7b852dc34b6171380f7b17edf94410e0857ff26f3a53d8618f11d73744db86e8ca9b8c64 languageName: node linkType: hard -"@babel/helper-remap-async-to-generator@npm:^7.22.5, @babel/helper-remap-async-to-generator@npm:^7.22.9": - version: 7.22.9 - resolution: "@babel/helper-remap-async-to-generator@npm:7.22.9" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.22.5" - "@babel/helper-environment-visitor": "npm:^7.22.5" - "@babel/helper-wrap-function": "npm:^7.22.9" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/05538079447829b13512157491cc77f9cf1ea7e1680e15cff0682c3ed9ee162de0c4862ece20a6d6b2df28177a1520bcfe45993fbeccf2747a81795a7c3f6290 - languageName: node - linkType: hard - -"@babel/helper-replace-supers@npm:^7.22.5, @babel/helper-replace-supers@npm:^7.25.0": +"@babel/helper-replace-supers@npm:^7.25.0": version: 7.25.0 resolution: "@babel/helper-replace-supers@npm:7.25.0" dependencies: @@ -2129,7 +2068,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-skip-transparent-expression-wrappers@npm:^7.22.5, @babel/helper-skip-transparent-expression-wrappers@npm:^7.24.7": +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.24.7" dependencies: @@ -2139,54 +2078,34 @@ __metadata: languageName: node linkType: hard -"@babel/helper-split-export-declaration@npm:^7.22.6": - version: 7.24.7 - resolution: "@babel/helper-split-export-declaration@npm:7.24.7" +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10/0ae29cc2005084abdae2966afdb86ed14d41c9c37db02c3693d5022fba9f5d59b011d039380b8e537c34daf117c549f52b452398f576e908fb9db3c7abbb3a00 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.24.7, @babel/helper-validator-identifier@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-identifier@npm:7.27.1" + checksum: 10/75041904d21bdc0cd3b07a8ac90b11d64cd3c881e89cb936fa80edd734bf23c35e6bd1312611e8574c4eab1f3af0f63e8a5894f4699e9cfdf70c06fcf4252320 + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.24.7, @babel/helper-validator-option@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-option@npm:7.27.1" + checksum: 10/db73e6a308092531c629ee5de7f0d04390835b21a263be2644276cb27da2384b64676cab9f22cd8d8dbd854c92b1d7d56fc8517cf0070c35d1c14a8c828b0903 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.27.6": + version: 7.28.2 + resolution: "@babel/helpers@npm:7.28.2" dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10/ff04a3071603c87de0d6ee2540b7291ab36305b329bd047cdbb6cbd7db335a12f9a77af1cf708779f75f13c4d9af46093c00b34432e50b2411872c658d1a2e5e - languageName: node - linkType: hard - -"@babel/helper-string-parser@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-string-parser@npm:7.25.9" - checksum: 10/c28656c52bd48e8c1d9f3e8e68ecafd09d949c57755b0d353739eb4eae7ba4f7e67e92e4036f1cd43378cc1397a2c943ed7bcaf5949b04ab48607def0258b775 - languageName: node - linkType: hard - -"@babel/helper-validator-identifier@npm:^7.22.5, @babel/helper-validator-identifier@npm:^7.24.7, @babel/helper-validator-identifier@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-validator-identifier@npm:7.25.9" - checksum: 10/3f9b649be0c2fd457fa1957b694b4e69532a668866b8a0d81eabfa34ba16dbf3107b39e0e7144c55c3c652bf773ec816af8df4a61273a2bb4eb3145ca9cf478e - languageName: node - linkType: hard - -"@babel/helper-validator-option@npm:^7.22.5, @babel/helper-validator-option@npm:^7.24.7, @babel/helper-validator-option@npm:^7.25.9": - version: 7.25.9 - resolution: "@babel/helper-validator-option@npm:7.25.9" - checksum: 10/9491b2755948ebbdd68f87da907283698e663b5af2d2b1b02a2765761974b1120d5d8d49e9175b167f16f72748ffceec8c9cf62acfbee73f4904507b246e2b3d - languageName: node - linkType: hard - -"@babel/helper-wrap-function@npm:^7.22.9": - version: 7.22.10 - resolution: "@babel/helper-wrap-function@npm:7.22.10" - dependencies: - "@babel/helper-function-name": "npm:^7.22.5" - "@babel/template": "npm:^7.22.5" - "@babel/types": "npm:^7.22.10" - checksum: 10/b59629a983b957b0fa9a8255cb48efea9777d6ef4c8053613c85d5743abc42a3a5c0a00eb24ac29ecd00d5bb81ed066bedd0579b39db4331cdcf821ae2ded5ca - languageName: node - linkType: hard - -"@babel/helpers@npm:^7.26.0": - version: 7.27.0 - resolution: "@babel/helpers@npm:7.27.0" - dependencies: - "@babel/template": "npm:^7.27.0" - "@babel/types": "npm:^7.27.0" - checksum: 10/0dd40ba1e5ba4b72d1763bb381384585a56f21a61a19dc1b9a03381fe8e840207fdaa4da645d14dc028ad768087d41aad46347cc6573bd69d82f597f5a12dc6f + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.28.2" + checksum: 10/09fd7965e83d4777a4331a082677a1a2261cec451bf3307cb0fb62b2d32c83d55fb1cac494a5dab5c6ad9da459883b8d4e49142812b10ef3e36b54022b2de3a4 languageName: node linkType: hard @@ -2202,47 +2121,14 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.25.9, @babel/parser@npm:^7.26.0, @babel/parser@npm:^7.26.2, @babel/parser@npm:^7.27.0": - version: 7.27.0 - resolution: "@babel/parser@npm:7.27.0" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.26.7, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.27.5, @babel/parser@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/parser@npm:7.28.0" dependencies: - "@babel/types": "npm:^7.27.0" + "@babel/types": "npm:^7.28.0" bin: parser: ./bin/babel-parser.js - checksum: 10/0fee9f05c6db753882ca9d10958301493443da9f6986d7020ebd7a696b35886240016899bc0b47d871aea2abcafd64632343719742e87432c8145e0ec2af2a03 - languageName: node - linkType: hard - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/1e353a060fb2cd8f1256d28cd768f16fb02513f905b9b6d656fb0242c96c341a196fa188b27c2701506a6e27515359fbcc1a5ca7fa8b9b530cf88fbd137baefc - languageName: node - linkType: hard - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.22.5" - "@babel/plugin-transform-optional-chaining": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.13.0 - checksum: 10/16e7a5f3bf2f2ac0ca032a70bf0ebd7e886d84dbb712b55c0643c04c495f0f221fbcbca14b5f8f8027fa6c87a3dafae0934022ad2b409384af6c5c356495b7bd - languageName: node - linkType: hard - -"@babel/plugin-proposal-private-property-in-object@npm:7.21.0-placeholder-for-preset-env.2": - version: 7.21.0-placeholder-for-preset-env.2 - resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.21.0-placeholder-for-preset-env.2" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/fab70f399aa869275690ec6c7cedb4ef361d4e8b6f55c3d7b04bfee61d52fb93c87cec2c65d73cddbaca89fb8ef5ec0921fce675c9169d9d51f18305ab34e78a + checksum: 10/2c14a0d2600bae9ab81924df0a85bbd34e427caa099c260743f7c6c12b2042e743e776043a0d1a2573229ae648f7e66a80cfb26fc27e2a9eb59b55932d44c817 languageName: node linkType: hard @@ -2268,7 +2154,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-class-properties@npm:^7.12.13, @babel/plugin-syntax-class-properties@npm:^7.8.3": +"@babel/plugin-syntax-class-properties@npm:^7.8.3": version: 7.12.13 resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" dependencies: @@ -2279,39 +2165,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-class-static-block@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-class-static-block@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/3e80814b5b6d4fe17826093918680a351c2d34398a914ce6e55d8083d72a9bdde4fbaf6a2dcea0e23a03de26dc2917ae3efd603d27099e2b98380345703bf948 - languageName: node - linkType: hard - -"@babel/plugin-syntax-dynamic-import@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-dynamic-import@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/ce307af83cf433d4ec42932329fad25fa73138ab39c7436882ea28742e1c0066626d224e0ad2988724c82644e41601cef607b36194f695cb78a1fcdc959637bd - languageName: node - linkType: hard - -"@babel/plugin-syntax-export-namespace-from@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-export-namespace-from@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.3" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/85740478be5b0de185228e7814451d74ab8ce0a26fcca7613955262a26e99e8e15e9da58f60c754b84515d4c679b590dbd3f2148f0f58025f4ae706f1c5a5d4a - languageName: node - linkType: hard - "@babel/plugin-syntax-flow@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-syntax-flow@npm:7.24.7" @@ -2323,29 +2176,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-assertions@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-syntax-import-assertions@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/2b8b5572db04a7bef1e6cd20debf447e4eef7cb012616f5eceb8fa3e23ce469b8f76ee74fd6d1e158ba17a8f58b0aec579d092fb67c5a30e83ccfbc5754916c1 - languageName: node - linkType: hard - -"@babel/plugin-syntax-import-attributes@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-syntax-import-attributes@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/197b3c5ea2a9649347f033342cb222ab47f4645633695205c0250c6bf2af29e643753b8bb24a2db39948bef08e7c540babfd365591eb57fc110cb30b425ffc47 - languageName: node - linkType: hard - -"@babel/plugin-syntax-import-meta@npm:^7.10.4, @babel/plugin-syntax-import-meta@npm:^7.8.3": +"@babel/plugin-syntax-import-meta@npm:^7.8.3": version: 7.10.4 resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" dependencies: @@ -2367,7 +2198,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.22.5, @babel/plugin-syntax-jsx@npm:^7.24.7, @babel/plugin-syntax-jsx@npm:^7.7.2": +"@babel/plugin-syntax-jsx@npm:^7.24.7, @babel/plugin-syntax-jsx@npm:^7.7.2": version: 7.24.7 resolution: "@babel/plugin-syntax-jsx@npm:7.24.7" dependencies: @@ -2378,7 +2209,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4, @babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": +"@babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": version: 7.10.4 resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" dependencies: @@ -2400,7 +2231,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-numeric-separator@npm:^7.10.4, @babel/plugin-syntax-numeric-separator@npm:^7.8.3": +"@babel/plugin-syntax-numeric-separator@npm:^7.8.3": version: 7.10.4 resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" dependencies: @@ -2444,18 +2275,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-private-property-in-object@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-private-property-in-object@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/b317174783e6e96029b743ccff2a67d63d38756876e7e5d0ba53a322e38d9ca452c13354a57de1ad476b4c066dbae699e0ca157441da611117a47af88985ecda - languageName: node - linkType: hard - -"@babel/plugin-syntax-top-level-await@npm:^7.14.5, @babel/plugin-syntax-top-level-await@npm:^7.8.3": +"@babel/plugin-syntax-top-level-await@npm:^7.8.3": version: 7.14.5 resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" dependencies: @@ -2477,79 +2297,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-unicode-sets-regex@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-syntax-unicode-sets-regex@npm:7.18.6" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.18.6" - "@babel/helper-plugin-utils": "npm:^7.18.6" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/a651d700fe63ff0ddfd7186f4ebc24447ca734f114433139e3c027bc94a900d013cf1ef2e2db8430425ba542e39ae160c3b05f06b59fd4656273a3df97679e9c - languageName: node - linkType: hard - -"@babel/plugin-transform-arrow-functions@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-arrow-functions@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/35abb6c57062802c7ce8bd96b2ef2883e3124370c688bbd67609f7d2453802fb73944df8808f893b6c67de978eb2bcf87bbfe325e46d6f39b5fcb09ece11d01a - languageName: node - linkType: hard - -"@babel/plugin-transform-async-generator-functions@npm:^7.22.10": - version: 7.22.11 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.22.11" - dependencies: - "@babel/helper-environment-visitor": "npm:^7.22.5" - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/helper-remap-async-to-generator": "npm:^7.22.9" - "@babel/plugin-syntax-async-generators": "npm:^7.8.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/f11227a1d2831972a7fe28ed54a618ee251547632dc384b2f291f9d8d6aae1177a68c6bbd7709ab78275fa84e757ae795ec08061d94f6f01826f02a35ee875d4 - languageName: node - linkType: hard - -"@babel/plugin-transform-async-to-generator@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-async-to-generator@npm:7.22.5" - dependencies: - "@babel/helper-module-imports": "npm:^7.22.5" - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/helper-remap-async-to-generator": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/b95f23f99dcb379a9f0a1c2a3bbea3f8dc0e1b16dc1ac8b484fe378370169290a7a63d520959a9ba1232837cf74a80e23f6facbe14fd42a3cda6d3c2d7168e62 - languageName: node - linkType: hard - -"@babel/plugin-transform-block-scoped-functions@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/416b1341858e8ca4e524dee66044735956ced5f478b2c3b9bc11ec2285b0c25d7dbb96d79887169eb938084c95d0a89338c8b2fe70d473bd9dc92e5d9db1732c - languageName: node - linkType: hard - -"@babel/plugin-transform-block-scoping@npm:^7.22.10": - version: 7.22.10 - resolution: "@babel/plugin-transform-block-scoping@npm:7.22.10" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/90ad83677dd26b6a33faade687b1316d44e6cdc77ace1e3b208f88927e8982686cc3ec11cf02b695dd4e1a5a8ba7e6f05d039cc45247741894b35567efe7a97c - languageName: node - linkType: hard - -"@babel/plugin-transform-class-properties@npm:^7.22.5, @babel/plugin-transform-class-properties@npm:^7.24.7": +"@babel/plugin-transform-class-properties@npm:^7.24.7": version: 7.25.4 resolution: "@babel/plugin-transform-class-properties@npm:7.25.4" dependencies: @@ -2561,120 +2309,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-static-block@npm:^7.22.5": - version: 7.22.11 - resolution: "@babel/plugin-transform-class-static-block@npm:7.22.11" - dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.22.11" - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.12.0 - checksum: 10/69f040506fad66f1c6918d288d0e0edbc5c8a07c8b4462c1184ad2f9f08995d68b057126c213871c0853ae0c72afc60ec87492049dfacb20902e32346a448bcb - languageName: node - linkType: hard - -"@babel/plugin-transform-classes@npm:^7.22.6": - version: 7.22.6 - resolution: "@babel/plugin-transform-classes@npm:7.22.6" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.22.5" - "@babel/helper-compilation-targets": "npm:^7.22.6" - "@babel/helper-environment-visitor": "npm:^7.22.5" - "@babel/helper-function-name": "npm:^7.22.5" - "@babel/helper-optimise-call-expression": "npm:^7.22.5" - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/helper-replace-supers": "npm:^7.22.5" - "@babel/helper-split-export-declaration": "npm:^7.22.6" - globals: "npm:^11.1.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/9b2f653d12ade0302f8b01a0f647cdbe5e5874984bf85f65e445fb5f660abe0347dd7e45bebc376aa4e096e607f62af73fc44a7e67765cfbe387b632ec8867f9 - languageName: node - linkType: hard - -"@babel/plugin-transform-computed-properties@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-computed-properties@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/template": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/a3efa8de19e4c52f01a99301d864819a7997a7845044d9cef5b67b0fb1e5e3e610ecc23053a8b5cf8fe40fcad93c15a586eaeffd22b89eeaa038339c37919661 - languageName: node - linkType: hard - -"@babel/plugin-transform-destructuring@npm:^7.22.10": - version: 7.22.10 - resolution: "@babel/plugin-transform-destructuring@npm:7.22.10" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/36cec1b8e3ecfc77753fe31698cc2441678ee4f70cf042202c3589a3b0079b04934f2d7d2f1c3d811ce6e3b2fe68fa1f4b6b6910555250987fce209d841cc686 - languageName: node - linkType: hard - -"@babel/plugin-transform-dotall-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-dotall-regex@npm:7.22.5" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.22.5" - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/409b658d11e3082c8f69e9cdef2d96e4d6d11256f005772425fb230cc48fd05945edbfbcb709dab293a1a2f01f9c8a5bb7b4131e632b23264039d9f95864b453 - languageName: node - linkType: hard - -"@babel/plugin-transform-duplicate-keys@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-duplicate-keys@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/bb1280fbabaab6fab2ede585df34900712698210a3bd413f4df5bae6d8c24be36b496c92722ae676a7a67d060a4624f4d6c23b923485f906bfba8773c69f55b4 - languageName: node - linkType: hard - -"@babel/plugin-transform-dynamic-import@npm:^7.22.5": - version: 7.22.11 - resolution: "@babel/plugin-transform-dynamic-import@npm:7.22.11" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/78fc9c532210bf9e8f231747f542318568ac360ee6c27e80853962c984283c73da3f8f8aebe83c2096090a435b356b092ed85de617a156cbe0729d847632be45 - languageName: node - linkType: hard - -"@babel/plugin-transform-exponentiation-operator@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.22.5" - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor": "npm:^7.22.5" - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/f2d660c1b1d51ad5fec1cd5ad426a52187204068c4158f8c4aa977b31535c61b66898d532603eef21c15756827be8277f724c869b888d560f26d7fe848bb5eae - languageName: node - linkType: hard - -"@babel/plugin-transform-export-namespace-from@npm:^7.22.5": - version: 7.22.11 - resolution: "@babel/plugin-transform-export-namespace-from@npm:7.22.11" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/73af5883a321ed56a4bfd43c8a7de0164faebe619287706896fc6ee2f7a4e69042adaa1338c0b8b4bdb9f7e5fdceb016fb1d40694cb43ca3b8827429e8aac4bf - languageName: node - linkType: hard - "@babel/plugin-transform-flow-strip-types@npm:^7.24.7": version: 7.25.2 resolution: "@babel/plugin-transform-flow-strip-types@npm:7.25.2" @@ -2687,89 +2321,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-for-of@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-for-of@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/07ab9ce49a15a03840937dbbddbf2235e0e6b9af3c1427746fab6aaa667acd92327620f937134922167193ac7aca871d20326b59e7a8b1efd52f22f876348928 - languageName: node - linkType: hard - -"@babel/plugin-transform-function-name@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-function-name@npm:7.22.5" - dependencies: - "@babel/helper-compilation-targets": "npm:^7.22.5" - "@babel/helper-function-name": "npm:^7.22.5" - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/cff3b876357999cb8ae30e439c3ec6b0491a53b0aa6f722920a4675a6dd5b53af97a833051df4b34791fe5b3dd326ccf769d5c8e45b322aa50ee11a660b17845 - languageName: node - linkType: hard - -"@babel/plugin-transform-json-strings@npm:^7.22.5": - version: 7.22.11 - resolution: "@babel/plugin-transform-json-strings@npm:7.22.11" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/plugin-syntax-json-strings": "npm:^7.8.3" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/50665e5979e66358c50e90a26db53c55917f78175127ac2fa05c7888d156d418ffb930ec0a109353db0a7c5f57c756ce01bfc9825d24cbfd2b3ec453f2ed8cba - languageName: node - linkType: hard - -"@babel/plugin-transform-literals@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-literals@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/ec37cc2ffb32667af935ab32fe28f00920ec8a1eb999aa6dc6602f2bebd8ba205a558aeedcdccdebf334381d5c57106c61f52332045730393e73410892a9735b - languageName: node - linkType: hard - -"@babel/plugin-transform-logical-assignment-operators@npm:^7.22.5": - version: 7.22.11 - resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.22.11" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/c664e9798e85afa7f92f07b867682dee7392046181d82f5d21bae6f2ca26dfe9c8375cdc52b7483c3fc09a983c1989f60eff9fbc4f373b0c0a74090553d05739 - languageName: node - linkType: hard - -"@babel/plugin-transform-member-expression-literals@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-member-expression-literals@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/ec4b0e07915ddd4fda0142fd104ee61015c208608a84cfa13643a95d18760b1dc1ceb6c6e0548898b8c49e5959a994e46367260176dbabc4467f729b21868504 - languageName: node - linkType: hard - -"@babel/plugin-transform-modules-amd@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-modules-amd@npm:7.22.5" - dependencies: - "@babel/helper-module-transforms": "npm:^7.22.5" - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/5453f829205f6c918cc74d66946c9bf9544869f961d72a9934b4370049bf72a9b0ac089b64389be5172b217858c5353ec3479a18ab14cebb23329d708f6fc1ab - languageName: node - linkType: hard - -"@babel/plugin-transform-modules-commonjs@npm:^7.22.5, @babel/plugin-transform-modules-commonjs@npm:^7.24.7": +"@babel/plugin-transform-modules-commonjs@npm:^7.24.7": version: 7.24.8 resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.8" dependencies: @@ -2782,56 +2334,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-systemjs@npm:^7.22.5": - version: 7.22.11 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.22.11" - dependencies: - "@babel/helper-hoist-variables": "npm:^7.22.5" - "@babel/helper-module-transforms": "npm:^7.22.9" - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/helper-validator-identifier": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/beccd37144e8c482da83282ee1ac8b605a188b9b3ddc5dbf99425a1a4e470158a3792c91cccdfb19fbd046436c42b63bb7432a60a83f7c3b50999f5f6a18819a - languageName: node - linkType: hard - -"@babel/plugin-transform-modules-umd@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-modules-umd@npm:7.22.5" - dependencies: - "@babel/helper-module-transforms": "npm:^7.22.5" - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/b955d066c68b60c1179bfb0b744e2fad32dbe86d0673bd94637439cfe425d1e3ff579bd47a417233609aac1624f4fe69915bee73e6deb2af6188fda8aaa5db63 - languageName: node - linkType: hard - -"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.22.5" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.22.5" - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/3ee564ddee620c035b928fdc942c5d17e9c4b98329b76f9cefac65c111135d925eb94ed324064cd7556d4f5123beec79abea1d4b97d1c8a2a5c748887a2eb623 - languageName: node - linkType: hard - -"@babel/plugin-transform-new-target@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-new-target@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/6b72112773487a881a1d6ffa680afde08bad699252020e86122180ee7a88854d5da3f15d9bca3331cf2e025df045604494a8208a2e63b486266b07c14e2ffbf3 - languageName: node - linkType: hard - -"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.22.5, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.7": +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.24.7" dependencies: @@ -2843,58 +2346,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-numeric-separator@npm:^7.22.5": - version: 7.22.11 - resolution: "@babel/plugin-transform-numeric-separator@npm:7.22.11" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/af064d06a4a041767ec396a5f258103f64785df290e038bba9f0ef454e6c914f2ac45d862bbdad8fac2c7ad47fa4e95356f29053c60c100a0160b02a995fe2a3 - languageName: node - linkType: hard - -"@babel/plugin-transform-object-rest-spread@npm:^7.22.5": - version: 7.22.11 - resolution: "@babel/plugin-transform-object-rest-spread@npm:7.22.11" - dependencies: - "@babel/compat-data": "npm:^7.22.9" - "@babel/helper-compilation-targets": "npm:^7.22.10" - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" - "@babel/plugin-transform-parameters": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/4ebf1e2be1f9e166f01c4f41b286f20ed4a02b1ef10821650f2ad70903967b1ddccfccdbe6ca00caace78fdaccc8cf52a8964af9751690f60959b5aeec6158b5 - languageName: node - linkType: hard - -"@babel/plugin-transform-object-super@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-object-super@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/helper-replace-supers": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/b71887877d74cb64dbccb5c0324fa67e31171e6a5311991f626650e44a4083e5436a1eaa89da78c0474fb095d4ec322d63ee778b202d33aa2e4194e1ed8e62d7 - languageName: node - linkType: hard - -"@babel/plugin-transform-optional-catch-binding@npm:^7.22.5": - version: 7.22.11 - resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.22.11" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/f17abd90e1de67c84d63afea29c8021c74abb2794d3a6eeafb0bbe7372d3db32aefca386e392116ec63884537a4a2815d090d26264d259bacc08f6e3ed05294c - languageName: node - linkType: hard - -"@babel/plugin-transform-optional-chaining@npm:^7.22.10, @babel/plugin-transform-optional-chaining@npm:^7.22.5, @babel/plugin-transform-optional-chaining@npm:^7.24.7": +"@babel/plugin-transform-optional-chaining@npm:^7.24.7": version: 7.24.8 resolution: "@babel/plugin-transform-optional-chaining@npm:7.24.8" dependencies: @@ -2907,18 +2359,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-parameters@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-parameters@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/86bec14b1a42a3c7059fe7dcbbedcae91e778a6b61e59922560d689fea10a165d89e53c2d9f383ad361b642ce444e183880a88dea39d87c09f2046a534b64304 - languageName: node - linkType: hard - -"@babel/plugin-transform-private-methods@npm:^7.22.5, @babel/plugin-transform-private-methods@npm:^7.24.7": +"@babel/plugin-transform-private-methods@npm:^7.24.7": version: 7.25.4 resolution: "@babel/plugin-transform-private-methods@npm:7.25.4" dependencies: @@ -2930,170 +2371,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-private-property-in-object@npm:^7.22.5": - version: 7.22.11 - resolution: "@babel/plugin-transform-private-property-in-object@npm:7.22.11" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.22.5" - "@babel/helper-create-class-features-plugin": "npm:^7.22.11" - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/b00623d107069c91a164d5cf7486c0929a4ee3023fcddbc8844e21b5e66f369271e1aa51921c7d87b80d9927bc75d63afcfe4d577872457ddb0443a5b86bacca - languageName: node - linkType: hard - -"@babel/plugin-transform-property-literals@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-property-literals@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/796176a3176106f77fcb8cd04eb34a8475ce82d6d03a88db089531b8f0453a2fb8b0c6ec9a52c27948bc0ea478becec449893741fc546dfc3930ab927e3f9f2e - languageName: node - linkType: hard - -"@babel/plugin-transform-react-constant-elements@npm:^7.18.12": - version: 7.22.5 - resolution: "@babel/plugin-transform-react-constant-elements@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/0f2fc4d0a4025975f6cb4e1e80be1fe2e14546d86341beed8dbbbf9357b56908574e89476bd693431966c15b31f9c30f735636232058cf7812ca46b687d053be - languageName: node - linkType: hard - -"@babel/plugin-transform-react-display-name@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-react-display-name@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/a12bfd1e4e93055efca3ace3c34722571bda59d9740dca364d225d9c6e3ca874f134694d21715c42cc63d79efd46db9665bd4a022998767f9245f1e29d5d204d - languageName: node - linkType: hard - -"@babel/plugin-transform-react-jsx-development@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-react-jsx-development@npm:7.22.5" - dependencies: - "@babel/plugin-transform-react-jsx": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/36bc3ff0b96bb0ef4723070a50cfdf2e72cfd903a59eba448f9fe92fea47574d6f22efd99364413719e1f3fb3c51b6c9b2990b87af088f8486a84b2a5f9e4560 - languageName: node - linkType: hard - -"@babel/plugin-transform-react-jsx@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-react-jsx@npm:7.22.5" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.22.5" - "@babel/helper-module-imports": "npm:^7.22.5" - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/plugin-syntax-jsx": "npm:^7.22.5" - "@babel/types": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/6be5db99b170c937c71fbe68dc64804bb041729d2f95b376ab5e7bc51c34a790f28753b14384160e87cabacf5e1b1aa3379a1a430a60b1fd6b031ba58955f5a6 - languageName: node - linkType: hard - -"@babel/plugin-transform-react-pure-annotations@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.22.5" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.22.5" - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/092021c4f404e267002099ec20b3f12dd730cb90b0d83c5feed3dc00dbe43b9c42c795a18e7c6c7d7bddea20c7dd56221b146aec81b37f2e7eb5137331c61120 - languageName: node - linkType: hard - -"@babel/plugin-transform-regenerator@npm:^7.22.10": - version: 7.22.10 - resolution: "@babel/plugin-transform-regenerator@npm:7.22.10" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - regenerator-transform: "npm:^0.15.2" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/e13678d62d6fa96f11cb8b863f00e8693491e7adc88bfca3f2820f80cbac8336e7dec3a596eee6a1c4663b7ececc3564f2cd7fb44ed6d4ce84ac2bb7f39ecc6e - languageName: node - linkType: hard - -"@babel/plugin-transform-reserved-words@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-reserved-words@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/3ffd7dbc425fe8132bfec118b9817572799cab1473113a635d25ab606c1f5a2341a636c04cf6b22df3813320365ed5a965b5eeb3192320a10e4cc2c137bd8bfc - languageName: node - linkType: hard - -"@babel/plugin-transform-shorthand-properties@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-shorthand-properties@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/a5ac902c56ea8effa99f681340ee61bac21094588f7aef0bc01dff98246651702e677552fa6d10e548c4ac22a3ffad047dd2f8c8f0540b68316c2c203e56818b - languageName: node - linkType: hard - -"@babel/plugin-transform-spread@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-spread@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/f9fd247b3fa8953416c8808c124c3a5db5cd697abbf791aae0143a0587fff6b386045f94c62bcd1b6783a1fd275629cc194f25f6c0aafc9f05f12a56fd5f94bf - languageName: node - linkType: hard - -"@babel/plugin-transform-sticky-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-sticky-regex@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/63b2c575e3e7f96c32d52ed45ee098fb7d354b35c2223b8c8e76840b32cc529ee0c0ceb5742fd082e56e91e3d82842a367ce177e82b05039af3d602c9627a729 - languageName: node - linkType: hard - -"@babel/plugin-transform-template-literals@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-template-literals@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/27e9bb030654cb425381c69754be4abe6a7c75b45cd7f962cd8d604b841b2f0fb7b024f2efc1c25cc53f5b16d79d5e8cfc47cacbdaa983895b3aeefa3e7e24ff - languageName: node - linkType: hard - -"@babel/plugin-transform-typeof-symbol@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-typeof-symbol@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/82a53a63ffc3010b689ca9a54e5f53b2718b9f4b4a9818f36f9b7dba234f38a01876680553d2716a645a61920b5e6e4aaf8d4a0064add379b27ca0b403049512 - languageName: node - linkType: hard - "@babel/plugin-transform-typescript@npm:^7.24.7": version: 7.25.2 resolution: "@babel/plugin-transform-typescript@npm:7.25.2" @@ -3109,143 +2386,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-escapes@npm:^7.22.10": - version: 7.22.10 - resolution: "@babel/plugin-transform-unicode-escapes@npm:7.22.10" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/807f40ed1324c8cb107c45358f1903384ca3f0ef1d01c5a3c5c9b271c8d8eec66936a3dcc8d75ddfceea9421420368c2e77ae3adef0a50557e778dfe296bf382 - languageName: node - linkType: hard - -"@babel/plugin-transform-unicode-property-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.22.5" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.22.5" - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/2495e5f663cb388e3d888b4ba3df419ac436a5012144ac170b622ddfc221f9ea9bdba839fa2bc0185cb776b578030666406452ec7791cbf0e7a3d4c88ae9574c - languageName: node - linkType: hard - -"@babel/plugin-transform-unicode-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-unicode-regex@npm:7.22.5" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.22.5" - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/6b5d1404c8c623b0ec9bd436c00d885a17d6a34f3f2597996343ddb9d94f6379705b21582dfd4cec2c47fd34068872e74ab6b9580116c0566b3f9447e2a7fa06 - languageName: node - linkType: hard - -"@babel/plugin-transform-unicode-sets-regex@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.22.5" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.22.5" - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/c042070f980b139547f8b0179efbc049ac5930abec7fc26ed7a41d89a048d8ab17d362200e204b6f71c3c20d6991a0e74415e1a412a49adc8131c2a40c04822e - languageName: node - linkType: hard - -"@babel/preset-env@npm:^7.19.4": - version: 7.22.10 - resolution: "@babel/preset-env@npm:7.22.10" - dependencies: - "@babel/compat-data": "npm:^7.22.9" - "@babel/helper-compilation-targets": "npm:^7.22.10" - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/helper-validator-option": "npm:^7.22.5" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.22.5" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.22.5" - "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-async-generators": "npm:^7.8.4" - "@babel/plugin-syntax-class-properties": "npm:^7.12.13" - "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" - "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" - "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3" - "@babel/plugin-syntax-import-assertions": "npm:^7.22.5" - "@babel/plugin-syntax-import-attributes": "npm:^7.22.5" - "@babel/plugin-syntax-import-meta": "npm:^7.10.4" - "@babel/plugin-syntax-json-strings": "npm:^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" - "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" - "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" - "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" - "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" - "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6" - "@babel/plugin-transform-arrow-functions": "npm:^7.22.5" - "@babel/plugin-transform-async-generator-functions": "npm:^7.22.10" - "@babel/plugin-transform-async-to-generator": "npm:^7.22.5" - "@babel/plugin-transform-block-scoped-functions": "npm:^7.22.5" - "@babel/plugin-transform-block-scoping": "npm:^7.22.10" - "@babel/plugin-transform-class-properties": "npm:^7.22.5" - "@babel/plugin-transform-class-static-block": "npm:^7.22.5" - "@babel/plugin-transform-classes": "npm:^7.22.6" - "@babel/plugin-transform-computed-properties": "npm:^7.22.5" - "@babel/plugin-transform-destructuring": "npm:^7.22.10" - "@babel/plugin-transform-dotall-regex": "npm:^7.22.5" - "@babel/plugin-transform-duplicate-keys": "npm:^7.22.5" - "@babel/plugin-transform-dynamic-import": "npm:^7.22.5" - "@babel/plugin-transform-exponentiation-operator": "npm:^7.22.5" - "@babel/plugin-transform-export-namespace-from": "npm:^7.22.5" - "@babel/plugin-transform-for-of": "npm:^7.22.5" - "@babel/plugin-transform-function-name": "npm:^7.22.5" - "@babel/plugin-transform-json-strings": "npm:^7.22.5" - "@babel/plugin-transform-literals": "npm:^7.22.5" - "@babel/plugin-transform-logical-assignment-operators": "npm:^7.22.5" - "@babel/plugin-transform-member-expression-literals": "npm:^7.22.5" - "@babel/plugin-transform-modules-amd": "npm:^7.22.5" - "@babel/plugin-transform-modules-commonjs": "npm:^7.22.5" - "@babel/plugin-transform-modules-systemjs": "npm:^7.22.5" - "@babel/plugin-transform-modules-umd": "npm:^7.22.5" - "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.22.5" - "@babel/plugin-transform-new-target": "npm:^7.22.5" - "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.22.5" - "@babel/plugin-transform-numeric-separator": "npm:^7.22.5" - "@babel/plugin-transform-object-rest-spread": "npm:^7.22.5" - "@babel/plugin-transform-object-super": "npm:^7.22.5" - "@babel/plugin-transform-optional-catch-binding": "npm:^7.22.5" - "@babel/plugin-transform-optional-chaining": "npm:^7.22.10" - "@babel/plugin-transform-parameters": "npm:^7.22.5" - "@babel/plugin-transform-private-methods": "npm:^7.22.5" - "@babel/plugin-transform-private-property-in-object": "npm:^7.22.5" - "@babel/plugin-transform-property-literals": "npm:^7.22.5" - "@babel/plugin-transform-regenerator": "npm:^7.22.10" - "@babel/plugin-transform-reserved-words": "npm:^7.22.5" - "@babel/plugin-transform-shorthand-properties": "npm:^7.22.5" - "@babel/plugin-transform-spread": "npm:^7.22.5" - "@babel/plugin-transform-sticky-regex": "npm:^7.22.5" - "@babel/plugin-transform-template-literals": "npm:^7.22.5" - "@babel/plugin-transform-typeof-symbol": "npm:^7.22.5" - "@babel/plugin-transform-unicode-escapes": "npm:^7.22.10" - "@babel/plugin-transform-unicode-property-regex": "npm:^7.22.5" - "@babel/plugin-transform-unicode-regex": "npm:^7.22.5" - "@babel/plugin-transform-unicode-sets-regex": "npm:^7.22.5" - "@babel/preset-modules": "npm:0.1.6-no-external-plugins" - "@babel/types": "npm:^7.22.10" - babel-plugin-polyfill-corejs2: "npm:^0.4.5" - babel-plugin-polyfill-corejs3: "npm:^0.8.3" - babel-plugin-polyfill-regenerator: "npm:^0.5.2" - core-js-compat: "npm:^3.31.0" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/887cc41e6df54342ffb41b42248f584b2f220e653fc70c683e3d7cc1aa6964b9694d7d6846694647e681f440cd7f2e4454c8a9ee5bce3f523c2e22a99278f2fd - languageName: node - linkType: hard - "@babel/preset-flow@npm:^7.24.7": version: 7.24.7 resolution: "@babel/preset-flow@npm:7.24.7" @@ -3259,36 +2399,7 @@ __metadata: languageName: node linkType: hard -"@babel/preset-modules@npm:0.1.6-no-external-plugins": - version: 0.1.6-no-external-plugins - resolution: "@babel/preset-modules@npm:0.1.6-no-external-plugins" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.0.0" - "@babel/types": "npm:^7.4.4" - esutils: "npm:^2.0.2" - peerDependencies: - "@babel/core": ^7.0.0-0 || ^8.0.0-0 <8.0.0 - checksum: 10/039aba98a697b920d6440c622aaa6104bb6076d65356b29dad4b3e6627ec0354da44f9621bafbeefd052cd4ac4d7f88c9a2ab094efcb50963cb352781d0c6428 - languageName: node - linkType: hard - -"@babel/preset-react@npm:^7.18.6": - version: 7.22.5 - resolution: "@babel/preset-react@npm:7.22.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/helper-validator-option": "npm:^7.22.5" - "@babel/plugin-transform-react-display-name": "npm:^7.22.5" - "@babel/plugin-transform-react-jsx": "npm:^7.22.5" - "@babel/plugin-transform-react-jsx-development": "npm:^7.22.5" - "@babel/plugin-transform-react-pure-annotations": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/7c78b1bca3f2de9cb863b50cf0a5378d5e80b1b2e7573b9daabf09c0517d197aa7ff7fcd7daeb4a51e148743ab5dbd24c7b34422c86a256baf0e10e13400fe98 - languageName: node - linkType: hard - -"@babel/preset-typescript@npm:^7.18.6, @babel/preset-typescript@npm:^7.24.7": +"@babel/preset-typescript@npm:^7.24.7": version: 7.24.7 resolution: "@babel/preset-typescript@npm:7.24.7" dependencies: @@ -3318,13 +2429,6 @@ __metadata: languageName: node linkType: hard -"@babel/regjsgen@npm:^0.8.0": - version: 0.8.0 - resolution: "@babel/regjsgen@npm:0.8.0" - checksum: 10/c57fb730b17332b7572574b74364a77d70faa302a281a62819476fa3b09822974fd75af77aea603ad77378395be64e81f89f0e800bf86cbbf21652d49ce12ee8 - languageName: node - linkType: hard - "@babel/runtime-corejs3@npm:^7.20.7, @babel/runtime-corejs3@npm:^7.22.15, @babel/runtime-corejs3@npm:^7.24.7": version: 7.27.0 resolution: "@babel/runtime-corejs3@npm:7.27.0" @@ -3336,47 +2440,45 @@ __metadata: linkType: hard "@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.26.10, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": - version: 7.27.0 - resolution: "@babel/runtime@npm:7.27.0" - dependencies: - regenerator-runtime: "npm:^0.14.0" - checksum: 10/e6966e03b695feb4c0ac0856a4355231c2580bf9ebd0298f47739f85c0ea658679dd84409daf26378d42c86c1cbe7e33feab709b14e784254b6c441d91606465 + version: 7.28.2 + resolution: "@babel/runtime@npm:7.28.2" + checksum: 10/a0965fbdd6aaa40709290923bbe05e1c4314021f0cef608eb1d69f04f717c41829e50a53d79c4a0f461512b4be9b3c0190dc19387b219bcdaacdd793b2fe1b8a languageName: node linkType: hard -"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.27.0, @babel/template@npm:^7.3.3": - version: 7.27.0 - resolution: "@babel/template@npm:7.27.0" +"@babel/template@npm:^7.27.2, @babel/template@npm:^7.3.3": + version: 7.27.2 + resolution: "@babel/template@npm:7.27.2" dependencies: - "@babel/code-frame": "npm:^7.26.2" - "@babel/parser": "npm:^7.27.0" - "@babel/types": "npm:^7.27.0" - checksum: 10/7159ca1daea287ad34676d45a7146675444d42c7664aca3e617abc9b1d9548c8f377f35a36bb34cf956e1d3610dcb7acfcfe890aebf81880d35f91a7bd273ee5 + "@babel/code-frame": "npm:^7.27.1" + "@babel/parser": "npm:^7.27.2" + "@babel/types": "npm:^7.27.1" + checksum: 10/fed15a84beb0b9340e5f81566600dbee5eccd92e4b9cc42a944359b1aa1082373391d9d5fc3656981dff27233ec935d0bc96453cf507f60a4b079463999244d8 languageName: node linkType: hard -"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.4, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.4.5": - version: 7.25.9 - resolution: "@babel/traverse@npm:7.25.9" +"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.4, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.27.3, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.4.5": + version: 7.28.0 + resolution: "@babel/traverse@npm:7.28.0" dependencies: - "@babel/code-frame": "npm:^7.25.9" - "@babel/generator": "npm:^7.25.9" - "@babel/parser": "npm:^7.25.9" - "@babel/template": "npm:^7.25.9" - "@babel/types": "npm:^7.25.9" + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.0" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.28.0" + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.28.0" debug: "npm:^4.3.1" - globals: "npm:^11.1.0" - checksum: 10/7431614d76d4a053e429208db82f2846a415833f3d9eb2e11ef72eeb3c64dfd71f4a4d983de1a4a047b36165a1f5a64de8ca2a417534cc472005c740ffcb9c6a + checksum: 10/c1c24b12b6cb46241ec5d11ddbd2989d6955c282715cbd8ee91a09fe156b3bdb0b88353ac33329c2992113e3dfb5198f616c834f8805bb3fa85da1f864bec5f3 languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0, @babel/types@npm:^7.27.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": - version: 7.27.0 - resolution: "@babel/types@npm:7.27.0" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.27.1, @babel/types@npm:^7.28.0, @babel/types@npm:^7.28.2, @babel/types@npm:^7.3.3": + version: 7.28.2 + resolution: "@babel/types@npm:7.28.2" dependencies: - "@babel/helper-string-parser": "npm:^7.25.9" - "@babel/helper-validator-identifier": "npm:^7.25.9" - checksum: 10/2c322bce107c8a534dc4a23be60d570e6a4cc7ca2e44d4f0eee08c0b626104eb7e60ab8de03463bc5da1773a2f69f1e6edec1648d648d65461d6520a7f3b0770 + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + checksum: 10/a8de404a2e3109651f346d892dc020ce2c82046068f4ce24de7f487738dfbfa7bd716b35f1dcd6d6c32dde96208dc74a56b7f56a2c0bcb5af0ddc56cbee13533 languageName: node linkType: hard @@ -3387,45 +2489,6 @@ __metadata: languageName: node linkType: hard -"@backstage-community/plugin-puppetdb@npm:^0.6.0": - version: 0.6.0 - resolution: "@backstage-community/plugin-puppetdb@npm:0.6.0" - dependencies: - "@backstage/catalog-model": "npm:^1.7.3" - "@backstage/core-components": "npm:^0.17.1" - "@backstage/core-plugin-api": "npm:^1.10.6" - "@backstage/errors": "npm:^1.2.7" - "@backstage/plugin-catalog-react": "npm:^1.17.0" - "@material-ui/core": "npm:^4.12.2" - "@types/react": "npm:^16.13.1 || ^17.0.0 || ^18.0.0" - react-use: "npm:^17.2.4" - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: 10/562b95a59ea7ecf829c60ee29a8c07eb111517d0d75f876050bbec5958e255b7478a4744dc4f2e899238c5c59d23b6c550095a210b936c385c71afc7e0950e7b - languageName: node - linkType: hard - -"@backstage-community/plugin-stackstorm@npm:^0.1.16": - version: 0.1.20 - resolution: "@backstage-community/plugin-stackstorm@npm:0.1.20" - dependencies: - "@backstage/core-components": "npm:^0.15.1" - "@backstage/core-plugin-api": "npm:^1.10.0" - "@backstage/errors": "npm:^1.2.4" - "@material-ui/core": "npm:^4.12.2" - "@material-ui/icons": "npm:^4.9.1" - "@types/react": "npm:^16.13.1 || ^17.0.0 || ^18.0.0" - react-use: "npm:^17.2.4" - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: 10/5dac0ca61198ba9e12ae06e9f00f5cf3cb2f620b58624e7270fa2539e0fb374f52bde9366cfa0f30dd123768a0a35d4515d2cbd822de92379ba59aaccedba869 - languageName: node - linkType: hard - "@backstage/app-defaults@workspace:^, @backstage/app-defaults@workspace:packages/app-defaults": version: 0.0.0-use.local resolution: "@backstage/app-defaults@workspace:packages/app-defaults" @@ -3469,83 +2532,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/backend-common@npm:^0.25.0": - version: 0.25.0 - resolution: "@backstage/backend-common@npm:0.25.0" - dependencies: - "@aws-sdk/abort-controller": "npm:^3.347.0" - "@aws-sdk/client-codecommit": "npm:^3.350.0" - "@aws-sdk/client-s3": "npm:^3.350.0" - "@aws-sdk/credential-providers": "npm:^3.350.0" - "@aws-sdk/types": "npm:^3.347.0" - "@backstage/backend-dev-utils": "npm:^0.1.5" - "@backstage/backend-plugin-api": "npm:^1.0.0" - "@backstage/cli-common": "npm:^0.1.14" - "@backstage/config": "npm:^1.2.0" - "@backstage/config-loader": "npm:^1.9.1" - "@backstage/errors": "npm:^1.2.4" - "@backstage/integration": "npm:^1.15.0" - "@backstage/integration-aws-node": "npm:^0.1.12" - "@backstage/plugin-auth-node": "npm:^0.5.2" - "@backstage/types": "npm:^1.1.1" - "@google-cloud/storage": "npm:^7.0.0" - "@keyv/memcache": "npm:^1.3.5" - "@keyv/redis": "npm:^2.5.3" - "@kubernetes/client-node": "npm:0.20.0" - "@manypkg/get-packages": "npm:^1.1.3" - "@octokit/rest": "npm:^19.0.3" - "@types/cors": "npm:^2.8.6" - "@types/dockerode": "npm:^3.3.0" - "@types/express": "npm:^4.17.6" - "@types/luxon": "npm:^3.0.0" - "@types/webpack-env": "npm:^1.15.2" - archiver: "npm:^7.0.0" - base64-stream: "npm:^1.0.0" - compression: "npm:^1.7.4" - concat-stream: "npm:^2.0.0" - cors: "npm:^2.8.5" - dockerode: "npm:^4.0.0" - express: "npm:^4.17.1" - express-promise-router: "npm:^4.1.0" - fs-extra: "npm:^11.2.0" - git-url-parse: "npm:^14.0.0" - helmet: "npm:^6.0.0" - isomorphic-git: "npm:^1.23.0" - jose: "npm:^5.0.0" - keyv: "npm:^4.5.2" - knex: "npm:^3.0.0" - lodash: "npm:^4.17.21" - logform: "npm:^2.3.2" - luxon: "npm:^3.0.0" - minimatch: "npm:^9.0.0" - minimist: "npm:^1.2.5" - morgan: "npm:^1.10.0" - mysql2: "npm:^3.0.0" - node-fetch: "npm:^2.7.0" - node-forge: "npm:^1.3.1" - p-limit: "npm:^3.1.0" - path-to-regexp: "npm:^8.0.0" - pg: "npm:^8.11.3" - pg-format: "npm:^1.0.4" - raw-body: "npm:^2.4.1" - selfsigned: "npm:^2.0.0" - stoppable: "npm:^1.1.0" - tar: "npm:^6.1.12" - triple-beam: "npm:^1.4.1" - uuid: "npm:^9.0.0" - winston: "npm:^3.2.1" - winston-transport: "npm:^4.5.0" - yauzl: "npm:^3.0.0" - yn: "npm:^4.0.0" - peerDependencies: - pg-connection-string: ^2.3.0 - peerDependenciesMeta: - pg-connection-string: - optional: true - checksum: 10/4db059f9d095e9eed2c0bb887893be0ede063e0a9c5ddd2b99d6a785f2d8a64780740118bc17dd5905a0ee8b4d16ee617fa12f9fa571429887051aa3911489f8 - languageName: node - linkType: hard - "@backstage/backend-defaults@workspace:^, @backstage/backend-defaults@workspace:packages/backend-defaults": version: 0.0.0-use.local resolution: "@backstage/backend-defaults@workspace:packages/backend-defaults" @@ -3587,13 +2573,14 @@ __metadata: "@types/cors": "npm:^2.8.6" "@types/express": "npm:^4.17.6" "@types/http-errors": "npm:^2.0.0" + "@types/is-glob": "npm:^4.0.2" "@types/node-forge": "npm:^1.3.0" "@types/pg-format": "npm:^1.0.5" "@types/yauzl": "npm:^2.10.0" archiver: "npm:^7.0.0" aws-sdk-client-mock: "npm:^4.0.0" base64-stream: "npm:^1.0.0" - better-sqlite3: "npm:^11.0.0" + better-sqlite3: "npm:^12.0.0" compression: "npm:^1.7.4" concat-stream: "npm:^2.0.0" cookie: "npm:^0.7.0" @@ -3601,10 +2588,12 @@ __metadata: cron: "npm:^3.0.0" express: "npm:^4.17.1" express-promise-router: "npm:^4.1.0" + express-rate-limit: "npm:^7.5.0" fs-extra: "npm:^11.2.0" git-url-parse: "npm:^15.0.0" helmet: "npm:^6.0.0" http-errors: "npm:^2.0.0" + is-glob: "npm:^4.0.3" jose: "npm:^5.0.0" keyv: "npm:^5.2.1" knex: "npm:^3.0.0" @@ -3622,6 +2611,7 @@ __metadata: pg: "npm:^8.11.3" pg-connection-string: "npm:^2.3.0" pg-format: "npm:^1.0.4" + rate-limit-redis: "npm:^4.2.0" raw-body: "npm:^2.4.1" selfsigned: "npm:^2.0.0" supertest: "npm:^7.0.0" @@ -3634,6 +2624,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: @@ -3642,7 +2633,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/backend-dev-utils@npm:^0.1.5, @backstage/backend-dev-utils@workspace:^, @backstage/backend-dev-utils@workspace:packages/backend-dev-utils": +"@backstage/backend-dev-utils@workspace:^, @backstage/backend-dev-utils@workspace:packages/backend-dev-utils": version: 0.0.0-use.local resolution: "@backstage/backend-dev-utils@workspace:packages/backend-dev-utils" dependencies: @@ -3706,7 +2697,7 @@ __metadata: "@types/express-serve-static-core": "npm:^4.17.5" ajv: "npm:^8.16.0" express: "npm:^4.17.1" - express-openapi-validator: "npm:^5.0.4" + express-openapi-validator: "npm:^5.5.8" express-promise-router: "npm:^4.1.0" get-port: "npm:^5.1.1" json-schema-to-ts: "npm:^3.0.0" @@ -3719,25 +2710,6 @@ __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@npm:^1.3.0": - version: 1.3.0 - resolution: "@backstage/backend-plugin-api@npm:1.3.0" - 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.2" - "@backstage/plugin-permission-common": "npm:^0.8.4" - "@backstage/plugin-permission-node": "npm:^0.9.1" - "@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/0c657159bf2d988a09fe066dba811fa1c9237162dffdbbded09a108d906ea580099343b86d1d5e675bf5f59ee09e292f23dd44c6a594c2f70ead8035f512b8b0 - 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" @@ -3752,9 +2724,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 @@ -3770,6 +2745,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-events-node": "workspace:^" + "@backstage/plugin-permission-common": "workspace:^" "@backstage/types": "workspace:^" "@keyv/memcache": "npm:^2.0.1" "@keyv/redis": "npm:^4.0.1" @@ -3780,7 +2756,7 @@ __metadata: "@types/keyv": "npm:^4.2.0" "@types/qs": "npm:^6.9.6" "@types/supertest": "npm:^2.0.8" - better-sqlite3: "npm:^11.0.0" + better-sqlite3: "npm:^12.0.0" cookie: "npm:^0.7.0" express: "npm:^4.17.1" fs-extra: "npm:^11.0.0" @@ -3794,62 +2770,11 @@ __metadata: textextensions: "npm:^5.16.0" uuid: "npm:^11.0.0" yn: "npm:^4.0.0" + zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.20.4" languageName: unknown linkType: soft -"@backstage/canon@workspace:^, @backstage/canon@workspace:packages/canon": - version: 0.0.0-use.local - resolution: "@backstage/canon@workspace:packages/canon" - dependencies: - "@backstage/cli": "workspace:^" - "@base-ui-components/react": "npm:1.0.0-alpha.7" - "@remixicon/react": "npm:^4.6.0" - "@storybook/addon-essentials": "npm:^8.6.12" - "@storybook/addon-interactions": "npm:^8.6.12" - "@storybook/addon-styling-webpack": "npm:^1.0.1" - "@storybook/addon-themes": "npm:^8.6.12" - "@storybook/addon-webpack5-compiler-swc": "npm:^3.0.0" - "@storybook/blocks": "npm:^8.6.12" - "@storybook/react": "npm:^8.6.12" - "@storybook/react-webpack5": "npm:^8.6.12" - "@storybook/test": "npm:^8.6.12" - "@tanstack/react-table": "npm:^8.21.3" - "@types/react": "npm:^18.0.0" - "@types/react-dom": "npm:^18.0.0" - chalk: "npm:^5.4.1" - clsx: "npm:^2.1.1" - eslint-plugin-storybook: "npm:^0.12.0" - glob: "npm:^11.0.1" - globals: "npm:^15.11.0" - lightningcss: "npm:^1.29.1" - mini-css-extract-plugin: "npm:^2.9.2" - react: "npm:^18.0.2" - react-dom: "npm:^18.0.2" - react-router-dom: "npm:^6.3.0" - storybook: "npm:^8.6.12" - 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 - languageName: unknown - linkType: soft - -"@backstage/catalog-client@npm:^1.9.1": - version: 1.9.1 - resolution: "@backstage/catalog-client@npm:1.9.1" - dependencies: - "@backstage/catalog-model": "npm:^1.7.3" - "@backstage/errors": "npm:^1.2.7" - cross-fetch: "npm:^4.0.0" - uri-template: "npm:^2.0.0" - checksum: 10/4e29bb4b6addf4330e298e08500efda6d7e9a416102abdd65dfbedb5e139d8911449f6bb91a90b2d571ddca53cb908c77a4a894fd34689f740e779e66020e13f - 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" @@ -3857,13 +2782,14 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/plugin-catalog-common": "workspace:^" cross-fetch: "npm:^4.0.0" msw: "npm:^1.0.0" uri-template: "npm:^2.0.0" 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@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": version: 0.0.0-use.local resolution: "@backstage/catalog-model@workspace:packages/catalog-model" dependencies: @@ -3878,7 +2804,7 @@ __metadata: languageName: unknown linkType: soft -"@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": +"@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: @@ -3944,18 +2870,12 @@ __metadata: "@rollup/plugin-json": "npm:^6.0.0" "@rollup/plugin-node-resolve": "npm:^15.0.0" "@rollup/plugin-yaml": "npm:^4.0.0" - "@rspack/core": "npm:^1.0.10" - "@rspack/dev-server": "npm:^1.0.9" - "@rspack/plugin-react-refresh": "npm:^1.0.0" + "@rspack/core": "npm:^1.4.11" + "@rspack/dev-server": "npm:^1.1.4" + "@rspack/plugin-react-refresh": "npm:^1.4.3" "@spotify/eslint-config-base": "npm:^15.0.0" "@spotify/eslint-config-react": "npm:^15.0.0" "@spotify/eslint-config-typescript": "npm:^15.0.0" - "@sucrase/webpack-loader": "npm:^2.0.0" - "@svgr/core": "npm:6.5.x" - "@svgr/plugin-jsx": "npm:6.5.x" - "@svgr/plugin-svgo": "npm:6.5.x" - "@svgr/rollup": "npm:6.5.x" - "@svgr/webpack": "npm:6.5.x" "@swc/core": "npm:^1.3.46" "@swc/helpers": "npm:^0.5.0" "@swc/jest": "npm:^0.2.22" @@ -4003,6 +2923,7 @@ __metadata: eslint-plugin-react: "npm:^7.37.2" eslint-plugin-react-hooks: "npm:^5.0.0" eslint-plugin-unused-imports: "npm:^4.1.4" + eslint-rspack-plugin: "npm:^4.2.1" eslint-webpack-plugin: "npm:^4.2.0" express: "npm:^4.17.1" fork-ts-checker-webpack-plugin: "npm:^9.0.0" @@ -4048,10 +2969,11 @@ __metadata: swc-loader: "npm:^0.2.3" tar: "npm:^6.1.12" terser-webpack-plugin: "npm:^5.1.3" + ts-checker-rspack-plugin: "npm:^1.1.5" ts-morph: "npm:^24.0.0" undici: "npm:^7.2.3" util: "npm:^0.12.3" - webpack: "npm:^5.94.0" + webpack: "npm:~5.96.0" webpack-dev-server: "npm:^5.0.0" yaml: "npm:^2.0.0" yargs: "npm:^16.2.0" @@ -4060,19 +2982,36 @@ __metadata: zod: "npm:^3.22.4" zod-validation-error: "npm:^3.4.0" peerDependencies: - "@rspack/core": ^1.0.10 - "@rspack/dev-server": ^1.0.9 - "@rspack/plugin-react-refresh": ^1.0.0 + "@module-federation/enhanced": ^0.9.0 + "@pmmmwh/react-refresh-webpack-plugin": ^0.5.7 + esbuild-loader: ^4.0.0 + eslint-webpack-plugin: ^4.2.0 + fork-ts-checker-webpack-plugin: ^9.0.0 + mini-css-extract-plugin: ^2.4.2 + terser-webpack-plugin: ^5.1.3 + webpack: ~5.96.0 + webpack-dev-server: ^5.0.0 peerDependenciesMeta: - "@rspack/core": + "@module-federation/enhanced": optional: true - "@rspack/dev-server": + "@pmmmwh/react-refresh-webpack-plugin": optional: true - "@rspack/plugin-react-refresh": + esbuild-loader: + optional: true + eslint-webpack-plugin: + optional: true + fork-ts-checker-webpack-plugin: + optional: true + mini-css-extract-plugin: + optional: true + terser-webpack-plugin: + optional: true + webpack: + optional: true + webpack-dev-server: optional: true bin: backstage-cli: bin/backstage-cli - backstage-cli-alpha: bin/backstage-cli-alpha languageName: unknown linkType: soft @@ -4093,29 +3032,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/config-loader@npm:^1.9.1": - version: 1.10.0 - resolution: "@backstage/config-loader@npm:1.10.0" - dependencies: - "@backstage/cli-common": "npm:^0.1.15" - "@backstage/config": "npm:^1.3.2" - "@backstage/errors": "npm:^1.2.7" - "@backstage/types": "npm:^1.2.1" - "@types/json-schema": "npm:^7.0.6" - ajv: "npm:^8.10.0" - chokidar: "npm:^3.5.2" - fs-extra: "npm:^11.2.0" - json-schema: "npm:^0.4.0" - json-schema-merge-allof: "npm:^0.8.1" - json-schema-traverse: "npm:^1.0.0" - lodash: "npm:^4.17.21" - minimist: "npm:^1.2.5" - typescript-json-schema: "npm:^0.65.0" - yaml: "npm:^2.0.0" - checksum: 10/dc8f78282648e50bc25608fce4699045cdf1bc8d2ed03d2635d0c9afce3e01f052f6d4dde82f12bdb606c73a04ce1217eb3f79401d668061e40e794184535e44 - languageName: node - linkType: hard - "@backstage/config-loader@workspace:^, @backstage/config-loader@workspace:packages/config-loader": version: 0.0.0-use.local resolution: "@backstage/config-loader@workspace:packages/config-loader" @@ -4144,7 +3060,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/config@npm:^1.1.1, @backstage/config@npm:^1.2.0, @backstage/config@npm:^1.3.2, @backstage/config@workspace:^, @backstage/config@workspace:packages/config": +"@backstage/config@workspace:^, @backstage/config@workspace:packages/config": version: 0.0.0-use.local resolution: "@backstage/config@workspace:packages/config" dependencies: @@ -4156,34 +3072,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-app-api@npm:^1.16.1": - version: 1.16.1 - resolution: "@backstage/core-app-api@npm:1.16.1" - dependencies: - "@backstage/config": "npm:^1.3.2" - "@backstage/core-plugin-api": "npm:^1.10.6" - "@backstage/types": "npm:^1.2.1" - "@backstage/version-bridge": "npm:^1.0.11" - "@types/prop-types": "npm:^15.7.3" - history: "npm:^5.0.0" - i18next: "npm:^22.4.15" - lodash: "npm:^4.17.21" - prop-types: "npm:^15.7.2" - react-use: "npm:^17.2.4" - zen-observable: "npm:^0.10.0" - 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/fae37fa5c4598dc1c2a4f82356d4039633cc7323f44f369ad1ca4151e1e27f668b2929f759e7a4843e273979a5aae1b473809e8add23cbd788fc48329bdf51fd - languageName: node - linkType: hard - "@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" @@ -4228,33 +3116,10 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-compat-api@npm:^0.4.1": - version: 0.4.1 - resolution: "@backstage/core-compat-api@npm:0.4.1" - dependencies: - "@backstage/core-plugin-api": "npm:^1.10.6" - "@backstage/frontend-plugin-api": "npm:^0.10.1" - "@backstage/plugin-catalog-react": "npm:^1.17.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/4c91cc4f4a06d6420a4bed63e540e46a34a7a1ecb831fd503960c9e9ae8817f015a0b6d39d37ac4588233caf2a21a93c9acadbe15d11b59706275166567a4581 - 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" dependencies: - "@backstage-community/plugin-puppetdb": "npm:^0.6.0" - "@backstage-community/plugin-stackstorm": "npm:^0.1.16" "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" "@backstage/core-plugin-api": "workspace:^" @@ -4266,7 +3131,6 @@ __metadata: "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" "@backstage/version-bridge": "workspace:^" - "@oriflame/backstage-plugin-score-card": "npm:^0.9.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@types/react": "npm:^18.0.0" @@ -4286,163 +3150,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-components@npm:^0.13.10": - version: 0.13.10 - resolution: "@backstage/core-components@npm:0.13.10" - dependencies: - "@backstage/config": "npm:^1.1.1" - "@backstage/core-plugin-api": "npm:^1.8.2" - "@backstage/errors": "npm:^1.2.3" - "@backstage/theme": "npm:^0.5.0" - "@backstage/version-bridge": "npm:^1.0.7" - "@date-io/core": "npm:^1.3.13" - "@material-table/core": "npm:^3.1.0" - "@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:^23.0.0" - "@types/react": "npm:^16.13.1 || ^17.0.0" - "@types/react-sparklines": "npm:^1.7.0" - "@types/react-text-truncate": "npm:^0.14.0" - ansi-regex: "npm:^6.0.1" - classnames: "npm:^2.2.6" - d3-selection: "npm:^3.0.0" - d3-shape: "npm:^3.0.0" - d3-zoom: "npm:^3.0.0" - dagre: "npm:^0.8.5" - linkify-react: "npm:4.1.3" - linkifyjs: "npm:4.1.3" - lodash: "npm:^4.17.21" - pluralize: "npm:^8.0.0" - qs: "npm:^6.9.4" - rc-progress: "npm:3.5.1" - react-helmet: "npm:6.1.0" - react-hook-form: "npm:^7.12.2" - react-idle-timer: "npm:5.6.2" - react-markdown: "npm:^8.0.0" - react-sparklines: "npm:^1.7.0" - react-syntax-highlighter: "npm:^15.4.5" - react-text-truncate: "npm:^0.19.0" - react-use: "npm:^17.3.2" - react-virtualized-auto-sizer: "npm:^1.0.11" - react-window: "npm:^1.8.6" - remark-gfm: "npm:^3.0.1" - zen-observable: "npm:^0.10.0" - zod: "npm:^3.22.4" - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: 10/41231c51c52571da5a3ecb2b2fed41f05e537973fda7786a875b0978053a330f8224e6f211d3d4a6c4195d614834ae0886df6044c519659460eb8e4402fc6817 - languageName: node - linkType: hard - -"@backstage/core-components@npm:^0.15.1": - version: 0.15.1 - resolution: "@backstage/core-components@npm:0.15.1" - dependencies: - "@backstage/config": "npm:^1.2.0" - "@backstage/core-plugin-api": "npm:^1.10.0" - "@backstage/errors": "npm:^1.2.4" - "@backstage/theme": "npm:^0.6.0" - "@backstage/version-bridge": "npm:^1.0.10" - "@date-io/core": "npm:^1.3.13" - "@material-table/core": "npm:^3.1.0" - "@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" - "@types/react-sparklines": "npm:^1.7.0" - ansi-regex: "npm:^6.0.1" - classnames: "npm:^2.2.6" - d3-selection: "npm:^3.0.0" - d3-shape: "npm:^3.0.0" - d3-zoom: "npm:^3.0.0" - dagre: "npm:^0.8.5" - linkify-react: "npm:4.1.3" - linkifyjs: "npm:4.1.3" - lodash: "npm:^4.17.21" - pluralize: "npm:^8.0.0" - qs: "npm:^6.9.4" - rc-progress: "npm:3.5.1" - react-helmet: "npm:6.1.0" - react-hook-form: "npm:^7.12.2" - react-idle-timer: "npm:5.7.2" - react-markdown: "npm:^8.0.0" - react-sparklines: "npm:^1.7.0" - react-syntax-highlighter: "npm:^15.4.5" - react-use: "npm:^17.3.2" - react-virtualized-auto-sizer: "npm:^1.0.11" - react-window: "npm:^1.8.6" - remark-gfm: "npm:^3.0.1" - zen-observable: "npm:^0.10.0" - zod: "npm:^3.22.4" - peerDependencies: - "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/48c765a7219255b4f230af13b6ba3e78cf0b23f1642a8175b6fa2498b7655dec662ea356f92fbbd9d834b55f8923540f879c0529a0bff0772ebff676152a2275 - languageName: node - linkType: hard - -"@backstage/core-components@npm:^0.17.1": - version: 0.17.1 - resolution: "@backstage/core-components@npm:0.17.1" - dependencies: - "@backstage/config": "npm:^1.3.2" - "@backstage/core-plugin-api": "npm:^1.10.6" - "@backstage/errors": "npm:^1.2.7" - "@backstage/theme": "npm:^0.6.5" - "@backstage/version-bridge": "npm:^1.0.11" - "@dagrejs/dagre": "npm:^1.1.4" - "@date-io/core": "npm:^1.3.13" - "@material-table/core": "npm:^3.1.0" - "@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" - "@testing-library/react": "npm:^16.0.0" - "@types/react-sparklines": "npm:^1.7.0" - ansi-regex: "npm:^6.0.1" - classnames: "npm:^2.2.6" - d3-selection: "npm:^3.0.0" - d3-shape: "npm:^3.0.0" - d3-zoom: "npm:^3.0.0" - js-yaml: "npm:^4.1.0" - linkify-react: "npm:4.1.3" - linkifyjs: "npm:4.1.3" - lodash: "npm:^4.17.21" - pluralize: "npm:^8.0.0" - qs: "npm:^6.9.4" - rc-progress: "npm:3.5.1" - react-helmet: "npm:6.1.0" - react-hook-form: "npm:^7.12.2" - react-idle-timer: "npm:5.7.2" - react-markdown: "npm:^8.0.0" - react-sparklines: "npm:^1.7.0" - react-syntax-highlighter: "npm:^15.4.5" - react-use: "npm:^17.3.2" - react-virtualized-auto-sizer: "npm:^1.0.11" - react-window: "npm:^1.8.6" - remark-gfm: "npm:^3.0.1" - zen-observable: "npm:^0.10.0" - 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/12e8f0675c2dd6bb871d77ceb833d2078cac41278d4122139669ffaadea09df7b3c8ca53c166e2c664c275e381f9c96f8383a5a1faaeb956ab0f1e4ee72ec1b7 - languageName: node - linkType: hard - "@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" @@ -4488,8 +3195,8 @@ __metadata: d3-zoom: "npm:^3.0.0" history: "npm:^5.0.0" js-yaml: "npm:^4.1.0" - linkify-react: "npm:4.1.3" - linkifyjs: "npm:4.1.3" + linkify-react: "npm:4.3.2" + linkifyjs: "npm:4.3.2" lodash: "npm:^4.17.21" msw: "npm:^1.0.0" pluralize: "npm:^8.0.0" @@ -4521,27 +3228,6 @@ __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": - version: 1.10.6 - resolution: "@backstage/core-plugin-api@npm:1.10.6" - dependencies: - "@backstage/config": "npm:^1.3.2" - "@backstage/errors": "npm:^1.2.7" - "@backstage/types": "npm:^1.2.1" - "@backstage/version-bridge": "npm:^1.0.11" - history: "npm:^5.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/1359bb2dc294c6eb0b8de6aa4a0788561c5d43ef2a4464d4f7600c382126398a42724e8ba894925ab02f1d332ef9b87b0eeee31e56a9e9223f8d6504a283d896 - languageName: node - linkType: hard - "@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" @@ -4653,7 +3339,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/errors@npm:^1.2.3, @backstage/errors@npm:^1.2.4, @backstage/errors@npm:^1.2.7, @backstage/errors@workspace:^, @backstage/errors@workspace:packages/errors": +"@backstage/errors@workspace:^, @backstage/errors@workspace:packages/errors": version: 0.0.0-use.local resolution: "@backstage/errors@workspace:packages/errors" dependencies: @@ -4663,7 +3349,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/eslint-plugin@workspace:^, @backstage/eslint-plugin@workspace:packages/eslint-plugin": +"@backstage/eslint-plugin@workspace:*, @backstage/eslint-plugin@workspace:^, @backstage/eslint-plugin@workspace:packages/eslint-plugin": version: 0.0.0-use.local resolution: "@backstage/eslint-plugin@workspace:packages/eslint-plugin" dependencies: @@ -4675,32 +3361,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/frontend-app-api@npm:^0.11.1": - version: 0.11.1 - resolution: "@backstage/frontend-app-api@npm:0.11.1" - dependencies: - "@backstage/config": "npm:^1.3.2" - "@backstage/core-app-api": "npm:^1.16.1" - "@backstage/core-plugin-api": "npm:^1.10.6" - "@backstage/errors": "npm:^1.2.7" - "@backstage/frontend-defaults": "npm:^0.2.1" - "@backstage/frontend-plugin-api": "npm:^0.10.1" - "@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/fdf5b7dddeba9320e8ec54751396a3e81897605c26520af60eb61a76f17dd78b6b786f5cfa9f4bad3beef0d3408868bfe8331f6773b54034d11dc170a35e60e7 - 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" @@ -4712,6 +3372,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/frontend-defaults": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/frontend-test-utils": "workspace:^" "@backstage/plugin-app": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" @@ -4735,34 +3396,13 @@ __metadata: languageName: unknown linkType: soft -"@backstage/frontend-defaults@npm:^0.2.1": - version: 0.2.1 - resolution: "@backstage/frontend-defaults@npm:0.2.1" - dependencies: - "@backstage/config": "npm:^1.3.2" - "@backstage/errors": "npm:^1.2.7" - "@backstage/frontend-app-api": "npm:^0.11.1" - "@backstage/frontend-plugin-api": "npm:^0.10.1" - "@backstage/plugin-app": "npm:^0.1.8" - "@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/3bd178ca78db5d467378272025d7a3fa7995a37063f39c2ec2c9a81b2bb6aa4890f888e99f1e5c4abb442614b978a490d8dbcaa09e54a94e47364caab618725c - 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" dependencies: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/frontend-app-api": "workspace:^" @@ -4818,30 +3458,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/frontend-plugin-api@npm:^0.10.1": - version: 0.10.1 - resolution: "@backstage/frontend-plugin-api@npm:0.10.1" - dependencies: - "@backstage/core-components": "npm:^0.17.1" - "@backstage/core-plugin-api": "npm:^1.10.6" - "@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/5a1b69d7a1b1db584f4f0c4934ac98dc678f53730c250959da47e20479032b02b1f02785bcb51989af899e8b1166599e23e03ce024469e5efb5b49f8c792e315 - 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" @@ -4876,31 +3492,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/frontend-test-utils@npm:^0.3.1": - version: 0.3.1 - resolution: "@backstage/frontend-test-utils@npm:0.3.1" - dependencies: - "@backstage/config": "npm:^1.3.2" - "@backstage/frontend-app-api": "npm:^0.11.1" - "@backstage/frontend-plugin-api": "npm:^0.10.1" - "@backstage/plugin-app": "npm:^0.1.8" - "@backstage/test-utils": "npm:^1.7.7" - "@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/b46c7e660ffbfecae9d0dd9fbc471c0c6324550d768f2749ff5726b4acd737aef4075290f1c2e8b73ce967b664997cc1f47f933254209d41d2fb080799c3dba9 - 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" @@ -4931,21 +3522,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration-aws-node@npm:^0.1.12": - version: 0.1.15 - resolution: "@backstage/integration-aws-node@npm:0.1.15" - dependencies: - "@aws-sdk/client-sts": "npm:^3.350.0" - "@aws-sdk/credential-provider-node": "npm:^3.350.0" - "@aws-sdk/credential-providers": "npm:^3.350.0" - "@aws-sdk/types": "npm:^3.347.0" - "@aws-sdk/util-arn-parser": "npm:^3.310.0" - "@backstage/config": "npm:^1.3.2" - "@backstage/errors": "npm:^1.2.7" - checksum: 10/92c52ad6f33ff270840b44bb060837f0f0b9f133c76c171f8aad29192580dd04c5d4db716ad6c07c8a4ebfc709458781bfb39def7ddadce64918d8b4352730ab - languageName: node - linkType: hard - "@backstage/integration-aws-node@workspace:^, @backstage/integration-aws-node@workspace:packages/integration-aws-node": version: 0.0.0-use.local resolution: "@backstage/integration-aws-node@workspace:packages/integration-aws-node" @@ -4965,27 +3541,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration-react@npm:^1.1.24, @backstage/integration-react@npm:^1.2.6": - version: 1.2.6 - resolution: "@backstage/integration-react@npm:1.2.6" - dependencies: - "@backstage/config": "npm:^1.3.2" - "@backstage/core-plugin-api": "npm:^1.10.6" - "@backstage/integration": "npm:^1.16.3" - "@material-ui/core": "npm:^4.12.2" - "@material-ui/icons": "npm:^4.9.1" - 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/7a3a13d277e03c5a95131b99a50210e4bd7bdcba133ef5ee1549d625fc373072669a52e9317bc1b29f27f7a3d99eb89d2dc6469ff7bd87de10eb1d19a03a51bb - languageName: node - linkType: hard - "@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" @@ -5017,24 +3572,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration@npm:^1.15.0, @backstage/integration@npm:^1.16.3, @backstage/integration@npm:^1.9.0": - version: 1.16.3 - resolution: "@backstage/integration@npm:1.16.3" - dependencies: - "@azure/identity": "npm:^4.0.0" - "@azure/storage-blob": "npm:^12.5.0" - "@backstage/config": "npm:^1.3.2" - "@backstage/errors": "npm:^1.2.7" - "@octokit/auth-app": "npm:^4.0.0" - "@octokit/rest": "npm:^19.0.3" - cross-fetch: "npm:^4.0.0" - git-url-parse: "npm:^15.0.0" - lodash: "npm:^4.17.21" - luxon: "npm:^3.0.0" - checksum: 10/ce27919bee116725b1cd0749388dacace62746eba46772343cdd95d93c0261d673b7feac62ff74a024e6a8b455b33f231ceb987c23ecf34804643eda6bed2821 - languageName: node - linkType: hard - "@backstage/integration@workspace:^, @backstage/integration@workspace:packages/integration": version: 0.0.0-use.local resolution: "@backstage/integration@workspace:packages/integration" @@ -5196,33 +3733,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-app@npm:^0.1.8": - version: 0.1.8 - resolution: "@backstage/plugin-app@npm:0.1.8" - dependencies: - "@backstage/core-components": "npm:^0.17.1" - "@backstage/core-plugin-api": "npm:^1.10.6" - "@backstage/frontend-plugin-api": "npm:^0.10.1" - "@backstage/integration-react": "npm:^1.2.6" - "@backstage/plugin-permission-react": "npm:^0.4.33" - "@backstage/theme": "npm:^0.6.5" - "@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/3634ce0bb0ac0f86050c45a4d24f728975232dcd04106b48b1ffdaec4e2ce9ef1ad17220afd0351fb75d6e10bfaacd45289064397332b5c08dc1c046d21466cf - 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" @@ -5231,15 +3741,19 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" + "@backstage/frontend-defaults": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" "@backstage/frontend-test-utils": "workspace:^" "@backstage/integration-react": "workspace:^" "@backstage/plugin-permission-react": "workspace:^" + "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/version-bridge": "workspace:^" "@material-ui/core": "npm:^4.9.13" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:^4.0.0-alpha.61" + "@react-hookz/web": "npm:^24.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@testing-library/user-event": "npm:^14.0.0" @@ -5249,6 +3763,7 @@ __metadata: react-dom: "npm:^18.0.2" react-router-dom: "npm:^6.3.0" react-use: "npm:^17.2.4" + zod: "npm:^3.22.4" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 @@ -5275,6 +3790,7 @@ __metadata: passport: "npm:^0.7.0" passport-atlassian-oauth2: "npm:^2.1.0" supertest: "npm:^7.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -5314,6 +3830,7 @@ __metadata: jose: "npm:^5.0.0" msw: "npm:^2.0.8" node-cache: "npm:^5.1.2" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -5332,6 +3849,7 @@ __metadata: express: "npm:^4.19.2" jose: "npm:^5.0.0" passport: "npm:^0.7.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -5350,6 +3868,7 @@ __metadata: passport: "npm:^0.7.0" passport-bitbucket-oauth2: "npm:^0.1.2" supertest: "npm:^7.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -5368,6 +3887,7 @@ __metadata: passport: "npm:^0.7.0" passport-oauth2: "npm:^1.6.1" supertest: "npm:^7.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -5389,6 +3909,7 @@ __metadata: msw: "npm:^2.0.0" node-mocks-http: "npm:^1.0.0" uuid: "npm:^11.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -5404,6 +3925,7 @@ __metadata: "@backstage/types": "workspace:^" express: "npm:^4.18.2" google-auth-library: "npm:^9.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -5421,6 +3943,7 @@ __metadata: "@types/passport-github2": "npm:^1.2.4" passport-github2: "npm:^0.1.12" supertest: "npm:^7.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -5439,6 +3962,7 @@ __metadata: passport: "npm:^0.7.0" passport-gitlab2: "npm:^5.0.0" supertest: "npm:^7.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -5456,6 +3980,7 @@ __metadata: google-auth-library: "npm:^9.0.0" passport-google-oauth20: "npm:^2.0.0" supertest: "npm:^7.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -5493,6 +4018,7 @@ __metadata: msw: "npm:^1.0.0" passport-microsoft: "npm:^1.0.0" supertest: "npm:^7.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -5510,6 +4036,7 @@ __metadata: passport: "npm:^0.7.0" passport-oauth2: "npm:^1.6.1" supertest: "npm:^7.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -5523,6 +4050,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" jose: "npm:^5.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -5547,6 +4075,7 @@ __metadata: openid-client: "npm:^5.5.0" passport: "npm:^0.7.0" supertest: "npm:^7.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -5561,10 +4090,11 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - "@davidzemon/passport-okta-oauth": "npm:^0.0.5" + "@davidzemon/passport-okta-oauth": "npm:^0.0.7" express: "npm:^4.18.2" passport: "npm:^0.7.0" supertest: "npm:^7.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -5583,6 +4113,7 @@ __metadata: passport: "npm:^0.7.0" passport-onelogin-oauth: "npm:^0.0.1" supertest: "npm:^7.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -5669,54 +4200,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-auth-node@npm:^0.5.2": - version: 0.5.6 - resolution: "@backstage/plugin-auth-node@npm:0.5.6" - dependencies: - "@backstage/backend-common": "npm:^0.25.0" - "@backstage/backend-plugin-api": "npm:^1.1.1" - "@backstage/catalog-client": "npm:^1.9.1" - "@backstage/catalog-model": "npm:^1.7.3" - "@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" - winston: "npm:^3.2.1" - zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.21.4" - zod-validation-error: "npm:^3.4.0" - checksum: 10/b286fcf7b8a274697a649567c4f96f9816f46b8a28729087e8338525a2a1d276e6960588313f4d60ae5711a9f9ca12b1ba49370f7496181f16fc80b2b629f31a - languageName: node - linkType: hard - -"@backstage/plugin-auth-node@npm:^0.6.2": - version: 0.6.2 - resolution: "@backstage/plugin-auth-node@npm:0.6.2" - dependencies: - "@backstage/backend-plugin-api": "npm:^1.3.0" - "@backstage/catalog-client": "npm:^1.9.1" - "@backstage/catalog-model": "npm:^1.7.3" - "@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/831b07fa41df68855f9a107a78c70d34986ddc7e12eac70cd3cdecb42b1297a65f0e6061549b12653704966887a5cd0199c7d69588e48a461dd74559d890aaa7 - 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" @@ -5942,6 +4425,23 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-catalog-backend-module-gitea@workspace:plugins/catalog-backend-module-gitea": + version: 0.0.0-use.local + resolution: "@backstage/plugin-catalog-backend-module-gitea@workspace:plugins/catalog-backend-module-gitea" + dependencies: + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/integration": "workspace:^" + "@backstage/plugin-catalog-common": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" + p-limit: "npm:^3.0.2" + uuid: "npm:^11.0.0" + languageName: unknown + linkType: soft + "@backstage/plugin-catalog-backend-module-github-org@workspace:plugins/catalog-backend-module-github-org": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-backend-module-github-org@workspace:plugins/catalog-backend-module-github-org" @@ -6199,7 +4699,6 @@ __metadata: "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-permission-node": "workspace:^" "@backstage/repo-tools": "workspace:^" - "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" "@opentelemetry/api": "npm:^1.9.0" "@types/core-js": "npm:^2.5.4" @@ -6208,7 +4707,7 @@ __metadata: "@types/glob": "npm:^8.0.0" "@types/lodash": "npm:^4.14.151" "@types/supertest": "npm:^2.0.8" - better-sqlite3: "npm:^11.0.0" + better-sqlite3: "npm:^12.0.0" codeowners-utils: "npm:^1.0.2" core-js: "npm:^3.6.5" express: "npm:^4.17.1" @@ -6233,17 +4732,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-common@npm:^1.0.20, @backstage/plugin-catalog-common@npm:^1.1.3": - version: 1.1.3 - resolution: "@backstage/plugin-catalog-common@npm:1.1.3" - dependencies: - "@backstage/catalog-model": "npm:^1.7.3" - "@backstage/plugin-permission-common": "npm:^0.8.4" - "@backstage/plugin-search-common": "npm:^1.2.17" - checksum: 10/60c0624603576bcf12bf99e6ac1c921e0847ad72f8ac89ed4315b88f128b738e00eab8c6fc1da6e9830dac93b9b0acd2e3cb1ee21667c48a98ac7b79b546a560 - languageName: node - linkType: hard - "@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" @@ -6370,47 +4858,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-react@npm:^1.17.0, @backstage/plugin-catalog-react@npm:^1.9.3": - version: 1.17.0 - resolution: "@backstage/plugin-catalog-react@npm:1.17.0" - dependencies: - "@backstage/catalog-client": "npm:^1.9.1" - "@backstage/catalog-model": "npm:^1.7.3" - "@backstage/core-compat-api": "npm:^0.4.1" - "@backstage/core-components": "npm:^0.17.1" - "@backstage/core-plugin-api": "npm:^1.10.6" - "@backstage/errors": "npm:^1.2.7" - "@backstage/frontend-plugin-api": "npm:^0.10.1" - "@backstage/frontend-test-utils": "npm:^0.3.1" - "@backstage/integration-react": "npm:^1.2.6" - "@backstage/plugin-catalog-common": "npm:^1.1.3" - "@backstage/plugin-permission-common": "npm:^0.8.4" - "@backstage/plugin-permission-react": "npm:^0.4.33" - "@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/025ed43087462b021c13eac7fc762ad7d52d8ebb593da0093130e6379a79ac1b428fa9024eb21ace9bdf1d43ecafcab39226dbbf2b44ba35a6c93a9ddb04f1da - 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" @@ -6532,6 +4979,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:^" @@ -6797,6 +5246,21 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-events-backend-module-kafka@workspace:plugins/events-backend-module-kafka": + version: 0.0.0-use.local + resolution: "@backstage/plugin-events-backend-module-kafka@workspace:plugins/events-backend-module-kafka" + dependencies: + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/plugin-events-backend-test-utils": "workspace:^" + "@backstage/plugin-events-node": "workspace:^" + "@backstage/types": "workspace:^" + kafkajs: "npm:^2.2.4" + languageName: unknown + linkType: soft + "@backstage/plugin-events-backend-test-utils@workspace:^, @backstage/plugin-events-backend-test-utils@workspace:plugins/events-backend-test-utils": version: 0.0.0-use.local resolution: "@backstage/plugin-events-backend-test-utils@workspace:plugins/events-backend-test-utils" @@ -6877,6 +5341,7 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@rjsf/utils": "npm:5.23.2" @@ -6956,7 +5421,6 @@ __metadata: "@aws-sdk/signature-v4": "npm:^3.347.0" "@azure/identity": "npm:^4.0.0" "@backstage/backend-app-api": "workspace:^" - "@backstage/backend-common": "npm:^0.25.0" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" @@ -7102,6 +5566,9 @@ __metadata: "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@types/react": "npm:^18.0.0" + "@xterm/addon-attach": "npm:^0.11.0" + "@xterm/addon-fit": "npm:^0.10.0" + "@xterm/xterm": "npm:^5.5.0" cronstrue: "npm:^2.32.0" jest-websocket-mock: "npm:^2.5.0" js-yaml: "npm:^4.1.0" @@ -7113,9 +5580,6 @@ __metadata: react-dom: "npm:^18.0.2" react-router-dom: "npm:^6.3.0" react-use: "npm:^17.4.0" - xterm: "npm:^5.3.0" - xterm-addon-attach: "npm:^0.9.0" - xterm-addon-fit: "npm:^0.8.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 @@ -7151,6 +5615,9 @@ __metadata: "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@types/react": "npm:^18.0.0" + "@xterm/addon-attach": "npm:^0.11.0" + "@xterm/addon-fit": "npm:^0.10.0" + "@xterm/xterm": "npm:^5.5.0" cronstrue: "npm:^2.2.0" js-yaml: "npm:^4.0.0" kubernetes-models: "npm:^4.1.0" @@ -7159,9 +5626,6 @@ __metadata: react: "npm:^18.0.2" react-dom: "npm:^18.0.2" react-router-dom: "npm:^6.3.0" - xterm: "npm:^5.2.1" - xterm-addon-attach: "npm:^0.9.0" - xterm-addon-fit: "npm:^0.8.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 @@ -7173,6 +5637,26 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-mcp-actions-backend@workspace:^, @backstage/plugin-mcp-actions-backend@workspace:plugins/mcp-actions-backend": + version: 0.0.0-use.local + resolution: "@backstage/plugin-mcp-actions-backend@workspace:plugins/mcp-actions-backend" + dependencies: + "@backstage/backend-defaults": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" + "@backstage/catalog-client": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" + "@backstage/types": "workspace:^" + "@modelcontextprotocol/sdk": "npm:^1.12.3" + "@types/express": "npm:^4.17.6" + express: "npm:^4.17.1" + express-promise-router: "npm:^4.1.0" + zod: "npm:^3.22.4" + languageName: unknown + linkType: soft + "@backstage/plugin-notifications-backend-module-email@workspace:plugins/notifications-backend-module-email": version: 0.0.0-use.local resolution: "@backstage/plugin-notifications-backend-module-email@workspace:plugins/notifications-backend-module-email" @@ -7205,7 +5689,6 @@ __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:^" @@ -7233,7 +5716,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:^" @@ -7458,21 +5940,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-permission-common@npm:^0.8.4": - version: 0.8.4 - resolution: "@backstage/plugin-permission-common@npm:0.8.4" - dependencies: - "@backstage/config": "npm:^1.3.2" - "@backstage/errors": "npm:^1.2.7" - "@backstage/types": "npm:^1.2.1" - cross-fetch: "npm:^4.0.0" - uuid: "npm:^11.0.0" - zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" - checksum: 10/c850ff17d4f97a77064a296a3b53b112906f7483e72d6e48008b17846d3fa2578ce532722844f1761eb81687155f7f58634fd6ea22313733174903fda90860be - languageName: node - linkType: hard - "@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" @@ -7489,24 +5956,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-permission-node@npm:^0.9.1": - version: 0.9.1 - resolution: "@backstage/plugin-permission-node@npm:0.9.1" - dependencies: - "@backstage/backend-plugin-api": "npm:^1.3.0" - "@backstage/config": "npm:^1.3.2" - "@backstage/errors": "npm:^1.2.7" - "@backstage/plugin-auth-node": "npm:^0.6.2" - "@backstage/plugin-permission-common": "npm:^0.8.4" - "@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/3292951e8cdcc4341ba53e67bcc580d77da14e1e3224b9323671704b7329a3a9e45905e3cc5afb8bff33462178ddf7c3a510392c048ce125f374910622e91bd4 - 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" @@ -7530,26 +5979,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-permission-react@npm:^0.4.33": - version: 0.4.33 - resolution: "@backstage/plugin-permission-react@npm:0.4.33" - dependencies: - "@backstage/config": "npm:^1.3.2" - "@backstage/core-plugin-api": "npm:^1.10.6" - "@backstage/plugin-permission-common": "npm:^0.8.4" - swr: "npm:^2.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/552bba79e3876ec48faf40df1c7f05f5b183130b3b214101b1dd48f519b6846867457ae458daeb20e74b8d8c805f4615c8a1c2d670e2f69f463cb484a408f16b - languageName: node - linkType: hard - "@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" @@ -7645,6 +6074,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 @@ -7663,6 +6093,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 @@ -7786,12 +6217,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:^" @@ -7803,6 +6234,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 @@ -7814,11 +6246,11 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" - "@backstage/core-app-api": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-scaffolder-node": "workspace:^" "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" + "@gitbeaker/requester-utils": "npm:^41.2.0" "@gitbeaker/rest": "npm:^41.2.0" luxon: "npm:^3.0.0" winston: "npm:^3.2.1" @@ -7837,7 +6269,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 @@ -7902,11 +6333,10 @@ __metadata: resolution: "@backstage/plugin-scaffolder-backend@workspace:plugins/scaffolder-backend" dependencies: "@backstage/backend-app-api": "workspace:^" - "@backstage/backend-common": "npm:^0.25.0" "@backstage/backend-defaults": "workspace:^" + "@backstage/backend-openapi-utils": "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:^" @@ -7930,6 +6360,7 @@ __metadata: "@backstage/plugin-scaffolder-common": "workspace:^" "@backstage/plugin-scaffolder-node": "workspace:^" "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" + "@backstage/repo-tools": "workspace:^" "@backstage/types": "workspace:^" "@opentelemetry/api": "npm:^1.9.0" "@types/express": "npm:^4.17.6" @@ -7941,7 +6372,6 @@ __metadata: concat-stream: "npm:^2.0.0" esbuild: "npm:^0.25.0" express: "npm:^4.17.1" - express-promise-router: "npm:^4.1.0" fs-extra: "npm:^11.2.0" globby: "npm:^11.0.0" isbinaryfile: "npm:^5.0.0" @@ -7976,8 +6406,18 @@ __metadata: dependencies: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/integration": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" + "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@microsoft/fetch-event-source": "npm:^2.0.1" + "@types/json-schema": "npm:^7.0.9" + cross-fetch: "npm:^4.0.0" + json-schema: "npm:^0.4.0" + msw: "npm:^1.0.0" + uri-template: "npm:^2.0.0" + zen-observable: "npm:^0.10.0" languageName: unknown linkType: soft @@ -8018,14 +6458,17 @@ __metadata: "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" + "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-scaffolder-common": "workspace:^" "@backstage/types": "workspace:^" "@isomorphic-git/pgp-plugin": "npm:^0.0.7" + "@types/lodash": "npm:^4.14.151" concat-stream: "npm:^2.0.0" fs-extra: "npm:^11.2.0" globby: "npm:^11.0.0" isomorphic-git: "npm:^1.23.0" jsonschema: "npm:^1.5.0" + lodash: "npm:^4.17.21" p-limit: "npm:^3.1.0" tar: "npm:^6.1.12" winston: "npm:^3.2.1" @@ -8134,7 +6577,6 @@ __metadata: "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" - "@microsoft/fetch-event-source": "npm:^2.0.1" "@react-hookz/web": "npm:^24.0.0" "@rjsf/core": "npm:5.23.2" "@rjsf/material-ui": "npm:5.23.2" @@ -8159,17 +6601,16 @@ __metadata: jszip: "npm:^3.10.1" lodash: "npm:^4.17.21" luxon: "npm:^3.0.0" - msw: "npm:^1.0.0" qs: "npm:^6.9.4" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" react-resizable: "npm:^3.0.5" + react-resizable-panels: "npm:^3.0.4" react-router-dom: "npm:^6.3.0" react-use: "npm:^17.2.4" react-window: "npm:^1.8.10" swr: "npm:^2.0.0" yaml: "npm:^2.0.0" - zen-observable: "npm:^0.10.0" zod: "npm:^3.22.4" zod-to-json-schema: "npm:^3.20.4" peerDependencies: @@ -8342,16 +6783,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-search-common@npm:^1.2.17": - version: 1.2.17 - resolution: "@backstage/plugin-search-common@npm:1.2.17" - dependencies: - "@backstage/plugin-permission-common": "npm:^0.8.4" - "@backstage/types": "npm:^1.2.1" - checksum: 10/30cfcb164940b7eb70d8163766cca981536372d0cb5c6e006837e044d91a60be16b83534a2887b0a7cf90a51bed35cd7b188e62598e7340cbf79e371a8bfd0e6 - languageName: node - linkType: hard - "@backstage/plugin-search-common@workspace:^, @backstage/plugin-search-common@workspace:plugins/search-common": version: 0.0.0-use.local resolution: "@backstage/plugin-search-common@workspace:plugins/search-common" @@ -8526,6 +6957,7 @@ __metadata: dependencies: "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" + "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -8724,6 +7156,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:^" @@ -8908,7 +7341,7 @@ __metadata: "@backstage/config-loader": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/types": "workspace:^" - "@electric-sql/pglite": "npm:^0.2.15" + "@electric-sql/pglite": "npm:^0.3.0" "@manypkg/get-packages": "npm:^1.1.3" "@microsoft/api-documenter": "npm:^7.25.7" "@microsoft/api-extractor": "npm:^7.47.2" @@ -8931,6 +7364,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" @@ -8943,15 +7377,16 @@ __metadata: portfinder: "npm:^1.0.32" tar: "npm:^6.1.12" ts-morph: "npm:^24.0.0" - typedoc: "npm:^0.27.6" + typedoc: "npm:^0.28.0" yaml-diff-patch: "npm:^2.0.0" + zod: "npm:^3.22.4" peerDependencies: "@microsoft/api-extractor-model": "*" "@microsoft/tsdoc": "*" "@microsoft/tsdoc-config": "*" "@useoptic/optic": ^1.0.0 prettier: ^2.8.1 - typedoc: ^0.27.0 + typedoc: ^0.28.0 typescript: "> 3.0.0" peerDependenciesMeta: prettier: @@ -8963,35 +7398,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/test-utils@npm:^1.7.7": - version: 1.7.7 - resolution: "@backstage/test-utils@npm:1.7.7" - dependencies: - "@backstage/config": "npm:^1.3.2" - "@backstage/core-app-api": "npm:^1.16.1" - "@backstage/core-plugin-api": "npm:^1.10.6" - "@backstage/plugin-permission-common": "npm:^0.8.4" - "@backstage/plugin-permission-react": "npm:^0.4.33" - "@backstage/theme": "npm:^0.6.5" - "@backstage/types": "npm:^1.2.1" - "@material-ui/core": "npm:^4.12.2" - "@material-ui/icons": "npm:^4.9.1" - cross-fetch: "npm:^4.0.0" - i18next: "npm:^22.4.15" - zen-observable: "npm:^0.10.0" - 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/2ffc4957e84b4c0e1328e43e05c88051026edbf8e2c9fd04f873176ecd636bc11269f36fa72933defc60d0d869986e9a2d46bba1f57e50811671d81c330bf1b1 - languageName: node - linkType: hard - "@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" @@ -9028,42 +7434,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/theme@npm:^0.5.0": - version: 0.5.7 - resolution: "@backstage/theme@npm:0.5.7" - dependencies: - "@emotion/react": "npm:^11.10.5" - "@emotion/styled": "npm:^11.10.5" - "@mui/material": "npm:^5.12.2" - peerDependencies: - "@material-ui/core": ^4.12.2 - "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - checksum: 10/b826aabc3f483e7f0dd2285c1510c4a59797433a2884a2423b22bfce6627aefabcd258aac23edecb58edb8275a87deb4ef212646d1832721a56862d46d8b4001 - languageName: node - linkType: hard - -"@backstage/theme@npm:^0.6.0, @backstage/theme@npm:^0.6.5": - version: 0.6.5 - resolution: "@backstage/theme@npm:0.6.5" - dependencies: - "@emotion/react": "npm:^11.10.5" - "@emotion/styled": "npm:^11.10.5" - "@mui/material": "npm:^5.12.2" - peerDependencies: - "@material-ui/core": ^4.12.2 - "@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/242ccb2af6e2d1c64d4084c568c31d696d6abfdbbbd0ba35b2a9dabaae9c196b4610e2124f2c23fff16de082701901627f85b180f8a3ea2a8bcb865beb982829 - languageName: node - linkType: hard - "@backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": version: 0.0.0-use.local resolution: "@backstage/theme@workspace:packages/theme" @@ -9091,7 +7461,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/types@npm:^1.1.1, @backstage/types@npm:^1.2.1, @backstage/types@workspace:^, @backstage/types@workspace:packages/types": +"@backstage/types@workspace:^, @backstage/types@workspace:packages/types": version: 0.0.0-use.local resolution: "@backstage/types@workspace:packages/types" dependencies: @@ -9102,7 +7472,42 @@ __metadata: languageName: unknown linkType: soft -"@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": +"@backstage/ui@workspace:^, @backstage/ui@workspace:packages/ui": + version: 0.0.0-use.local + resolution: "@backstage/ui@workspace:packages/ui" + dependencies: + "@backstage/cli": "workspace:^" + "@base-ui-components/react": "npm:1.0.0-alpha.7" + "@remixicon/react": "npm:^4.6.0" + "@storybook/react": "npm:^8.6.12" + "@storybook/test": "npm:^8.6.12" + "@tanstack/react-table": "npm:^8.21.3" + "@types/react": "npm:^18.0.0" + "@types/react-dom": "npm:^18.0.0" + chalk: "npm:^5.4.1" + clsx: "npm:^2.1.1" + eslint-plugin-storybook: "npm:^0.12.0" + glob: "npm:^11.0.1" + globals: "npm:^15.11.0" + lightningcss: "npm:^1.29.1" + mini-css-extract-plugin: "npm:^2.9.2" + react: "npm:^18.0.2" + react-aria-components: "npm:^1.10.1" + react-dom: "npm:^18.0.2" + react-router-dom: "npm:^6.3.0" + storybook: "npm:^8.6.12" + 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 + languageName: unknown + linkType: soft + +"@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: @@ -9215,9 +7620,9 @@ __metadata: languageName: node linkType: hard -"@changesets/assemble-release-plan@npm:^6.0.7": - version: 6.0.7 - resolution: "@changesets/assemble-release-plan@npm:6.0.7" +"@changesets/assemble-release-plan@npm:^6.0.9": + version: 6.0.9 + resolution: "@changesets/assemble-release-plan@npm:6.0.9" dependencies: "@changesets/errors": "npm:^0.2.0" "@changesets/get-dependents-graph": "npm:^2.1.3" @@ -9225,7 +7630,7 @@ __metadata: "@changesets/types": "npm:^6.1.0" "@manypkg/get-packages": "npm:^1.1.3" semver: "npm:^7.5.3" - checksum: 10/61e0962c8116b802de11c7eddc34aa0a827166dba84686b69705da7c8e57bf16101f062b4de784a622f511e967554685b050d6f54fffe953da04f6564e65e414 + checksum: 10/f84656eabb700ed77f97751b282e1701636ed45a44b443abd9af0291870495cc046fee301478010f39a1dc455799065ae007b9d7d2bb5ae8b793b65bbb8e052a languageName: node linkType: hard @@ -9239,16 +7644,16 @@ __metadata: linkType: hard "@changesets/cli@npm:^2.14.0": - version: 2.29.3 - resolution: "@changesets/cli@npm:2.29.3" + version: 2.29.5 + resolution: "@changesets/cli@npm:2.29.5" dependencies: "@changesets/apply-release-plan": "npm:^7.0.12" - "@changesets/assemble-release-plan": "npm:^6.0.7" + "@changesets/assemble-release-plan": "npm:^6.0.9" "@changesets/changelog-git": "npm:^0.2.1" "@changesets/config": "npm:^3.1.1" "@changesets/errors": "npm:^0.2.0" "@changesets/get-dependents-graph": "npm:^2.1.3" - "@changesets/get-release-plan": "npm:^4.0.11" + "@changesets/get-release-plan": "npm:^4.0.13" "@changesets/git": "npm:^3.0.4" "@changesets/logger": "npm:^0.1.1" "@changesets/pre": "npm:^2.0.2" @@ -9272,7 +7677,7 @@ __metadata: term-size: "npm:^2.1.0" bin: changeset: bin.js - checksum: 10/9ab528d026651b8544d071000d7c26a9f6ac157299050ca67279b001ef6215f9647b83906aa5a4c761215696d898e704e5613df16c81c4bab7895ef1fabea9e0 + checksum: 10/f401da29025d7bcc07b732bb09a9627f785bfc21c7c2005861d11ffea732bc14d33394fc2fcae50cc5f2b710f6080c5babe2fa90d432de5fdb47ae6afc147936 languageName: node linkType: hard @@ -9312,17 +7717,17 @@ __metadata: languageName: node linkType: hard -"@changesets/get-release-plan@npm:^4.0.11": - version: 4.0.11 - resolution: "@changesets/get-release-plan@npm:4.0.11" +"@changesets/get-release-plan@npm:^4.0.13": + version: 4.0.13 + resolution: "@changesets/get-release-plan@npm:4.0.13" dependencies: - "@changesets/assemble-release-plan": "npm:^6.0.7" + "@changesets/assemble-release-plan": "npm:^6.0.9" "@changesets/config": "npm:^3.1.1" "@changesets/pre": "npm:^2.0.2" "@changesets/read": "npm:^0.6.5" "@changesets/types": "npm:^6.1.0" "@manypkg/get-packages": "npm:^1.1.3" - checksum: 10/4a05b4474847a60604dc6158d0c8204a400a98fcb4593dc46b897abca3a1ce1eea40af208100e119e50ad693e34f061cc9b84912aa5ff3eb1560d4db59a4af59 + checksum: 10/9983fae5a68012c4c418ddd62f2fb3d325363f21160252ff7b868503a1a2effb8fdd32e4a0289b72653afc3605ce19d163ff69205c942a0004efb571a5f78fd0 languageName: node linkType: hard @@ -9458,8 +7863,8 @@ __metadata: linkType: hard "@codemirror/language@npm:^6.0.0": - version: 6.11.0 - resolution: "@codemirror/language@npm:6.11.0" + version: 6.11.2 + resolution: "@codemirror/language@npm:6.11.2" dependencies: "@codemirror/state": "npm:^6.0.0" "@codemirror/view": "npm:^6.23.0" @@ -9467,7 +7872,7 @@ __metadata: "@lezer/highlight": "npm:^1.0.0" "@lezer/lr": "npm:^1.0.0" style-mod: "npm:^4.0.0" - checksum: 10/571d8d18c51173c25dc3a1c1327fa2265e3d666acd2f2e3dbab828f49bc7f350159bbe8d46ee4baf88cb3eb0fbcfb3351028df6e2981455a55fd2951254c950f + checksum: 10/6ecccc48ad4390fca94525eefd0f4c904effad285e1d1de0db3764f08fd33299e5e453ab4d9ff8c33b4baeeb95f4a78660152ab64255fd19eccd31142410f6ed languageName: node linkType: hard @@ -9524,13 +7929,14 @@ __metadata: linkType: hard "@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.23.0": - version: 6.36.7 - resolution: "@codemirror/view@npm:6.36.7" + version: 6.38.1 + resolution: "@codemirror/view@npm:6.38.1" dependencies: "@codemirror/state": "npm:^6.5.0" + crelt: "npm:^1.0.6" style-mod: "npm:^4.1.0" w3c-keyname: "npm:^2.2.4" - checksum: 10/0ba49a3025fbb381a8a35022135ea40363a3a3c298d41e3083bb5e263dfa4f11ff9419a46f2b78ef8e853376384ad6c7e48d2a7887e10d366e878d10b53e3b54 + checksum: 10/e0c5a365608749dd096ba7a930c8393f316bf4c2cacd1465a47a057d0a9f9868ff372a0bb6eb696c926f88411139f79a97a05f8c884bcc380145445cc61e68c8 languageName: node linkType: hard @@ -9562,11 +7968,11 @@ __metadata: linkType: hard "@dagrejs/dagre@npm:^1.1.4": - version: 1.1.4 - resolution: "@dagrejs/dagre@npm:1.1.4" + version: 1.1.5 + resolution: "@dagrejs/dagre@npm:1.1.5" dependencies: "@dagrejs/graphlib": "npm:2.2.4" - checksum: 10/0b3744b170c68ae0666e03aca19c3100d5131feafeb54b3ea096b749a9f0fe5385b8bd8889c11a49493cfab945b2486b9e30bc41b321755ed718e9f5cb4b74f1 + checksum: 10/c00abd1e04d19f90ad8dfa0a4e16365371bc4309affead3827a1b39f6b0b946643b8af0b1e5519011deca3fda4c7471b27e9ebb03423309a94f95ac0b881ac4f languageName: node linkType: hard @@ -9595,15 +8001,25 @@ __metadata: languageName: node linkType: hard -"@davidzemon/passport-okta-oauth@npm:^0.0.5": - version: 0.0.5 - resolution: "@davidzemon/passport-okta-oauth@npm:0.0.5" +"@davidzemon/passport-okta-oauth@npm:^0.0.7": + version: 0.0.7 + resolution: "@davidzemon/passport-okta-oauth@npm:0.0.7" dependencies: "@types/passport-oauth2": "npm:^1.4.11" passport-oauth2: "npm:^1.6.1" pkginfo: "npm:^0.4.1" uid2: "npm:^1.0.0" - checksum: 10/6932aca4213f1e47c41454cbb9ef1309643ffa14fa3da401f18e397e76f3fa1669be76d96db65dd6435854d7f05b30d3a27f645d10e3c6ed955ad30930d47b3e + checksum: 10/50a7a7d070b77ca26f810177300a4253071b2e0418675bf2f5a17c028fbb677152ba54963b27aa0c6965089b58a9a6c5d45044426828a90db68db761b1fcd4aa + languageName: node + linkType: hard + +"@dependents/detective-less@npm:^5.0.1": + version: 5.0.1 + resolution: "@dependents/detective-less@npm:5.0.1" + dependencies: + gonzales-pe: "npm:^4.3.0" + node-source-walk: "npm:^7.0.1" + checksum: 10/647d4427f6aad119bdfea607ab19ca30be827f7f0462677b5a538d8f8ab4ebda2c05ece60fceb5d3abb093e69ab3393e24a8e966936d2745b07421cfd014b14f languageName: node linkType: hard @@ -9630,13 +8046,48 @@ __metadata: languageName: node linkType: hard -"@electric-sql/pglite@npm:^0.2.14, @electric-sql/pglite@npm:^0.2.15": +"@electric-sql/pglite@npm:^0.2.14": version: 0.2.17 resolution: "@electric-sql/pglite@npm:0.2.17" checksum: 10/cb04d18614c9a3d985fa63f5ca889104eab55b15a9aa569df264e335ea559f2279ee64c1284aa68d5b92ff516dd37ae553299cf55c226a3e541d6c46416c7160 languageName: node linkType: hard +"@electric-sql/pglite@npm:^0.3.0": + version: 0.3.7 + resolution: "@electric-sql/pglite@npm:0.3.7" + checksum: 10/e76b99a06bdba55c3249f2e4fc17fce043460509943b73c14c2ad558854ff4cc752ccb8d438cd7340866b76b3031e3bc37085032decfbaaf871dcd9d07d732df + languageName: node + linkType: hard + +"@emnapi/core@npm:^1.4.5": + version: 1.4.5 + resolution: "@emnapi/core@npm:1.4.5" + dependencies: + "@emnapi/wasi-threads": "npm:1.0.4" + tslib: "npm:^2.4.0" + checksum: 10/412322102dc861e8aa78123ae20560ac980362a220c736fe59ddea3228d490757780ea4cdc3bd54903a5ca2a92085f119e42f2c07f60e2aec2c0b8a69ea094c0 + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.4.5": + version: 1.4.5 + resolution: "@emnapi/runtime@npm:1.4.5" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/1d6f406ff116d2363e60aef3ed49eb8d577387f4941abea508ba376900d8831609d5cce92a58076b1a9613f8e83c75c2e3fea71e4fbcdbe06019876144c2559b + languageName: node + linkType: hard + +"@emnapi/wasi-threads@npm:1.0.4": + version: 1.0.4 + resolution: "@emnapi/wasi-threads@npm:1.0.4" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/86688f416095b59d8d3e5ea2d8b5574a7c180257fe0c067c7a492f3de2cf5ebc2c8b00af17d6341c7555c614266d3987f332015d7ce6e88b234a9a314e66f396 + languageName: node + linkType: hard + "@emotion/babel-plugin@npm:^11.13.5": version: 11.13.5 resolution: "@emotion/babel-plugin@npm:11.13.5" @@ -9827,9 +8278,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/aix-ppc64@npm:0.25.4" +"@esbuild/aix-ppc64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/aix-ppc64@npm:0.25.8" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard @@ -9841,9 +8292,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/android-arm64@npm:0.25.4" +"@esbuild/android-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/android-arm64@npm:0.25.8" conditions: os=android & cpu=arm64 languageName: node linkType: hard @@ -9855,9 +8306,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/android-arm@npm:0.25.4" +"@esbuild/android-arm@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/android-arm@npm:0.25.8" conditions: os=android & cpu=arm languageName: node linkType: hard @@ -9869,9 +8320,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/android-x64@npm:0.25.4" +"@esbuild/android-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/android-x64@npm:0.25.8" conditions: os=android & cpu=x64 languageName: node linkType: hard @@ -9883,9 +8334,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/darwin-arm64@npm:0.25.4" +"@esbuild/darwin-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/darwin-arm64@npm:0.25.8" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -9897,9 +8348,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/darwin-x64@npm:0.25.4" +"@esbuild/darwin-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/darwin-x64@npm:0.25.8" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -9911,9 +8362,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/freebsd-arm64@npm:0.25.4" +"@esbuild/freebsd-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/freebsd-arm64@npm:0.25.8" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard @@ -9925,9 +8376,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/freebsd-x64@npm:0.25.4" +"@esbuild/freebsd-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/freebsd-x64@npm:0.25.8" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard @@ -9939,9 +8390,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-arm64@npm:0.25.4" +"@esbuild/linux-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-arm64@npm:0.25.8" conditions: os=linux & cpu=arm64 languageName: node linkType: hard @@ -9953,9 +8404,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-arm@npm:0.25.4" +"@esbuild/linux-arm@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-arm@npm:0.25.8" conditions: os=linux & cpu=arm languageName: node linkType: hard @@ -9967,9 +8418,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-ia32@npm:0.25.4" +"@esbuild/linux-ia32@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-ia32@npm:0.25.8" conditions: os=linux & cpu=ia32 languageName: node linkType: hard @@ -9981,9 +8432,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-loong64@npm:0.25.4" +"@esbuild/linux-loong64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-loong64@npm:0.25.8" conditions: os=linux & cpu=loong64 languageName: node linkType: hard @@ -9995,9 +8446,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-mips64el@npm:0.25.4" +"@esbuild/linux-mips64el@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-mips64el@npm:0.25.8" conditions: os=linux & cpu=mips64el languageName: node linkType: hard @@ -10009,9 +8460,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-ppc64@npm:0.25.4" +"@esbuild/linux-ppc64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-ppc64@npm:0.25.8" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard @@ -10023,9 +8474,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-riscv64@npm:0.25.4" +"@esbuild/linux-riscv64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-riscv64@npm:0.25.8" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard @@ -10037,9 +8488,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-s390x@npm:0.25.4" +"@esbuild/linux-s390x@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-s390x@npm:0.25.8" conditions: os=linux & cpu=s390x languageName: node linkType: hard @@ -10051,16 +8502,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-x64@npm:0.25.4" +"@esbuild/linux-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/linux-x64@npm:0.25.8" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/netbsd-arm64@npm:0.25.4" +"@esbuild/netbsd-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/netbsd-arm64@npm:0.25.8" conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard @@ -10072,16 +8523,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/netbsd-x64@npm:0.25.4" +"@esbuild/netbsd-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/netbsd-x64@npm:0.25.8" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/openbsd-arm64@npm:0.25.4" +"@esbuild/openbsd-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/openbsd-arm64@npm:0.25.8" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard @@ -10093,13 +8544,20 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/openbsd-x64@npm:0.25.4" +"@esbuild/openbsd-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/openbsd-x64@npm:0.25.8" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard +"@esbuild/openharmony-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/openharmony-arm64@npm:0.25.8" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/sunos-x64@npm:0.21.5" @@ -10107,9 +8565,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/sunos-x64@npm:0.25.4" +"@esbuild/sunos-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/sunos-x64@npm:0.25.8" conditions: os=sunos & cpu=x64 languageName: node linkType: hard @@ -10121,9 +8579,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/win32-arm64@npm:0.25.4" +"@esbuild/win32-arm64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/win32-arm64@npm:0.25.8" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -10135,9 +8593,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/win32-ia32@npm:0.25.4" +"@esbuild/win32-ia32@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/win32-ia32@npm:0.25.8" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -10149,9 +8607,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/win32-x64@npm:0.25.4" +"@esbuild/win32-x64@npm:0.25.8": + version: 0.25.8 + resolution: "@esbuild/win32-x64@npm:0.25.8" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -10329,14 +8787,16 @@ __metadata: languageName: node linkType: hard -"@gerrit0/mini-shiki@npm:^1.24.0": - version: 1.27.2 - resolution: "@gerrit0/mini-shiki@npm:1.27.2" +"@gerrit0/mini-shiki@npm:^3.9.0": + version: 3.9.1 + resolution: "@gerrit0/mini-shiki@npm:3.9.1" dependencies: - "@shikijs/engine-oniguruma": "npm:^1.27.2" - "@shikijs/types": "npm:^1.27.2" - "@shikijs/vscode-textmate": "npm:^10.0.1" - checksum: 10/ee4c80d94da587bd0dfe262f92ce81be0a1aa86dd0ef88e708839751af552451dd113f139b556be4f18747bf883d81ee85a119e535852114a71efda1cb3c5be5 + "@shikijs/engine-oniguruma": "npm:^3.9.1" + "@shikijs/langs": "npm:^3.9.1" + "@shikijs/themes": "npm:^3.9.1" + "@shikijs/types": "npm:^3.9.1" + "@shikijs/vscode-textmate": "npm:^10.0.2" + checksum: 10/4ef9c7f24866d2d1e802df085698140fd359d144eafc081d9599b8be73f3606fc1c1870ec5ce240e201a7a34cd959e15cff322469d211cfb299e34733e65c1ed languageName: node linkType: hard @@ -10374,7 +8834,7 @@ __metadata: languageName: node linkType: hard -"@gitbeaker/requester-utils@npm:^41.3.0": +"@gitbeaker/requester-utils@npm:^41.2.0, @gitbeaker/requester-utils@npm:^41.3.0": version: 41.3.0 resolution: "@gitbeaker/requester-utils@npm:41.3.0" dependencies: @@ -10407,14 +8867,14 @@ __metadata: linkType: hard "@google-cloud/cloud-sql-connector@npm:^1.4.0": - version: 1.8.0 - resolution: "@google-cloud/cloud-sql-connector@npm:1.8.0" + version: 1.8.2 + resolution: "@google-cloud/cloud-sql-connector@npm:1.8.2" dependencies: - "@googleapis/sqladmin": "npm:^28.0.0" - gaxios: "npm:^6.1.1" - google-auth-library: "npm:^9.2.0" + "@googleapis/sqladmin": "npm:^31.0.0" + gaxios: "npm:^7.0.0" + google-auth-library: "npm:^10.0.0" p-throttle: "npm:^7.0.0" - checksum: 10/a2cd85beb822a37e1eaabd556e2f540f89c41a1614e520b8c120b8107d7edb3c335dcbf62fc34dd8973ae091a2a06882780ae6510c45f2b702a59df4953ba427 + checksum: 10/a9e8113f6b252e93ebdac2901c47584fa2c50d4ff0ad7ce1e1beb8ac8c710b24543927b2231eeb0f74eb467e122ec313ba3fb845d6f5878d35760eb3021aa6ec languageName: node linkType: hard @@ -10428,15 +8888,15 @@ __metadata: linkType: hard "@google-cloud/firestore@npm:^7.0.0": - version: 7.11.0 - resolution: "@google-cloud/firestore@npm:7.11.0" + version: 7.11.3 + resolution: "@google-cloud/firestore@npm:7.11.3" dependencies: "@opentelemetry/api": "npm:^1.3.0" fast-deep-equal: "npm:^3.1.1" functional-red-black-tree: "npm:^1.0.1" google-gax: "npm:^4.3.3" protobufjs: "npm:^7.2.6" - checksum: 10/ae0e42ffee1e5cf7160149e1a581c0ceee702ea34d87d7212f7ffa326d9101f0aa2e9c5d37527a7f75864ac174cf32b50f1c593a81d02476ca4c8b8e85cba3bc + checksum: 10/1d512e236b315daeb1cf562988a4b200e75abb7ba64a0d2be71815deae5662e81ca501a7dfaf9b9e81396e0d1f84f6f5b063da0029a98be9d7dfbc0afc2d06e0 languageName: node linkType: hard @@ -10516,12 +8976,12 @@ __metadata: languageName: node linkType: hard -"@googleapis/sqladmin@npm:^28.0.0": - version: 28.0.0 - resolution: "@googleapis/sqladmin@npm:28.0.0" +"@googleapis/sqladmin@npm:^31.0.0": + version: 31.1.0 + resolution: "@googleapis/sqladmin@npm:31.1.0" dependencies: - googleapis-common: "npm:^7.0.0" - checksum: 10/ee4074b63d45276e0513a75b461bbf3d39abcb894e5eb660f0f28a9d0665c6638079958f04cb171767c69bcbb324ecdd2033199e6ecff61bf10d6a09adecdd16 + googleapis-common: "npm:^8.0.2-rc.0" + checksum: 10/720365e99bff8b7de161dc629ffb097624b98a3ff42febca47d9a9c73480b66faff39f3601614dce3178b3acf9d49c6e006633e1847cceaa7d21bc8437fa1936 languageName: node linkType: hard @@ -11140,47 +9600,40 @@ __metadata: languageName: unknown linkType: soft -"@internationalized/date@npm:^3.7.0": - version: 3.7.0 - resolution: "@internationalized/date@npm:3.7.0" +"@internationalized/date@npm:^3.8.2": + version: 3.8.2 + resolution: "@internationalized/date@npm:3.8.2" dependencies: "@swc/helpers": "npm:^0.5.0" - checksum: 10/59adffb57f1391e17bfe9b2e9b1a71c0957a1835b0176a86ad5a99fd2f2919f094d3f553fb39be157d84c315a88917e0eda779c9101026e3bfac217ace18a559 + checksum: 10/ca281d8785acff520ba4782d4eb0c542bb6c2783dbd3eb5457aeddbb68b9674f860fc00c6dd467f39c12eff9a04381742b07f0b9e210a115c094507e046d7b5a languageName: node linkType: hard -"@internationalized/message@npm:^3.1.6": - version: 3.1.6 - resolution: "@internationalized/message@npm:3.1.6" +"@internationalized/message@npm:^3.1.8": + version: 3.1.8 + resolution: "@internationalized/message@npm:3.1.8" dependencies: "@swc/helpers": "npm:^0.5.0" intl-messageformat: "npm:^10.1.0" - checksum: 10/31611a81bba7c0b18fad02ccb363632cbef3a0cc6e6e564578dfc7ae42cfde48b0ec18b89bff24390bb748df63d40cd2028483068e7d1492aee0533648a71e04 + checksum: 10/8f27a31f5d1eef7084447ed141e896e12cc19d786a1346ba60137de5b8bcc58a9da978d79954e2a74302aa673f942fb772130ebd6195565e33db30bd7eb4ef47 languageName: node linkType: hard -"@internationalized/number@npm:^3.6.0": - version: 3.6.0 - resolution: "@internationalized/number@npm:3.6.0" +"@internationalized/number@npm:^3.6.3": + version: 3.6.3 + resolution: "@internationalized/number@npm:3.6.3" dependencies: "@swc/helpers": "npm:^0.5.0" - checksum: 10/76428f75fd289008580108beef02816f94aaca37d48c9e26c2274fdbb81e8f32c4247ff624b8f854384b18ce2556cc8d817ccf1bc39b0fd87b0da1f3d911209b + checksum: 10/b6f1793d12f89aca4c5bd88b60643739c4b02cade50a42c61192a2a5e0a913088ee35e4013b7ef3f675bdc919ebaf9a1631f1c40b1ceffd998f3fc14201a01c3 languageName: node linkType: hard -"@internationalized/string@npm:^3.2.5": - version: 3.2.5 - resolution: "@internationalized/string@npm:3.2.5" +"@internationalized/string@npm:^3.2.7": + version: 3.2.7 + resolution: "@internationalized/string@npm:3.2.7" dependencies: "@swc/helpers": "npm:^0.5.0" - checksum: 10/7a20359796545cd5724aa546018554b81ea168c58738e98884c54ef0de2fba7b802504c79678d690709b0a435478912aa771c426feed72aae1d93ee782b7ac5d - languageName: node - linkType: hard - -"@ioredis/commands@npm:^1.1.1": - version: 1.1.1 - resolution: "@ioredis/commands@npm:1.1.1" - checksum: 10/eab61d07f6c40901a24cce1f7e8676268753243c0ee6ee34981cf63b404608ac7c7acd41dd550018d2cd5e62741446641135d760e61afbf0a86db4db2687aac3 + checksum: 10/38b54817cf125ba88d1136a6bca4fb57c46672d26d21490f838efe928049546800df6d9c8048411696455fc8caacb8ac23c2de2a1b61f2258b1302c1c97cc128 languageName: node linkType: hard @@ -11191,6 +9644,22 @@ __metadata: languageName: node linkType: hard +"@isaacs/balanced-match@npm:^4.0.1": + version: 4.0.1 + resolution: "@isaacs/balanced-match@npm:4.0.1" + checksum: 10/102fbc6d2c0d5edf8f6dbf2b3feb21695a21bc850f11bc47c4f06aa83bd8884fde3fe9d6d797d619901d96865fdcb4569ac2a54c937992c48885c5e3d9967fe8 + languageName: node + linkType: hard + +"@isaacs/brace-expansion@npm:^5.0.0": + version: 5.0.0 + resolution: "@isaacs/brace-expansion@npm:5.0.0" + dependencies: + "@isaacs/balanced-match": "npm:^4.0.1" + checksum: 10/cf3b7f206aff12128214a1df764ac8cdbc517c110db85249b945282407e3dfc5c6e66286383a7c9391a059fc8e6e6a8ca82262fc9d2590bd615376141fbebd2d + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -11541,14 +10010,30 @@ __metadata: languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.2, @jridgewell/gen-mapping@npm:^0.3.5": - version: 0.3.5 - resolution: "@jridgewell/gen-mapping@npm:0.3.5" +"@joshwooding/vite-plugin-react-docgen-typescript@npm:0.5.0": + version: 0.5.0 + resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.5.0" dependencies: - "@jridgewell/set-array": "npm:^1.2.1" - "@jridgewell/sourcemap-codec": "npm:^1.4.10" + glob: "npm:^10.0.0" + magic-string: "npm:^0.27.0" + react-docgen-typescript: "npm:^2.2.2" + peerDependencies: + typescript: ">= 4.3.x" + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/1dcb03f2df1723799a7a9c75ac8360990f75c44fd2425d2d52a9e21882fc3054d372892ab1cad0927864d8a934ad5b347f4ae00b01785649e2a8f1c4b861aa67 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.2": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10/81587b3c4dd8e6c60252122937cea0c637486311f4ed208b52b62aae2e7a87598f63ec330e6cd0984af494bfb16d3f0d60d3b21d7e5b4aedd2602ff3fe9d32e2 + checksum: 10/902f8261dcf450b4af7b93f9656918e02eec80a2169e155000cb2059f90113dd98f3ccf6efc6072cee1dd84cac48cade51da236972d942babc40e4c23da4d62a languageName: node linkType: hard @@ -11559,13 +10044,6 @@ __metadata: languageName: node linkType: hard -"@jridgewell/set-array@npm:^1.2.1": - version: 1.2.1 - resolution: "@jridgewell/set-array@npm:1.2.1" - checksum: 10/832e513a85a588f8ed4f27d1279420d8547743cc37fcad5a5a76fc74bb895b013dfe614d0eed9cb860048e6546b798f8f2652020b4b2ba0561b05caa8c654b10 - languageName: node - linkType: hard - "@jridgewell/source-map@npm:^0.3.3": version: 0.3.5 resolution: "@jridgewell/source-map@npm:0.3.5" @@ -11576,10 +10054,10 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15, @jridgewell/sourcemap-codec@npm:^1.5.0": - version: 1.5.0 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" - checksum: 10/4ed6123217569a1484419ac53f6ea0d9f3b57e5b57ab30d7c267bdb27792a27eb0e4b08e84a2680aa55cc2f2b411ffd6ec3db01c44fdc6dc43aca4b55f8374fd +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.13, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15, @jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10/5d9d207b462c11e322d71911e55e21a4e2772f71ffe8d6f1221b8eb5ae6774458c1d242f897fb0814e8714ca9a6b498abfa74dfe4f434493342902b1a48b33a5 languageName: node linkType: hard @@ -11593,13 +10071,13 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.9": - version: 0.3.25 - resolution: "@jridgewell/trace-mapping@npm:0.3.25" +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28, @jridgewell/trace-mapping@npm:^0.3.9": + version: 0.3.30 + resolution: "@jridgewell/trace-mapping@npm:0.3.30" dependencies: "@jridgewell/resolve-uri": "npm:^3.1.0" "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10/dced32160a44b49d531b80a4a2159dceab6b3ddf0c8e95a0deae4b0e894b172defa63d5ac52a19c2068e1fe7d31ea4ba931fbeec103233ecb4208953967120fc + checksum: 10/f9fabe1122058f4fedc242bdc43fcaacb6b222c6fb712b7904c37704dcb16e50e07ca137ff99043da44292b18a8720296ff97f7703e960678b8ef51d0db4d250 languageName: node linkType: hard @@ -11644,6 +10122,73 @@ __metadata: languageName: node linkType: hard +"@jsonjoy.com/base64@npm:^1.1.2": + version: 1.1.2 + resolution: "@jsonjoy.com/base64@npm:1.1.2" + peerDependencies: + tslib: 2 + checksum: 10/d76bb58eff841c090d9bf69a073611ffa73c40a664ccbcea689f65961f57d7b24051269d06b437e4f6204285d6ba92f50f587c5e95c5f9e4f10b36a2ed4cd0c8 + languageName: node + linkType: hard + +"@jsonjoy.com/buffers@npm:^1.0.0": + version: 1.0.0 + resolution: "@jsonjoy.com/buffers@npm:1.0.0" + peerDependencies: + tslib: 2 + checksum: 10/3347a16a555398c19265203877b8e325be5facc1d875c0e85db0cc20b53418302257ed5af15bf53b84ff8337bd024a10be25ff3a02ebc54ae8e643577ca29e63 + languageName: node + linkType: hard + +"@jsonjoy.com/codegen@npm:^1.0.0": + version: 1.0.0 + resolution: "@jsonjoy.com/codegen@npm:1.0.0" + peerDependencies: + tslib: 2 + checksum: 10/a0afb03d2af4fbc1377c547e507f5db99a25f515d8c4b6b2cef1ff28145ac59fff12b6e1f41f9734cb62ea5619e7f9be1acd0908305d6f4176898ee534ee9a64 + languageName: node + linkType: hard + +"@jsonjoy.com/json-pack@npm:^1.0.3": + version: 1.10.0 + resolution: "@jsonjoy.com/json-pack@npm:1.10.0" + dependencies: + "@jsonjoy.com/base64": "npm:^1.1.2" + "@jsonjoy.com/buffers": "npm:^1.0.0" + "@jsonjoy.com/codegen": "npm:^1.0.0" + "@jsonjoy.com/json-pointer": "npm:^1.0.1" + "@jsonjoy.com/util": "npm:^1.9.0" + hyperdyperid: "npm:^1.2.0" + thingies: "npm:^2.5.0" + peerDependencies: + tslib: 2 + checksum: 10/5ad1eb008cf5f681dbc1301f5a7c33ca3a101d22773dbd2a9db4b02e5847c2f8b0c5f074c69f4ced5c24b3dc1dd85ddbc1688f0002fee9416869c579c34c4a8e + languageName: node + linkType: hard + +"@jsonjoy.com/json-pointer@npm:^1.0.1": + version: 1.0.1 + resolution: "@jsonjoy.com/json-pointer@npm:1.0.1" + dependencies: + "@jsonjoy.com/util": "npm:^1.3.0" + peerDependencies: + tslib: 2 + checksum: 10/ff15df95df38677a09fe2155cf4521844c9f0653c77a7090bb6e726d275fb78ab6c3543c4ebdc14aff07e44da08b1545631a63e42e217c18059194957e060ed9 + languageName: node + linkType: hard + +"@jsonjoy.com/util@npm:^1.3.0, @jsonjoy.com/util@npm:^1.9.0": + version: 1.9.0 + resolution: "@jsonjoy.com/util@npm:1.9.0" + dependencies: + "@jsonjoy.com/buffers": "npm:^1.0.0" + "@jsonjoy.com/codegen": "npm:^1.0.0" + peerDependencies: + tslib: 2 + checksum: 10/1a6e5301d725a7161b93ff707eb1a954bf4552a2fa96eee9a960d3ae3ed5f993d18b56dcff29e98036341a5968c5d1b2dfe21f76695390e7f0d89b81f24c85e0 + languageName: node + linkType: hard + "@juggle/resize-observer@npm:^3.3.1": version: 3.3.1 resolution: "@juggle/resize-observer@npm:3.3.1" @@ -11651,62 +10196,46 @@ __metadata: languageName: node linkType: hard -"@keyv/memcache@npm:^1.3.5": - version: 1.3.7 - resolution: "@keyv/memcache@npm:1.3.7" - dependencies: - json-buffer: "npm:^3.0.1" - memjs: "npm:^1.3.1" - checksum: 10/61c836c1b0c264c35281a38dbc380a102b2899eab46226a0e31614d3d7c4708ecdb06d8858f711893212ae5b357a79213c7a7b7a81b6196e56dd889dd1ca498b - languageName: node - linkType: hard - "@keyv/memcache@npm:^2.0.1": - version: 2.0.1 - resolution: "@keyv/memcache@npm:2.0.1" + version: 2.0.2 + resolution: "@keyv/memcache@npm:2.0.2" dependencies: - "@keyv/serialize": "npm:*" + "@keyv/serialize": "npm:^1.0.3" buffer: "npm:^6.0.3" memjs: "npm:^1.3.2" - checksum: 10/618fd2441cb7e0b71c6af37b87bddc39d3439c9863def47cff8c75c9c05ca45e035bfc1bfa140a661befce5a06a491f25388aab6d330c0eaefe7b87534274c48 - languageName: node - linkType: hard - -"@keyv/redis@npm:^2.5.3": - version: 2.8.5 - resolution: "@keyv/redis@npm:2.8.5" - dependencies: - ioredis: "npm:^5.4.1" - checksum: 10/7243abf11ab698b3f97f0c86bd56f1ec79a26b0e422b23c026d8dd2ad9f629852eaf683354c863508d142b934c73b985cd01f62583b07dfcd2b6842b52b853c3 + peerDependencies: + keyv: ^5.3.4 + checksum: 10/8328bfa4a348d26c1aa1a563561a50114f75288af5fe18e8dc2ce6ff2f7f6aa63c27f8293c5e8b35bc0dcc9740159e56d8ff1b4015fc6a56e18d62850a8a9b83 languageName: node linkType: hard "@keyv/redis@npm:^4.0.1": - version: 4.1.0 - resolution: "@keyv/redis@npm:4.1.0" + version: 4.4.1 + resolution: "@keyv/redis@npm:4.4.1" dependencies: + "@redis/client": "npm:^1.6.0" cluster-key-slot: "npm:^1.1.2" - keyv: "npm:^5.2.1" - redis: "npm:^4.7.0" - checksum: 10/5f1d6c6596829bbae5c6369dff6e70de4d7f91c7c8312585b2bef336638582c9969c792a4b328ee07b80b50136e67769fffd844e237d4db32a44ea7de20fb3f5 + peerDependencies: + keyv: ^5.3.4 + checksum: 10/85bf5830d5a19f45fbba864f7eb830cff0059f3f7c59c711a65b439821008a3321a94fb06c4d1f09295f0f233422586458652d266696c0925b5c8d1d695700ec languageName: node linkType: hard -"@keyv/serialize@npm:*, @keyv/serialize@npm:^1.0.2": - version: 1.0.2 - resolution: "@keyv/serialize@npm:1.0.2" +"@keyv/serialize@npm:^1.0.3": + version: 1.0.3 + resolution: "@keyv/serialize@npm:1.0.3" dependencies: buffer: "npm:^6.0.3" - checksum: 10/6a42a5778a6b4542f6903ba7e6a17c5bd116441798d75c95fba9908c76c7606db527fad710b5c54abc6175e49b1bbaaafe3b836ad4b91e1af701394134f1d504 + checksum: 10/d6a9194dd781bc26cc4d55f392d843810c1fdc0da81e69203e633cb289fc0a8edc8bc6466f66c4cbb55da0a5b405e89f14a68b48d6e73919ae82f8249fb5e444 languageName: node linkType: hard "@keyv/valkey@npm:^1.0.1": - version: 1.0.3 - resolution: "@keyv/valkey@npm:1.0.3" + version: 1.0.8 + resolution: "@keyv/valkey@npm:1.0.8" dependencies: - iovalkey: "npm:^0.3.1" - checksum: 10/ff6ba62e4d19c426e45a1437fe215ed2baddc58e811d97507dd75ead0058c3105d679c8c7c4241ddf732abe56320357c2e568c014620e50d7c0eaef1f2528b88 + iovalkey: "npm:^0.3.3" + checksum: 10/bca81d5603dab9f5f5c1759c25961c5c3e837b3de56775436a8a4b851c08acdb8689bd7db58d3c6bcd0ceead84241f4276b5f7638317ba75bbb2cb4c4ac71c02 languageName: node linkType: hard @@ -11750,32 +10279,6 @@ __metadata: languageName: node linkType: hard -"@kubernetes/client-node@npm:0.20.0": - version: 0.20.0 - resolution: "@kubernetes/client-node@npm:0.20.0" - dependencies: - "@types/js-yaml": "npm:^4.0.1" - "@types/node": "npm:^20.1.1" - "@types/request": "npm:^2.47.1" - "@types/ws": "npm:^8.5.3" - byline: "npm:^5.0.0" - isomorphic-ws: "npm:^5.0.0" - js-yaml: "npm:^4.1.0" - jsonpath-plus: "npm:^7.2.0" - openid-client: "npm:^5.3.0" - request: "npm:^2.88.0" - rfc4648: "npm:^1.3.0" - stream-buffers: "npm:^3.0.2" - tar: "npm:^6.1.11" - tslib: "npm:^2.4.1" - ws: "npm:^8.11.0" - dependenciesMeta: - openid-client: - optional: true - checksum: 10/1d53a062f4d9a574b6400fa04dbbaea090aa2c69a8d65d609b90d3d606f384f60c191d2f969602a29eaaff1eebf30b4cbbc56a479910bd85c98205c4d3219e00 - languageName: node - linkType: hard - "@kubernetes/client-node@npm:1.1.2": version: 1.1.2 resolution: "@kubernetes/client-node@npm:1.1.2" @@ -11936,7 +10439,7 @@ __metadata: languageName: node linkType: hard -"@material-ui/icons@npm:^4.11.2, @material-ui/icons@npm:^4.11.3, @material-ui/icons@npm:^4.9.1": +"@material-ui/icons@npm:^4.11.3, @material-ui/icons@npm:^4.9.1": version: 4.11.3 resolution: "@material-ui/icons@npm:4.11.3" dependencies: @@ -12198,6 +10701,25 @@ __metadata: languageName: node linkType: hard +"@modelcontextprotocol/sdk@npm:^1.12.3": + version: 1.13.1 + resolution: "@modelcontextprotocol/sdk@npm:1.13.1" + dependencies: + ajv: "npm:^6.12.6" + content-type: "npm:^1.0.5" + cors: "npm:^2.8.5" + cross-spawn: "npm:^7.0.5" + eventsource: "npm:^3.0.2" + express: "npm:^5.0.1" + express-rate-limit: "npm:^7.5.0" + pkce-challenge: "npm:^5.0.0" + raw-body: "npm:^3.0.0" + zod: "npm:^3.23.8" + zod-to-json-schema: "npm:^3.24.1" + checksum: 10/b516d72e1cd14c67c8a2e5cb95fcc1c03c50be989850e3e963a7ed11000acb604e65efeaad47ea93c847130536ee51859a8d34e6dbe99d408e1a24224592e57f + languageName: node + linkType: hard + "@module-federation/bridge-react-webpack-plugin@npm:0.9.1": version: 0.9.1 resolution: "@module-federation/bridge-react-webpack-plugin@npm:0.9.1" @@ -12284,10 +10806,10 @@ __metadata: languageName: node linkType: hard -"@module-federation/error-codes@npm:0.13.0": - version: 0.13.0 - resolution: "@module-federation/error-codes@npm:0.13.0" - checksum: 10/42a826d374f8451c320236f73f1337f69449d1875ee1141e70a0150c4820cd2c0a2997bb134aa01f39e405fe7142d5998b008c8ea32d493e9a507ae7c29c3ec9 +"@module-federation/error-codes@npm:0.17.1": + version: 0.17.1 + resolution: "@module-federation/error-codes@npm:0.17.1" + checksum: 10/5f5f02a90a423479c84e4ff4398a3a9e31b66bd545e7c978ecb8a417f33162b86e749356baab14c006e741c9cebae549335a4c99e94ce7ef54210269fdf74f7f languageName: node linkType: hard @@ -12355,13 +10877,13 @@ __metadata: languageName: node linkType: hard -"@module-federation/runtime-core@npm:0.13.0": - version: 0.13.0 - resolution: "@module-federation/runtime-core@npm:0.13.0" +"@module-federation/runtime-core@npm:0.17.1": + version: 0.17.1 + resolution: "@module-federation/runtime-core@npm:0.17.1" dependencies: - "@module-federation/error-codes": "npm:0.13.0" - "@module-federation/sdk": "npm:0.13.0" - checksum: 10/04d23270be780dc3626a2391d14fe7b794aed9dc2e31b02339d399e9369b21cdc4c80b86edd14a06b0142fe711539284b381a2ea4894898923d3b457a472e569 + "@module-federation/error-codes": "npm:0.17.1" + "@module-federation/sdk": "npm:0.17.1" + checksum: 10/b0c945379bde13af84ceb833e3bfe3c8cf11fd265af0ad7640a1506017529458f408a4a3f1bd0f4b5983da71438913d5c25ed25e20908eb1f789bd1483616650 languageName: node linkType: hard @@ -12375,13 +10897,13 @@ __metadata: languageName: node linkType: hard -"@module-federation/runtime-tools@npm:0.13.0": - version: 0.13.0 - resolution: "@module-federation/runtime-tools@npm:0.13.0" +"@module-federation/runtime-tools@npm:0.17.1": + version: 0.17.1 + resolution: "@module-federation/runtime-tools@npm:0.17.1" dependencies: - "@module-federation/runtime": "npm:0.13.0" - "@module-federation/webpack-bundler-runtime": "npm:0.13.0" - checksum: 10/deb7abf5e44714a19766ec6009efd656e9e4da8e1ddeea775fa8d2d4b3d2e165629163ed86c83a003a4f4e76f17649b9d889582ce0c5d35a7a2eacf8da5bfb2e + "@module-federation/runtime": "npm:0.17.1" + "@module-federation/webpack-bundler-runtime": "npm:0.17.1" + checksum: 10/2e183e357b644dbe015d0e51df3fe601852ca79ffe3a30c582eee7a2050d7600eb3253f5de15e476c60741d0a1dd70add1ade7b5a3537cd2ee12bfee286284ea languageName: node linkType: hard @@ -12395,14 +10917,14 @@ __metadata: languageName: node linkType: hard -"@module-federation/runtime@npm:0.13.0": - version: 0.13.0 - resolution: "@module-federation/runtime@npm:0.13.0" +"@module-federation/runtime@npm:0.17.1": + version: 0.17.1 + resolution: "@module-federation/runtime@npm:0.17.1" dependencies: - "@module-federation/error-codes": "npm:0.13.0" - "@module-federation/runtime-core": "npm:0.13.0" - "@module-federation/sdk": "npm:0.13.0" - checksum: 10/a79accc2a44e2a8b5d6f5025582520c8904776c1b8fabe62554c20baf4602937a0bfdde2bf69c6b29cefb3e108ba8cc6b00c2dc78751da3f9dfd75722cede4af + "@module-federation/error-codes": "npm:0.17.1" + "@module-federation/runtime-core": "npm:0.17.1" + "@module-federation/sdk": "npm:0.17.1" + checksum: 10/f5405968dff4fa2cf510127701ec1722105f44298fd09eafeecead450b7bb95a05450749157fe2fc39caf6241bec9e45caa9a55375b48e7f195db84799a8df0c languageName: node linkType: hard @@ -12417,10 +10939,10 @@ __metadata: languageName: node linkType: hard -"@module-federation/sdk@npm:0.13.0": - version: 0.13.0 - resolution: "@module-federation/sdk@npm:0.13.0" - checksum: 10/aa08ca793fa6f0912d06f02592337340d973672a79f632b278527f72cc76ee19d3a95579ecb930ac355120b89c13c0260dc37fe08de1854f99626a796e5a6a7f +"@module-federation/sdk@npm:0.17.1": + version: 0.17.1 + resolution: "@module-federation/sdk@npm:0.17.1" + checksum: 10/daaaa49ed900c00a69641130cf673ad5d5b8623d82fb4bd03a67c839a6da760a0a5ae29b836ba66eeb95ee5392e558588ffd987a2c00b05c2b0a7c5039ed042d languageName: node linkType: hard @@ -12442,13 +10964,13 @@ __metadata: languageName: node linkType: hard -"@module-federation/webpack-bundler-runtime@npm:0.13.0": - version: 0.13.0 - resolution: "@module-federation/webpack-bundler-runtime@npm:0.13.0" +"@module-federation/webpack-bundler-runtime@npm:0.17.1": + version: 0.17.1 + resolution: "@module-federation/webpack-bundler-runtime@npm:0.17.1" dependencies: - "@module-federation/runtime": "npm:0.13.0" - "@module-federation/sdk": "npm:0.13.0" - checksum: 10/692fdbd6d99d5dac9420691f581eebbb7d0538a7b01e6f7d6d2e4c3ec23798962f693775a6239298e53daf4b7cb4b5cac4775ce15625257c7c12aa7d6aa5b398 + "@module-federation/runtime": "npm:0.17.1" + "@module-federation/sdk": "npm:0.17.1" + checksum: 10/72e5030529dbc53df6271fa78bdb63976d0601fe9fde5105f8a7325e0fa296bc35277b9b084e52995cd314b89e12d33f8b869c1d63a13231c2948d4c741e72fd languageName: node linkType: hard @@ -12553,9 +11075,9 @@ __metadata: languageName: node linkType: hard -"@mswjs/interceptors@npm:^0.37.0": - version: 0.37.1 - resolution: "@mswjs/interceptors@npm:0.37.1" +"@mswjs/interceptors@npm:^0.39.1": + version: 0.39.2 + resolution: "@mswjs/interceptors@npm:0.39.2" dependencies: "@open-draft/deferred-promise": "npm:^2.2.0" "@open-draft/logger": "npm:^0.3.0" @@ -12563,7 +11085,7 @@ __metadata: is-node-process: "npm:^1.2.0" outvariant: "npm:^1.4.3" strict-event-emitter: "npm:^0.5.1" - checksum: 10/332d8aa50beb4834ccbda6a800ca00b1204adc0eba23e1c1f7bb9f4e564a92707e563f7a2424d4a8607404ec91424e5d8c34a87c250b191ca7b24dff12eba2c5 + checksum: 10/faaa95d636363a197f125c32066457fa74d5063d8ccae4c9c0e0510179060d92b1faf8640df45a0623e0bf42a30d610c83364a58e0eb0ca412c87b2e835936c1 languageName: node linkType: hard @@ -12743,27 +11265,40 @@ __metadata: languageName: node linkType: hard -"@nestjs/axios@npm:3.1.1": - version: 3.1.1 - resolution: "@nestjs/axios@npm:3.1.1" - peerDependencies: - "@nestjs/common": ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 - axios: ^1.3.1 - rxjs: ^6.0.0 || ^7.0.0 - checksum: 10/2022e63016577f9448837591ec5c700c724bf48763ec3006f41acd3edba4e18d5508bcf5f513dfdb8c0f25eb8c6c84aa6378ed6e3ed6abb4aab3f8b8e851db96 +"@napi-rs/wasm-runtime@npm:^1.0.1": + version: 1.0.1 + resolution: "@napi-rs/wasm-runtime@npm:1.0.1" + dependencies: + "@emnapi/core": "npm:^1.4.5" + "@emnapi/runtime": "npm:^1.4.5" + "@tybys/wasm-util": "npm:^0.10.0" + checksum: 10/8ed2a1f1246ecbef04b73e06c994f635ebb8c7b929620f28859c49cb7e373adee4fd1b9529bab286171c0ef0f668331d683bcf30ab193e5bf05a04b4e07cd56a languageName: node linkType: hard -"@nestjs/common@npm:10.4.6": - version: 10.4.6 - resolution: "@nestjs/common@npm:10.4.6" +"@nestjs/axios@npm:4.0.0": + version: 4.0.0 + resolution: "@nestjs/axios@npm:4.0.0" + peerDependencies: + "@nestjs/common": ^10.0.0 || ^11.0.0 + axios: ^1.3.1 + rxjs: ^7.0.0 + checksum: 10/9a61ac8a2fdbf304961696148945ba9e19c0ed73256767b0a643bbafe77106b2f738cb2f35f2fc63bff09a8abcd18365d2f8c772484e2fdd70b455bceb7b5822 + languageName: node + linkType: hard + +"@nestjs/common@npm:11.1.3": + version: 11.1.3 + resolution: "@nestjs/common@npm:11.1.3" dependencies: + file-type: "npm:21.0.0" iterare: "npm:1.2.1" - tslib: "npm:2.7.0" + load-esm: "npm:1.0.2" + tslib: "npm:2.8.1" uid: "npm:2.0.2" peerDependencies: - class-transformer: "*" - class-validator: "*" + class-transformer: ">=0.4.1" + class-validator: ">=0.13.2" reflect-metadata: ^0.1.12 || ^0.2.0 rxjs: ^7.1.0 peerDependenciesMeta: @@ -12771,25 +11306,25 @@ __metadata: optional: true class-validator: optional: true - checksum: 10/bf1887715a8612e207c621a00761c0caed5c32f3fce9f6abd7de6a9c6f0217f825696f4dde218952017967493e47bd6fcc5b85d6f5d7fe82e31be9811179d2f5 + checksum: 10/b1c9699cdf9cee96ed0178e5eed3b1829c210bbdbd703bb9e1e0def2c7456273d73329635439b16164e43de8a8d54dd5b7d1dd5dba65417d746bd731b8c5d685 languageName: node linkType: hard -"@nestjs/core@npm:10.4.6": - version: 10.4.6 - resolution: "@nestjs/core@npm:10.4.6" +"@nestjs/core@npm:11.1.3": + version: 11.1.3 + resolution: "@nestjs/core@npm:11.1.3" dependencies: - "@nuxtjs/opencollective": "npm:0.3.2" + "@nuxt/opencollective": "npm:0.4.1" fast-safe-stringify: "npm:2.1.1" iterare: "npm:1.2.1" - path-to-regexp: "npm:3.3.0" - tslib: "npm:2.7.0" + path-to-regexp: "npm:8.2.0" + tslib: "npm:2.8.1" uid: "npm:2.0.2" peerDependencies: - "@nestjs/common": ^10.0.0 - "@nestjs/microservices": ^10.0.0 - "@nestjs/platform-express": ^10.0.0 - "@nestjs/websockets": ^10.0.0 + "@nestjs/common": ^11.0.0 + "@nestjs/microservices": ^11.0.0 + "@nestjs/platform-express": ^11.0.0 + "@nestjs/websockets": ^11.0.0 reflect-metadata: ^0.1.12 || ^0.2.0 rxjs: ^7.1.0 peerDependenciesMeta: @@ -12799,7 +11334,7 @@ __metadata: optional: true "@nestjs/websockets": optional: true - checksum: 10/09dd2b2a44a4c3cb546db78027ec76fbd6b12510b3013217d012b670abcbf6f7f021a91b0a59fe65eb5bc0cd53634edb9daebb61341d1d10e44476087759d21a + checksum: 10/b46a9f877170f7e96429da5970684525277bcb341f3a24032b6748ad503b5c7164b77410daf9c0f3ca99b5f6b99985d5eb739501cfc01c941218397f32f2d095 languageName: node linkType: hard @@ -13086,6 +11621,17 @@ __metadata: languageName: node linkType: hard +"@nuxt/opencollective@npm:0.4.1": + version: 0.4.1 + resolution: "@nuxt/opencollective@npm:0.4.1" + dependencies: + consola: "npm:^3.2.3" + bin: + opencollective: bin/opencollective.js + checksum: 10/37739657e87196c7f1019a76bc33dc6e33b028eeeec43ffbf29c821e89bf5c170514e9e224456e1da85d95859ba63a3a36bd7ce1b82f2d366f7be3d6299e7631 + languageName: node + linkType: hard + "@nuxtjs/opencollective@npm:0.3.2": version: 0.3.2 resolution: "@nuxtjs/opencollective@npm:0.3.2" @@ -13301,8 +11847,8 @@ __metadata: linkType: hard "@octokit/core@npm:^5.0.0, @octokit/core@npm:^5.2.0": - version: 5.2.1 - resolution: "@octokit/core@npm:5.2.1" + version: 5.2.2 + resolution: "@octokit/core@npm:5.2.2" dependencies: "@octokit/auth-token": "npm:^4.0.0" "@octokit/graphql": "npm:^7.1.0" @@ -13311,7 +11857,7 @@ __metadata: "@octokit/types": "npm:^13.0.0" before-after-hook: "npm:^2.2.0" universal-user-agent: "npm:^6.0.0" - checksum: 10/9d95da740f350811dc5aadbf6670f3ddb8735f7c80029add497311ca0b13c4919fea473110e3fd9e85929e51ee64797fad8732e2f31703d55d80bdab1fdc846b + checksum: 10/0c39b43e562a8acf8f1d563a85f3c0e55e6d678ae16a4b3d6341060b3d5315c021dfa1bd15dc818fa4cc5612eb5cd518b13cb7c194e3c92ca3da9c0dc6a854b5 languageName: node linkType: hard @@ -13507,10 +12053,10 @@ __metadata: languageName: node linkType: hard -"@octokit/openapi-types@npm:^22.2.0": - version: 22.2.0 - resolution: "@octokit/openapi-types@npm:22.2.0" - checksum: 10/0471b0c789fada5aa2390e6f82ba477738228ef7d2d986dda9aab0cb625d1562bd178ba0ba4d2655ce841079cd5efff9e58ece2077c27e569ea22109ea301830 +"@octokit/openapi-types@npm:^24.2.0": + version: 24.2.0 + resolution: "@octokit/openapi-types@npm:24.2.0" + checksum: 10/000897ebc6e247c2591049d6081e95eb5636f73798dadd695ee6048496772b58065df88823e74a760201828545a7ac601dd3c1bcd2e00079a62a9ee9d389409c languageName: node linkType: hard @@ -13523,14 +12069,14 @@ __metadata: languageName: node linkType: hard -"@octokit/plugin-paginate-rest@npm:11.3.1": - version: 11.3.1 - resolution: "@octokit/plugin-paginate-rest@npm:11.3.1" +"@octokit/plugin-paginate-rest@npm:11.4.4-cjs.2": + version: 11.4.4-cjs.2 + resolution: "@octokit/plugin-paginate-rest@npm:11.4.4-cjs.2" dependencies: - "@octokit/types": "npm:^13.5.0" + "@octokit/types": "npm:^13.7.0" peerDependencies: "@octokit/core": 5 - checksum: 10/82f5bcc3a536a44bed0a205c8301176c0d210b7a1c6d035a79b31a102e2e02f46234a38629cc984a21be544194ac69151814e9a909416aa7389cdffd1297bcd9 + checksum: 10/e0f696b3b69febe4e7c736d909065871f38bb8346a07f19a9c83246a02972568ac672667db472f846baef20a9611adf26ce8f0f189a11004c4b6618765078e19 languageName: node linkType: hard @@ -13566,14 +12112,14 @@ __metadata: languageName: node linkType: hard -"@octokit/plugin-rest-endpoint-methods@npm:13.2.2": - version: 13.2.2 - resolution: "@octokit/plugin-rest-endpoint-methods@npm:13.2.2" +"@octokit/plugin-rest-endpoint-methods@npm:13.3.2-cjs.1": + version: 13.3.2-cjs.1 + resolution: "@octokit/plugin-rest-endpoint-methods@npm:13.3.2-cjs.1" dependencies: - "@octokit/types": "npm:^13.5.0" + "@octokit/types": "npm:^13.8.0" peerDependencies: "@octokit/core": ^5 - checksum: 10/9eccc1a22aa0b65f3f9378f26a74c386683db420c33202998918df1eef492e93212e1849e1d85530f425602663cfc2bfbf385a30991b8a04470334c74ba2386b + checksum: 10/479827e62466e55bc1a50129d51597807bddc6c909e56be9e8dd9c1a91efa0f466a2f56b7d80438649e21ab0a3a195f840b3fccf2ae7f11fb0a919db8e62bc62 languageName: node linkType: hard @@ -13715,12 +12261,12 @@ __metadata: languageName: node linkType: hard -"@octokit/types@npm:^13.0.0, @octokit/types@npm:^13.1.0, @octokit/types@npm:^13.5.0": - version: 13.6.2 - resolution: "@octokit/types@npm:13.6.2" +"@octokit/types@npm:^13.0.0, @octokit/types@npm:^13.1.0, @octokit/types@npm:^13.7.0, @octokit/types@npm:^13.8.0": + version: 13.10.0 + resolution: "@octokit/types@npm:13.10.0" dependencies: - "@octokit/openapi-types": "npm:^22.2.0" - checksum: 10/8e614796f3554d28dfb77c570e80ef52d68ef311bdd4614ec263f8ea2266b9c06d4f7963fe2989f32cbfe4ea0c05e13eba9a64a6e0f64afb997cd975af154d52 + "@octokit/openapi-types": "npm:^24.2.0" + checksum: 10/32f8f5010d7faae128b0cdd0c221f0ca8c3781fe44483ecd87162b3da507db667f7369acda81340f6e2c9c374d9a938803409c6085c2c01d98210b6c58efb99a languageName: node linkType: hard @@ -13767,10 +12313,10 @@ __metadata: languageName: node linkType: hard -"@octokit/webhooks-methods@npm:^4.0.0": - version: 4.0.0 - resolution: "@octokit/webhooks-methods@npm:4.0.0" - checksum: 10/f26892ed868488bf08d5be1fdacbc51f5b6ba84cef21067e0b1ff969d087202989e74303049691a77c75bb1940e7835ad6522b0f4f151ceef015e6d083789c80 +"@octokit/webhooks-methods@npm:^4.1.0": + version: 4.1.0 + resolution: "@octokit/webhooks-methods@npm:4.1.0" + checksum: 10/a95ad68600c43798b09ea29d5a356fb69de25b45d38fbddf0ade00aadb0492b1d59985031e072a66d12e91034999a536653e5d2d4d01350d38cccf735d9ca270 languageName: node linkType: hard @@ -13781,10 +12327,10 @@ __metadata: languageName: node linkType: hard -"@octokit/webhooks-types@npm:7.1.0": - version: 7.1.0 - resolution: "@octokit/webhooks-types@npm:7.1.0" - checksum: 10/80b41945586243df9178a24dce9a5c4b2784eb963c5f6a1c76bcf5600a56e9cb51d8dc4a0da2108abfab3323cde577474a6365615fa1dfaac84c14e9295b54f9 +"@octokit/webhooks-types@npm:7.6.1": + version: 7.6.1 + resolution: "@octokit/webhooks-types@npm:7.6.1" + checksum: 10/0b11bd7e8d13b5a9cf14214421298a423d0180a5e1aaaea876ee4db6f97b5cca536f48d89af63105db75419d777a2402733eb0e110002d4dd59581ef36037bdc languageName: node linkType: hard @@ -13800,15 +12346,15 @@ __metadata: languageName: node linkType: hard -"@octokit/webhooks@npm:^12.0.4": - version: 12.0.10 - resolution: "@octokit/webhooks@npm:12.0.10" +"@octokit/webhooks@npm:^12.0.4, @octokit/webhooks@npm:^12.3.1": + version: 12.3.1 + resolution: "@octokit/webhooks@npm:12.3.1" dependencies: "@octokit/request-error": "npm:^5.0.0" - "@octokit/webhooks-methods": "npm:^4.0.0" - "@octokit/webhooks-types": "npm:7.1.0" + "@octokit/webhooks-methods": "npm:^4.1.0" + "@octokit/webhooks-types": "npm:7.6.1" aggregate-error: "npm:^3.1.0" - checksum: 10/ab7d216d1a1fae91bc3f75057c093707c1f6dbd5ff2106656154a7f471b1a1b5b78c947474e21362590e5510516f9537c4ab79ad6780cc91bfaaaa685aa366e0 + checksum: 10/373266807eb8dcf8d8c6f4685594106f1a257798a1b391cbe36e5b5faf60aca38a0467acd1356427ea7375edc111c03be3d443bc04efb16019a84e853c8a1bc3 languageName: node linkType: hard @@ -13853,30 +12399,30 @@ __metadata: linkType: hard "@openapitools/openapi-generator-cli@npm:^2.4.26, @openapitools/openapi-generator-cli@npm:^2.7.0": - version: 2.15.3 - resolution: "@openapitools/openapi-generator-cli@npm:2.15.3" + version: 2.20.5 + resolution: "@openapitools/openapi-generator-cli@npm:2.20.5" dependencies: - "@nestjs/axios": "npm:3.1.1" - "@nestjs/common": "npm:10.4.6" - "@nestjs/core": "npm:10.4.6" + "@nestjs/axios": "npm:4.0.0" + "@nestjs/common": "npm:11.1.3" + "@nestjs/core": "npm:11.1.3" "@nuxtjs/opencollective": "npm:0.3.2" - axios: "npm:1.7.7" + axios: "npm:1.9.0" chalk: "npm:4.1.2" commander: "npm:8.3.0" compare-versions: "npm:4.1.4" concurrently: "npm:6.5.1" console.table: "npm:0.10.0" - fs-extra: "npm:10.1.0" - glob: "npm:9.3.5" + fs-extra: "npm:11.3.0" + glob: "npm:11.0.3" inquirer: "npm:8.2.6" lodash: "npm:4.17.21" - proxy-agent: "npm:6.4.0" - reflect-metadata: "npm:0.1.13" - rxjs: "npm:7.8.1" + proxy-agent: "npm:6.5.0" + reflect-metadata: "npm:0.2.2" + rxjs: "npm:7.8.2" tslib: "npm:2.8.1" bin: openapi-generator-cli: main.js - checksum: 10/8956772c697b11205c577a3f77161853a00cfdc405189fdc4d71cc20a1bc2d2d07a28ae50b0e2d7c43dee89ad57af1dab2b4675599d9ce2e223811542c8b5cd0 + checksum: 10/76e40b238074c8bf09c99c70222cd80aa1d8be8b9abe6c400542f1bd46c3058238247085835a08a57bb0a43dceeff40b3cb8c4ea736692d040c1d29c48bd1db7 languageName: node linkType: hard @@ -13894,6 +12440,15 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/api-logs@npm:0.202.0, @opentelemetry/api-logs@npm:^0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/api-logs@npm:0.202.0" + dependencies: + "@opentelemetry/api": "npm:^1.3.0" + checksum: 10/22171137ad1d876a79a6f046b4adcc44a13941a07f2172948e4c8dbb6cacfe276b1fa5087f15e4acdd567f748010d531321c93ebf93a6c09586351c9048bf6c7 + languageName: node + linkType: hard + "@opentelemetry/api-logs@npm:0.54.2": version: 0.54.2 resolution: "@opentelemetry/api-logs@npm:0.54.2" @@ -13903,15 +12458,6 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/api-logs@npm:0.56.0, @opentelemetry/api-logs@npm:^0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/api-logs@npm:0.56.0" - dependencies: - "@opentelemetry/api": "npm:^1.3.0" - checksum: 10/5a6e25015acada7449d11124e9adbbe6670e1e9f7e8b46c60360ac89bb1537f2be326bcf18c66dcbcdee9f34e3a18bd4807c5a40faa0a4ac0135cb3675efb2a9 - languageName: node - linkType: hard - "@opentelemetry/api@npm:^1.3.0, @opentelemetry/api@npm:^1.4.0, @opentelemetry/api@npm:^1.9.0, @opentelemetry/api@npm:~1.9.0": version: 1.9.0 resolution: "@opentelemetry/api@npm:1.9.0" @@ -13919,60 +12465,62 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/auto-instrumentations-node@npm:^0.54.0": - version: 0.54.0 - resolution: "@opentelemetry/auto-instrumentations-node@npm:0.54.0" +"@opentelemetry/auto-instrumentations-node@npm:^0.61.0": + version: 0.61.0 + resolution: "@opentelemetry/auto-instrumentations-node@npm:0.61.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/instrumentation-amqplib": "npm:^0.45.0" - "@opentelemetry/instrumentation-aws-lambda": "npm:^0.49.0" - "@opentelemetry/instrumentation-aws-sdk": "npm:^0.48.0" - "@opentelemetry/instrumentation-bunyan": "npm:^0.44.0" - "@opentelemetry/instrumentation-cassandra-driver": "npm:^0.44.0" - "@opentelemetry/instrumentation-connect": "npm:^0.42.0" - "@opentelemetry/instrumentation-cucumber": "npm:^0.12.0" - "@opentelemetry/instrumentation-dataloader": "npm:^0.15.0" - "@opentelemetry/instrumentation-dns": "npm:^0.42.0" - "@opentelemetry/instrumentation-express": "npm:^0.46.0" - "@opentelemetry/instrumentation-fastify": "npm:^0.43.0" - "@opentelemetry/instrumentation-fs": "npm:^0.18.0" - "@opentelemetry/instrumentation-generic-pool": "npm:^0.42.0" - "@opentelemetry/instrumentation-graphql": "npm:^0.46.0" - "@opentelemetry/instrumentation-grpc": "npm:^0.56.0" - "@opentelemetry/instrumentation-hapi": "npm:^0.44.0" - "@opentelemetry/instrumentation-http": "npm:^0.56.0" - "@opentelemetry/instrumentation-ioredis": "npm:^0.46.0" - "@opentelemetry/instrumentation-kafkajs": "npm:^0.6.0" - "@opentelemetry/instrumentation-knex": "npm:^0.43.0" - "@opentelemetry/instrumentation-koa": "npm:^0.46.0" - "@opentelemetry/instrumentation-lru-memoizer": "npm:^0.43.0" - "@opentelemetry/instrumentation-memcached": "npm:^0.42.0" - "@opentelemetry/instrumentation-mongodb": "npm:^0.50.0" - "@opentelemetry/instrumentation-mongoose": "npm:^0.45.0" - "@opentelemetry/instrumentation-mysql": "npm:^0.44.0" - "@opentelemetry/instrumentation-mysql2": "npm:^0.44.0" - "@opentelemetry/instrumentation-nestjs-core": "npm:^0.43.0" - "@opentelemetry/instrumentation-net": "npm:^0.42.0" - "@opentelemetry/instrumentation-pg": "npm:^0.49.0" - "@opentelemetry/instrumentation-pino": "npm:^0.45.0" - "@opentelemetry/instrumentation-redis": "npm:^0.45.0" - "@opentelemetry/instrumentation-redis-4": "npm:^0.45.0" - "@opentelemetry/instrumentation-restify": "npm:^0.44.0" - "@opentelemetry/instrumentation-router": "npm:^0.43.0" - "@opentelemetry/instrumentation-socket.io": "npm:^0.45.0" - "@opentelemetry/instrumentation-tedious": "npm:^0.17.0" - "@opentelemetry/instrumentation-undici": "npm:^0.9.0" - "@opentelemetry/instrumentation-winston": "npm:^0.43.0" - "@opentelemetry/resource-detector-alibaba-cloud": "npm:^0.29.6" - "@opentelemetry/resource-detector-aws": "npm:^1.9.0" - "@opentelemetry/resource-detector-azure": "npm:^0.4.0" - "@opentelemetry/resource-detector-container": "npm:^0.5.2" - "@opentelemetry/resource-detector-gcp": "npm:^0.31.0" - "@opentelemetry/resources": "npm:^1.24.0" - "@opentelemetry/sdk-node": "npm:^0.56.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation-amqplib": "npm:^0.49.0" + "@opentelemetry/instrumentation-aws-lambda": "npm:^0.53.1" + "@opentelemetry/instrumentation-aws-sdk": "npm:^0.55.0" + "@opentelemetry/instrumentation-bunyan": "npm:^0.48.0" + "@opentelemetry/instrumentation-cassandra-driver": "npm:^0.48.0" + "@opentelemetry/instrumentation-connect": "npm:^0.46.0" + "@opentelemetry/instrumentation-cucumber": "npm:^0.17.1" + "@opentelemetry/instrumentation-dataloader": "npm:^0.20.0" + "@opentelemetry/instrumentation-dns": "npm:^0.46.0" + "@opentelemetry/instrumentation-express": "npm:^0.51.1" + "@opentelemetry/instrumentation-fastify": "npm:^0.47.1" + "@opentelemetry/instrumentation-fs": "npm:^0.22.0" + "@opentelemetry/instrumentation-generic-pool": "npm:^0.46.1" + "@opentelemetry/instrumentation-graphql": "npm:^0.50.0" + "@opentelemetry/instrumentation-grpc": "npm:^0.202.0" + "@opentelemetry/instrumentation-hapi": "npm:^0.49.0" + "@opentelemetry/instrumentation-http": "npm:^0.202.0" + "@opentelemetry/instrumentation-ioredis": "npm:^0.50.1" + "@opentelemetry/instrumentation-kafkajs": "npm:^0.11.0" + "@opentelemetry/instrumentation-knex": "npm:^0.47.0" + "@opentelemetry/instrumentation-koa": "npm:^0.50.2" + "@opentelemetry/instrumentation-lru-memoizer": "npm:^0.47.0" + "@opentelemetry/instrumentation-memcached": "npm:^0.46.0" + "@opentelemetry/instrumentation-mongodb": "npm:^0.55.1" + "@opentelemetry/instrumentation-mongoose": "npm:^0.49.0" + "@opentelemetry/instrumentation-mysql": "npm:^0.48.1" + "@opentelemetry/instrumentation-mysql2": "npm:^0.48.1" + "@opentelemetry/instrumentation-nestjs-core": "npm:^0.48.1" + "@opentelemetry/instrumentation-net": "npm:^0.46.1" + "@opentelemetry/instrumentation-oracledb": "npm:^0.28.0" + "@opentelemetry/instrumentation-pg": "npm:^0.54.1" + "@opentelemetry/instrumentation-pino": "npm:^0.49.1" + "@opentelemetry/instrumentation-redis": "npm:^0.50.0" + "@opentelemetry/instrumentation-restify": "npm:^0.48.2" + "@opentelemetry/instrumentation-router": "npm:^0.47.0" + "@opentelemetry/instrumentation-runtime-node": "npm:^0.16.0" + "@opentelemetry/instrumentation-socket.io": "npm:^0.49.0" + "@opentelemetry/instrumentation-tedious": "npm:^0.21.1" + "@opentelemetry/instrumentation-undici": "npm:^0.13.2" + "@opentelemetry/instrumentation-winston": "npm:^0.47.0" + "@opentelemetry/resource-detector-alibaba-cloud": "npm:^0.31.2" + "@opentelemetry/resource-detector-aws": "npm:^2.2.0" + "@opentelemetry/resource-detector-azure": "npm:^0.9.0" + "@opentelemetry/resource-detector-container": "npm:^0.7.2" + "@opentelemetry/resource-detector-gcp": "npm:^0.36.0" + "@opentelemetry/resources": "npm:^2.0.0" + "@opentelemetry/sdk-node": "npm:^0.202.0" peerDependencies: "@opentelemetry/api": ^1.4.1 - checksum: 10/0b5992c0ab3eeb22a81ffcd8f4f1b9c4694e0c2fa5e208918774119838f20921e7918e39f801fb1194bf8ceca5e58d87c82f153ab1e26d7bd502a7644b125330 + "@opentelemetry/core": ^2.0.0 + checksum: 10/845728e050aa40f501ca3c67ad10ef64859413cb3af6926c6e64dbec206dc4ee7c8cff4d93df95345cebf8970427a03665032f97941a6a1153699f0fbbac1a6f languageName: node linkType: hard @@ -13985,12 +12533,12 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/context-async-hooks@npm:1.29.0": - version: 1.29.0 - resolution: "@opentelemetry/context-async-hooks@npm:1.29.0" +"@opentelemetry/context-async-hooks@npm:2.0.1": + version: 2.0.1 + resolution: "@opentelemetry/context-async-hooks@npm:2.0.1" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/bfd960bd03d8c5b2d92447fd7435b86ff919299b56821fe8f99f2ffe19831f0ecadba25bd52a37faff0f98c4fecdcc76e6886e56a3844b367360c6232674ae0e + checksum: 10/198dacdce36377f6ded7062eb9fc77f86c9fcc8c86dd395e9cb276e14a01c7906ea2f0271999cae21bf251dbec44641286b8bd34a39c67c88c08ed925a986f4b languageName: node linkType: hard @@ -14005,18 +12553,18 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/core@npm:1.29.0": - version: 1.29.0 - resolution: "@opentelemetry/core@npm:1.29.0" +"@opentelemetry/core@npm:2.0.1, @opentelemetry/core@npm:^2.0.0": + version: 2.0.1 + resolution: "@opentelemetry/core@npm:2.0.1" dependencies: - "@opentelemetry/semantic-conventions": "npm:1.28.0" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/eb62dce11cb0cb637acfb3582ad25e48766d9fb37b6b70e57f3c60521c7680a85431a0853c50d98cc8e807e5e3c2fddda314623d879e932bf1a5f629344b39ce + checksum: 10/dd891afd427067a9e6c610c36ab5638b0b9e5303ccca7c75ad744f5db53c6162a4b5d9cd2f5a77cdc3e4bda2eae850a4e29983ea244c929b7b872b7e086fc61c languageName: node linkType: hard -"@opentelemetry/core@npm:1.30.1, @opentelemetry/core@npm:^1.0.0, @opentelemetry/core@npm:^1.1.0, @opentelemetry/core@npm:^1.25.0, @opentelemetry/core@npm:^1.25.1, @opentelemetry/core@npm:^1.26.0, @opentelemetry/core@npm:^1.29.0, @opentelemetry/core@npm:^1.8.0": +"@opentelemetry/core@npm:^1.29.0": version: 1.30.1 resolution: "@opentelemetry/core@npm:1.30.1" dependencies: @@ -14027,6 +12575,22 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/exporter-logs-otlp-grpc@npm:0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.202.0" + dependencies: + "@grpc/grpc-js": "npm:^1.7.1" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/otlp-exporter-base": "npm:0.202.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.202.0" + "@opentelemetry/otlp-transformer": "npm:0.202.0" + "@opentelemetry/sdk-logs": "npm:0.202.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/b293a3778b6d5b1fe07b9203fe83d0dde3ea57e48efec8d4ccfc27d007fdb5cdafe7446ff10ea3eca69eb2fba8bdb97546a7dfcfc21b73d303e94e15f917b253 + languageName: node + linkType: hard + "@opentelemetry/exporter-logs-otlp-grpc@npm:0.54.2": version: 0.54.2 resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.54.2" @@ -14042,18 +12606,18 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-grpc@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.56.0" +"@opentelemetry/exporter-logs-otlp-http@npm:0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/exporter-logs-otlp-http@npm:0.202.0" dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.56.0" - "@opentelemetry/otlp-transformer": "npm:0.56.0" - "@opentelemetry/sdk-logs": "npm:0.56.0" + "@opentelemetry/api-logs": "npm:0.202.0" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/otlp-exporter-base": "npm:0.202.0" + "@opentelemetry/otlp-transformer": "npm:0.202.0" + "@opentelemetry/sdk-logs": "npm:0.202.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/e798faebab931c25941d7d6ebac91dbc0f0fd86b9a6ec641b12ba76ce4675a68acd4d7099ef41d5c040650b0ee2881c61f5cab053dcd280f3cb6dcc29fc450d5 + checksum: 10/850be5f7a2b0468887357845ffb7f8ca53dfedcd8fc79fbd4d8825cabbdc9a27be107e7aecb6b21ba544cead796e13f116396fcc66ba58954e47ac8fc1eaf270 languageName: node linkType: hard @@ -14072,18 +12636,20 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-http@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/exporter-logs-otlp-http@npm:0.56.0" +"@opentelemetry/exporter-logs-otlp-proto@npm:0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/exporter-logs-otlp-proto@npm:0.202.0" dependencies: - "@opentelemetry/api-logs": "npm:0.56.0" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/otlp-exporter-base": "npm:0.56.0" - "@opentelemetry/otlp-transformer": "npm:0.56.0" - "@opentelemetry/sdk-logs": "npm:0.56.0" + "@opentelemetry/api-logs": "npm:0.202.0" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/otlp-exporter-base": "npm:0.202.0" + "@opentelemetry/otlp-transformer": "npm:0.202.0" + "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/sdk-logs": "npm:0.202.0" + "@opentelemetry/sdk-trace-base": "npm:2.0.1" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/f2313074b0b124a36c292a9f97d6865df4c8d97ff2c4f95a122212c32a7ab91f0a4b6c1e01c2d1fe8c6619d245be81c7d7ab5178ddf2b8cfec15b224ae38c9e5 + checksum: 10/b69e19a035ae7d2d3f99f5ef40760e9f874685de9679b09a143ae9c120a248d9b5995eeec869ebeed6df1a4ebb4e312b5a569f4189410ad02e6ec4438f8fd63f languageName: node linkType: hard @@ -14104,20 +12670,65 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-proto@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/exporter-logs-otlp-proto@npm:0.56.0" +"@opentelemetry/exporter-metrics-otlp-grpc@npm:0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/exporter-metrics-otlp-grpc@npm:0.202.0" dependencies: - "@opentelemetry/api-logs": "npm:0.56.0" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/otlp-exporter-base": "npm:0.56.0" - "@opentelemetry/otlp-transformer": "npm:0.56.0" - "@opentelemetry/resources": "npm:1.29.0" - "@opentelemetry/sdk-logs": "npm:0.56.0" - "@opentelemetry/sdk-trace-base": "npm:1.29.0" + "@grpc/grpc-js": "npm:^1.7.1" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.202.0" + "@opentelemetry/otlp-exporter-base": "npm:0.202.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.202.0" + "@opentelemetry/otlp-transformer": "npm:0.202.0" + "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/sdk-metrics": "npm:2.0.1" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/3d062bd054108a0d40da1295ea11f68d4b71f9cf665bde479498939680497a54e1d542f358bc9b04b3332da15c95b1bdb570f7b60ca1a96909a2675d9f26a1ae + checksum: 10/383e6eee1016dbdfe78ed8c96fb6e8e314e0103a11353373f794637594b2b935efc5707eee33712a4ed242b12c808a09584105db1b2e2deab477d80e3d2d06ed + languageName: node + linkType: hard + +"@opentelemetry/exporter-metrics-otlp-http@npm:0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/exporter-metrics-otlp-http@npm:0.202.0" + dependencies: + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/otlp-exporter-base": "npm:0.202.0" + "@opentelemetry/otlp-transformer": "npm:0.202.0" + "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/sdk-metrics": "npm:2.0.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/ed694b8369bb2f979f0aa3552716b267060e5d8d7853fd645e0ae776888091a08c079e9f1e88d8f7eb93d9fe31f0fbb887ebb9469177c2ec2af16c247f8faf5d + languageName: node + linkType: hard + +"@opentelemetry/exporter-metrics-otlp-proto@npm:0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/exporter-metrics-otlp-proto@npm:0.202.0" + dependencies: + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.202.0" + "@opentelemetry/otlp-exporter-base": "npm:0.202.0" + "@opentelemetry/otlp-transformer": "npm:0.202.0" + "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/sdk-metrics": "npm:2.0.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/22ecc54c4a19a36d5f6ba8cc061e25fba6ff9f2bbaff9a84f8c1e7e5253b2b0dc07ac18e21884eafcb31e6693ce59912baeb19c722a07ad4da9f22e2a6f94549 + languageName: node + linkType: hard + +"@opentelemetry/exporter-prometheus@npm:0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/exporter-prometheus@npm:0.202.0" + dependencies: + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/sdk-metrics": "npm:2.0.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/e6cf4dfdc3bd6ccd402a00e0cb1bd2fa5cc4c813a86b23d27bf851e38a22788dac234c10d643a1506acf10415b1cba9bd68da7554358ba32a0ad8b978bc2d467 languageName: node linkType: hard @@ -14134,6 +12745,23 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/exporter-trace-otlp-grpc@npm:0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.202.0" + dependencies: + "@grpc/grpc-js": "npm:^1.7.1" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/otlp-exporter-base": "npm:0.202.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.202.0" + "@opentelemetry/otlp-transformer": "npm:0.202.0" + "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/sdk-trace-base": "npm:2.0.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/28cd0d2a0d3ef08cd7830b044690c5e6d86eb36f0d1298d9cd921f89c382cfbd3f80272c9fb8c12ece7eb417a48f852f6b3c299c21b0eba5d340d244cd41e6e2 + languageName: node + linkType: hard + "@opentelemetry/exporter-trace-otlp-grpc@npm:0.54.2": version: 0.54.2 resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.54.2" @@ -14150,19 +12778,18 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-trace-otlp-grpc@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.56.0" +"@opentelemetry/exporter-trace-otlp-http@npm:0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.202.0" dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.56.0" - "@opentelemetry/otlp-transformer": "npm:0.56.0" - "@opentelemetry/resources": "npm:1.29.0" - "@opentelemetry/sdk-trace-base": "npm:1.29.0" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/otlp-exporter-base": "npm:0.202.0" + "@opentelemetry/otlp-transformer": "npm:0.202.0" + "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/sdk-trace-base": "npm:2.0.1" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/3092a8c1ae7e092904b135760c7512636e132fc53c1657e5c9beda50ed104cfb0e85fed0fe53c53d95afd6b9403f6a9767106dd581ed5ae4559120c7d0616faa + checksum: 10/f9e60e51b5dcca3d4d32f2091f04faa2bcaf677b9f8a88990c8cdc7c6345e4244e46c4e8df1c336fc6f741aea092407032ade1236089eb729196e6fcdc055ad0 languageName: node linkType: hard @@ -14181,18 +12808,18 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-trace-otlp-http@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.56.0" +"@opentelemetry/exporter-trace-otlp-proto@npm:0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/exporter-trace-otlp-proto@npm:0.202.0" dependencies: - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/otlp-exporter-base": "npm:0.56.0" - "@opentelemetry/otlp-transformer": "npm:0.56.0" - "@opentelemetry/resources": "npm:1.29.0" - "@opentelemetry/sdk-trace-base": "npm:1.29.0" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/otlp-exporter-base": "npm:0.202.0" + "@opentelemetry/otlp-transformer": "npm:0.202.0" + "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/sdk-trace-base": "npm:2.0.1" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/2c9a0fb15ace10ca904c27a06d84b2a60b38ae989d045c3c8466419a5439d3dd5d0584481036ee1675cbf8eb403b863c7d51a086c10ffb5435235375062b33af + checksum: 10/884d1d097f3defee57dbb64281815a062603f64ce81e2f4460477925f4fb23c1d42aac2bb9d3b188e00cacaa9d231ef11e2e0f36e4b26863d009e795953191d8 languageName: node linkType: hard @@ -14211,21 +12838,6 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-trace-otlp-proto@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/exporter-trace-otlp-proto@npm:0.56.0" - dependencies: - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/otlp-exporter-base": "npm:0.56.0" - "@opentelemetry/otlp-transformer": "npm:0.56.0" - "@opentelemetry/resources": "npm:1.29.0" - "@opentelemetry/sdk-trace-base": "npm:1.29.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/85197740a1744a16f10ee355e250c4e7a77250454266cf9bad1875beb245dd39ba4fb19e87322fae65bf038075698fa5d1b284cc7d76160ee6eff279b728bef3 - languageName: node - linkType: hard - "@opentelemetry/exporter-zipkin@npm:1.27.0": version: 1.27.0 resolution: "@opentelemetry/exporter-zipkin@npm:1.27.0" @@ -14240,508 +12852,531 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-zipkin@npm:1.29.0": - version: 1.29.0 - resolution: "@opentelemetry/exporter-zipkin@npm:1.29.0" +"@opentelemetry/exporter-zipkin@npm:2.0.1": + version: 2.0.1 + resolution: "@opentelemetry/exporter-zipkin@npm:2.0.1" dependencies: - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/resources": "npm:1.29.0" - "@opentelemetry/sdk-trace-base": "npm:1.29.0" - "@opentelemetry/semantic-conventions": "npm:1.28.0" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/sdk-trace-base": "npm:2.0.1" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/8fdd2873723c48a00b9e82bfc4d878887cb65f68b7843d32e402677758481ce523759f30aa800bd538441dcca85a83765f0b4ae720d1309dbdce0c858fc93ec4 + checksum: 10/67fa9c4e33276218fd8a4c6fb04bab55aaf35383e396056034ab1f826f3d00c672b91ec890a9859df13c9f2890d3dc3d9f1a2b2904f0855272fc0cb48157e874 languageName: node linkType: hard -"@opentelemetry/instrumentation-amqplib@npm:^0.45.0": - version: 0.45.0 - resolution: "@opentelemetry/instrumentation-amqplib@npm:0.45.0" - dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/7a0be9861a12a7baf1a63fecd4eb221eecb9cb422223d1b62f183b14c1999e9ba039450a60bbaad139abab040d48dc843d4c93e8247b39215e31066e4ff9aa17 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-aws-lambda@npm:^0.49.0": +"@opentelemetry/instrumentation-amqplib@npm:^0.49.0": version: 0.49.0 - resolution: "@opentelemetry/instrumentation-aws-lambda@npm:0.49.0" + resolution: "@opentelemetry/instrumentation-amqplib@npm:0.49.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@types/aws-lambda": "npm:8.10.143" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/efd4d8e66fd0cb418b923bf971a0bae607b06dc02bd7a0215a316013fd8bf4e2d80d7c099215b34cf4466115664fa062c9395816e4456b468314cff7ab24125e + checksum: 10/891f201f476d77b960b6f3dc0186b3af52f7c249b69da365da184b01cf2056052fa93f85196e8811156908b2f532adf6a13773f102b4a78626046275827df2b1 languageName: node linkType: hard -"@opentelemetry/instrumentation-aws-sdk@npm:^0.48.0": +"@opentelemetry/instrumentation-aws-lambda@npm:^0.53.1": + version: 0.53.1 + resolution: "@opentelemetry/instrumentation-aws-lambda@npm:0.53.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@types/aws-lambda": "npm:8.10.150" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/a305988e8332e416c5fd81ffbdad71ffd68eeb623c55ea26fa25212f3c0c254ce30022461067981e4a7c5cfb1e25d1f1ed306d09f09d68c17fbbe693ec2ed4a1 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-aws-sdk@npm:^0.55.0": + version: 0.55.0 + resolution: "@opentelemetry/instrumentation-aws-sdk@npm:0.55.0" + dependencies: + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/propagation-utils": "npm:^0.31.2" + "@opentelemetry/semantic-conventions": "npm:^1.34.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/c2ce4063f4761baa6e12d1830270952d67d9bd0fdba3d15b97ec107df54d2a21c01be6960e0c2e3dcb2358bc57ba977b694e02c78a01c0f193377d0e4557dd7e + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-bunyan@npm:^0.48.0": version: 0.48.0 - resolution: "@opentelemetry/instrumentation-aws-sdk@npm:0.48.0" + resolution: "@opentelemetry/instrumentation-bunyan@npm:0.48.0" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/propagation-utils": "npm:^0.30.14" + "@opentelemetry/api-logs": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" + "@types/bunyan": "npm:1.8.11" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/c0147693e5e6202198448b0ca8b2550ca972d9a72a78b3c95edb1942b6e0a1b295351c205dbfc7562e11a50b815a64b3b920ceb474d6a59daa4cfbea01d8f621 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-cassandra-driver@npm:^0.48.0": + version: 0.48.0 + resolution: "@opentelemetry/instrumentation-cassandra-driver@npm:0.48.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.202.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/7d036e6467dbbaed5f3569ead30f628e73c6144c769278dec29eb9ef85804dda48c37a864c05ce0fcf69f89ea7447999e0630db612c361e72980fbe7e6615d39 + checksum: 10/7a3b2ba1b472108f0b1e1e799d7f8f8d0eb10034378b4b06fbec0e1fc59e095f3e374dd7739047856d27f00d8706603db08cfb90aa37b427fce18b43b0731932 languageName: node linkType: hard -"@opentelemetry/instrumentation-bunyan@npm:^0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/instrumentation-bunyan@npm:0.44.0" +"@opentelemetry/instrumentation-connect@npm:^0.46.0": + version: 0.46.0 + resolution: "@opentelemetry/instrumentation-connect@npm:0.46.0" dependencies: - "@opentelemetry/api-logs": "npm:^0.56.0" - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@types/bunyan": "npm:1.8.9" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/2be4fd8ec960bfd355626c31e7f0192a3dcd232d5749eb7fe46adfd1f33298e55bab595dd9b45a2569a4c712c6d6dc6b4aea8374bc3fe248da9d2bf9d0e0bc3b - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-cassandra-driver@npm:^0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/instrumentation-cassandra-driver@npm:0.44.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@types/connect": "npm:3.4.38" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/7617d2097710e94389c00fbf82e1e49f6b772370b2746fd6744681b46a2b3f4bbfa909bfcd556dc0eb99ad3d095c78f1d3016abff532b782d95164339213b3d0 + checksum: 10/e5b96bde0b8fbcf2edc22ed3a5ac655758440e9a3f8c4f39ae3dcf88edda8be6dd0bb121b3cffcf8df8b53fb0aef3c5bc62549cf5c24439fb9ba15a03f93280c languageName: node linkType: hard -"@opentelemetry/instrumentation-connect@npm:^0.42.0": - version: 0.42.0 - resolution: "@opentelemetry/instrumentation-connect@npm:0.42.0" +"@opentelemetry/instrumentation-cucumber@npm:^0.17.1": + version: 0.17.1 + resolution: "@opentelemetry/instrumentation-cucumber@npm:0.17.1" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@types/connect": "npm:3.4.36" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/f03090f36028d654fcbb649d52ec57613b407aceb7e3d61978ab4f737c3617f4dd7061612139e0cb856a9df566d1ff0202f2c426ee3dacaf2a68c1ea5a34be63 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-cucumber@npm:^0.12.0": - version: 0.12.0 - resolution: "@opentelemetry/instrumentation-cucumber@npm:0.12.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/0829bd8f609fc038e1649e1f214f63398304e8479ee600706c072f54b58a6e9bed5d59af3d13d62da629ecf78b1c59d2217fff4b472a062b1967b7142130e33b + checksum: 10/ef65ef03f4aa79a3b4ae223aebf714b62b7d96261680932df02ba4590685253d4e7bf44ac58109e39a951bb0217ffef11f12224ede62ce65ac93a46f2b65e517 languageName: node linkType: hard -"@opentelemetry/instrumentation-dataloader@npm:^0.15.0": - version: 0.15.0 - resolution: "@opentelemetry/instrumentation-dataloader@npm:0.15.0" +"@opentelemetry/instrumentation-dataloader@npm:^0.20.0": + version: 0.20.0 + resolution: "@opentelemetry/instrumentation-dataloader@npm:0.20.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/2a13e5159f5977f8d14955d6ce6994a6c6249ae25da10975aca782d0a74d74355143cdc4c47c99f36abd71d6097b610aa34611bb64a5d042948bbb788c26dd77 + checksum: 10/4d9e5600644e5d941860d492be9bd1b3966acd69e281f491f151347d482464e1481f7ce8c6898e744e235d052c16868774b6353d606a58a3538b9e32d43c2b92 languageName: node linkType: hard -"@opentelemetry/instrumentation-dns@npm:^0.42.0": - version: 0.42.0 - resolution: "@opentelemetry/instrumentation-dns@npm:0.42.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/5bbdb5095f257c479a11eabaf49019e8ed5d49ececbeba9c5723ecc9f3343f601edecbea36d9a7581e46dcd80cb27fe7299fea1e42cd60083a1f50b3df32c1a0 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-express@npm:^0.46.0": +"@opentelemetry/instrumentation-dns@npm:^0.46.0": version: 0.46.0 - resolution: "@opentelemetry/instrumentation-express@npm:0.46.0" + resolution: "@opentelemetry/instrumentation-dns@npm:0.46.0" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/68c72e76a5c53d7d49632fb0126c097aa717c173fc0a2502f4b03be382844fbcdcc385f49dab502853006e3e64555879e5c26a24d126a87d7825ad01be1a9f01 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-express@npm:^0.51.1": + version: 0.51.1 + resolution: "@opentelemetry/instrumentation-express@npm:0.51.1" + dependencies: + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/757b074aece5ad34ac082c54f3c4a6aadcbdf6cfb55510de5d77acc3e75e28a3c7f8b80ba5727b5d47ffa70790d0fdc672fc67d5f3854ba2acbb011424ce914c + checksum: 10/a0ecb32a8599d826a032df0c5558d43bf5024b131b93e6e57c659f208e265d40bafbad15c46a5621c698e7b6383ef2a02f4600633d52ba2f4b6fb75c08eed631 languageName: node linkType: hard -"@opentelemetry/instrumentation-fastify@npm:^0.43.0": - version: 0.43.0 - resolution: "@opentelemetry/instrumentation-fastify@npm:0.43.0" +"@opentelemetry/instrumentation-fastify@npm:^0.47.1": + version: 0.47.1 + resolution: "@opentelemetry/instrumentation-fastify@npm:0.47.1" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/aee85c357ad0f0a88935919aa5bd09547e0b81fd11fc83aabc298fa84e4b3f7d6e64aa03f6273849ce93d11cad591a743a2b73f7838ddf26f54a48b53906259c + checksum: 10/89ffbe54deaa90a33deb873db24967f7e0380c4dc448703ed6b7abbadd345687902e3511e23c1af622ce55e9b70a12e724d3773ad61d7007e4d3d8a47615591d languageName: node linkType: hard -"@opentelemetry/instrumentation-fs@npm:^0.18.0": - version: 0.18.0 - resolution: "@opentelemetry/instrumentation-fs@npm:0.18.0" +"@opentelemetry/instrumentation-fs@npm:^0.22.0": + version: 0.22.0 + resolution: "@opentelemetry/instrumentation-fs@npm:0.22.0" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/634c23bac8aee7325be2cf2d64f0856d11a9de746f942fec52ebd96a6a7159c63913758c78295c9cb909d950f72c73e1e2685f4d9ed5f9754254ff0ce678dcc6 + checksum: 10/56090810d8c31949ddbd9f1e2c9f91ca38e7214ada60bc9abc72cfac94fb303e5cc905a63e0c1ab2b524aaf80e7a152a3ab286d792fe82bb4e814cd5eb1d2f16 languageName: node linkType: hard -"@opentelemetry/instrumentation-generic-pool@npm:^0.42.0": - version: 0.42.0 - resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.42.0" +"@opentelemetry/instrumentation-generic-pool@npm:^0.46.1": + version: 0.46.1 + resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.46.1" dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/09c2cf4b64798ce92ddc0214c6c0443bb7800f94a2a8578f2276f3d8a5dd7018b4f5ec9d82d771b4cedd29b949760ca333c1a4fec0d43244ba338af36ad150b2 + checksum: 10/10f8cbf5d052c3cb895857390f6a53ec292a1d0e8ec82df94bfb6cd49a0d0a0db0ba66cbb4127298418be4b68444d048f70833257d37cad404edf17578d477e5 languageName: node linkType: hard -"@opentelemetry/instrumentation-graphql@npm:^0.46.0": - version: 0.46.0 - resolution: "@opentelemetry/instrumentation-graphql@npm:0.46.0" +"@opentelemetry/instrumentation-graphql@npm:^0.50.0": + version: 0.50.0 + resolution: "@opentelemetry/instrumentation-graphql@npm:0.50.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/f7850db04df91a8d213396bbaaf7762535295ac2c572d1338a9798cebfb149ccf3a8b98d9255cdcdbbd42f17228440f2a75f064d2f2010da336174a55b644ec6 + checksum: 10/a359e0839381d74dacf0b3acc8c951686cf73d6201fd305583e1c58ed6f4dcf1421efa322075808ed9c9298af3e579cbe4ac275960ceb7aceb8639b7346b5990 languageName: node linkType: hard -"@opentelemetry/instrumentation-grpc@npm:^0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/instrumentation-grpc@npm:0.56.0" +"@opentelemetry/instrumentation-grpc@npm:^0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/instrumentation-grpc@npm:0.202.0" dependencies: - "@opentelemetry/instrumentation": "npm:0.56.0" - "@opentelemetry/semantic-conventions": "npm:1.28.0" + "@opentelemetry/instrumentation": "npm:0.202.0" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/00973b36078641afecb085d0bef2dbabef7af3c68bb3abd2a3021600dbaf9a0b86011160c10d2b7db87a0ec21fcd01082547082f8656c94426c2662c29b68613 + checksum: 10/4b59bb35de3b900e9fc5e9ba44d1828b6b4eea15f1ce65a63ad9e047cff5989a9d1d23180e8c221c7bd375c104ecd23f2e7650405e37cf8d8a46edda53d447ee languageName: node linkType: hard -"@opentelemetry/instrumentation-hapi@npm:^0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/instrumentation-hapi@npm:0.44.0" +"@opentelemetry/instrumentation-hapi@npm:^0.49.0": + version: 0.49.0 + resolution: "@opentelemetry/instrumentation-hapi@npm:0.49.0" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/d8eafb0213ea8c6971b7a21dd3a3f7ea988954cdf23e708c5fc8bd132267709fd449823d496ac2bb8cf93cf164fa452edb16089c307cefcabb54bc99f20b203e + checksum: 10/2e2640e26ae193000b63fb62aea5dc03eff2c4896442361ebc7e3e482713166bad49616a5d3c157fb8dd0fab908d0c4412e24f4d806b071435890436ccf8fd9f languageName: node linkType: hard -"@opentelemetry/instrumentation-http@npm:^0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/instrumentation-http@npm:0.56.0" +"@opentelemetry/instrumentation-http@npm:^0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/instrumentation-http@npm:0.202.0" dependencies: - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/instrumentation": "npm:0.56.0" - "@opentelemetry/semantic-conventions": "npm:1.28.0" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/instrumentation": "npm:0.202.0" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" forwarded-parse: "npm:2.1.2" - semver: "npm:^7.5.2" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/3fb73d5f0df152f7ff9744a32a521a71656075df1ea6418005a991c912f16fd1b16c468bc9dcc48d4d19621be5b0b4aedc377cf6d00f828e0d2d1f8d4822e6ca + checksum: 10/fb36be7a08a33d07bb51e8c8dc64be937117f03d93a1425bc9ddb041464da1f01914adc1bc8173a145b61a7fd8b6151db8e284db1bcce12135d6b27b01c2ad0a languageName: node linkType: hard -"@opentelemetry/instrumentation-ioredis@npm:^0.46.0": +"@opentelemetry/instrumentation-ioredis@npm:^0.50.1": + version: 0.50.1 + resolution: "@opentelemetry/instrumentation-ioredis@npm:0.50.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/redis-common": "npm:^0.38.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/08e6aed4aa6d0ad0d1dd4a6d02b260f46d1ce5168b8dd1f6b2d3f8c71f0204378e1b52f175817e53cdb6f98841a6ddfc5ca2005592ea678f046316dc4edff281 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-kafkajs@npm:^0.11.0": + version: 0.11.0 + resolution: "@opentelemetry/instrumentation-kafkajs@npm:0.11.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/semantic-conventions": "npm:^1.30.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/6998633cda7260fde928f8691482bdbf5478d7f4c7c0be4c199c366345525733ec87e94b0a38b5b65a719489998f377ad52a065ebe0eeafc010e237533f1ce55 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-knex@npm:^0.47.0": + version: 0.47.0 + resolution: "@opentelemetry/instrumentation-knex@npm:0.47.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/semantic-conventions": "npm:^1.33.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/f3a753505fd6499d05022fbddb0977b781ad41a857df9cde7410273ca3ad9f2343afc6136138bbdde16e5358d77bba66e2ddf7f93d4dab878b6e5b85d72fc6d8 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-koa@npm:^0.50.2": + version: 0.50.2 + resolution: "@opentelemetry/instrumentation-koa@npm:0.50.2" + dependencies: + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/3598afe454d6df1861dd0b7f4a8b617f4d3b89789961577912151df9a2d159523490f25c3e8d03234b5faa62d5f8f8985e8a52b65122cae4c58016debf109edb + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-lru-memoizer@npm:^0.47.0": + version: 0.47.0 + resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.47.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.202.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/91fd11ce2f012b715a1bffb5c81ca5d681e5c2d9f638a2e459f2d4bead587ed755e438c234132484d6d030f0b3104a0747596708f51df109449cd970e965e6ae + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-memcached@npm:^0.46.0": version: 0.46.0 - resolution: "@opentelemetry/instrumentation-ioredis@npm:0.46.0" + resolution: "@opentelemetry/instrumentation-memcached@npm:0.46.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/redis-common": "npm:^0.36.2" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/579296b0fe06c0d727d9866845110b109160579654bb442f4e4ce05df0d47e2ad3912df1ace2bca84423b1d62584524a79d2c3f82943ae7429801e96440c3db5 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-kafkajs@npm:^0.6.0": - version: 0.6.0 - resolution: "@opentelemetry/instrumentation-kafkajs@npm:0.6.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/85723b216fea262d65b6aad9a24242842b0716e06424c6a94a1b9299470c3756dc9d128816f7053bf68eb46bde41d545385e47ac680e2aeff609b3fbc7c6b135 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-knex@npm:^0.43.0": - version: 0.43.0 - resolution: "@opentelemetry/instrumentation-knex@npm:0.43.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/bb801ba2198b33e0b4feba84313ad54d502969d5fddea9de26ebbeb1a59ade61f03eed2aea983f43682b53102de837916f6dd44eede3a55237980dc99527bf6c - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-koa@npm:^0.46.0": - version: 0.46.0 - resolution: "@opentelemetry/instrumentation-koa@npm:0.46.0" - dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/63d2209e7acb53d7183cd1926656d388a60cca34f3517a26af101acdb4e5612c123071076828a49e18f288a5d694a2f7994f95ee48dab8312f6c2904321d2e4a - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-lru-memoizer@npm:^0.43.0": - version: 0.43.0 - resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.43.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/5c1ea5e1f453dea989285199fe8385f31f17982fbc14eb504dc801f5e3a97f8e3fdc4d0f23212ec3cc522e84c1b5750073192b4a33b8a8302cdd18f4935280cb - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-memcached@npm:^0.42.0": - version: 0.42.0 - resolution: "@opentelemetry/instrumentation-memcached@npm:0.42.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" "@types/memcached": "npm:^2.2.6" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/0f14d19084bdb7327113003998b89b9d0a6ddc91ff7af958799d3f09a7a6ccdb1aa565d58605ced57dc98f72efa465387b8540924a926e5aaf620a3b5feca77a + checksum: 10/12bbcc4b9dcc62b0b48ba83f555112881cc9723f174335bbf7695374ae17582fb12cb2f72295656c84608b73ef5a88afcf551d5c392afebddbf97d7a64230080 languageName: node linkType: hard -"@opentelemetry/instrumentation-mongodb@npm:^0.50.0": - version: 0.50.0 - resolution: "@opentelemetry/instrumentation-mongodb@npm:0.50.0" +"@opentelemetry/instrumentation-mongodb@npm:^0.55.1": + version: 0.55.1 + resolution: "@opentelemetry/instrumentation-mongodb@npm:0.55.1" dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/beee37f0be94c614c620ea431ea7315c9293ea14f3b8f751075a5b4d663ed3daa4fe73ef9f58af812ab38d6b59d511da6252386d1fc7aab4d60b4384a8c2065c + checksum: 10/233822b1886b5693923430fb126363005424bcabd03681bf2cf4c035a52dbc6475718417b2a284d38f2fdcac1423625f5a25b767018a3686e9da54f4313f4da9 languageName: node linkType: hard -"@opentelemetry/instrumentation-mongoose@npm:^0.45.0": - version: 0.45.0 - resolution: "@opentelemetry/instrumentation-mongoose@npm:0.45.0" - dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/a4872b8c7783ace3510f954957aaf6880dee89c00f44a55153e463b5ffeacfdb896ca285cb8b816a746e7dec7f69901d0625c959cbdb09f4dfcba123dfa82213 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-mysql2@npm:^0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/instrumentation-mysql2@npm:0.44.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@opentelemetry/sql-common": "npm:^0.40.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/8ba5e66d3f994a3473df480bfd417b3e870b2fa25ae77b74ed1e516afb4ad38d1625d36c3947b02d9e52298edda1ea5f2df008a7c5bc8543455d760deb9fe3e1 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-mysql@npm:^0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/instrumentation-mysql@npm:0.44.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@types/mysql": "npm:2.15.26" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/7043e7446280be01ff12c88cbe843d131543c48ee73894b2862e9970cf380b43538f82a31fe32569d1fc2627acf0ccd1635e7ba3858ae93d7163a15b293036a8 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-nestjs-core@npm:^0.43.0": - version: 0.43.0 - resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.43.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/8d7170dfb574c77a8f11081aa205c83fb8d8ec7e9935005b1d6ca418b917bc359c55ed955747d330b8d3bdfbc599cc7eac38c0b1c5a092771847f9fd9bf49b3f - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-net@npm:^0.42.0": - version: 0.42.0 - resolution: "@opentelemetry/instrumentation-net@npm:0.42.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/ef0c3dc42cb75aa3a53c7fc72f214cad9da6276c07ac32cf65a4d9991741cea7daac4a764f7c99d0265aa84b27b8692f77a996cca98dd8a67dd299bfcd8a85a6 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-pg@npm:^0.49.0": +"@opentelemetry/instrumentation-mongoose@npm:^0.49.0": version: 0.49.0 - resolution: "@opentelemetry/instrumentation-pg@npm:0.49.0" + resolution: "@opentelemetry/instrumentation-mongoose@npm:0.49.0" dependencies: - "@opentelemetry/core": "npm:^1.26.0" - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" - "@opentelemetry/sql-common": "npm:^0.40.1" - "@types/pg": "npm:8.6.1" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/4b2b320346a0d00a56438c041ddda6cbd8b34d37de613daab618892fcba380b0cbd5396886028e9e2ac81c497f25e66f7668ccbf57c3f870678d30fc16e4ae24 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mysql2@npm:^0.48.1": + version: 0.48.1 + resolution: "@opentelemetry/instrumentation-mysql2@npm:0.48.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/sql-common": "npm:^0.41.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/d52d0950201adb3c4ce8f82edc544eee489de0971045f1e907832d6e7424d372da3a1e62143f6fcde698d883dfbe7adbea243cef9653f467c96d011d98225676 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mysql@npm:^0.48.1": + version: 0.48.1 + resolution: "@opentelemetry/instrumentation-mysql@npm:0.48.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@types/mysql": "npm:2.15.27" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/8e4493e5a685d98f0b7797b6f3996f7949cdae758e68c12e2edb57d8388be47a67e668c12340f1a2e18c692c8db990a765913120e402215c8d473695c9a281b8 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-nestjs-core@npm:^0.48.1": + version: 0.48.1 + resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.48.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/semantic-conventions": "npm:^1.30.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/fbbd5dd411b79371725e9075bca0c492a524ebd90d5e17434c3608bae6ec1cbbd5cca4dc0385c423e59963bfc1b945efdff5cd02f09a3f2a06391fc176d52c50 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-net@npm:^0.46.1": + version: 0.46.1 + resolution: "@opentelemetry/instrumentation-net@npm:0.46.1" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/3372ac7b8643c4adea2d8ffd762a2c872e02d000d5cc5afca922f422033eea251adeeac1c5d1bd49014d76c9167a0dfa98cd41dda7b3950395c179a8a8c6de81 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-oracledb@npm:^0.28.0": + version: 0.28.0 + resolution: "@opentelemetry/instrumentation-oracledb@npm:0.28.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@types/oracledb": "npm:6.5.2" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/820a6e760d2137ad00b27ff78014316fc37e92f2be94ad4c6082e92ace8fcd89a6d2bcc744e6330358ebd0f127d7993b65843b167b0c1ea0f8d5cb0fe201c634 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-pg@npm:^0.54.1": + version: 0.54.1 + resolution: "@opentelemetry/instrumentation-pg@npm:0.54.1" + dependencies: + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/sql-common": "npm:^0.41.0" + "@types/pg": "npm:8.15.4" "@types/pg-pool": "npm:2.0.6" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/04457e441872a0afecfa471f04bbecdf4b62782d110cd1a75423f9ff12c34396896056dda99923690f4a26c572fccc0c28d25fbd26e71f07ca2778e61fc3a7d1 + checksum: 10/eb88b8bd4417db8188af44e62659a7bc35b3f6a4b155d12c69357fc88185f3d719b3dd93e5562896088d07041c882a71a06d9672c14332846ae4f5abd26ec99a languageName: node linkType: hard -"@opentelemetry/instrumentation-pino@npm:^0.45.0": - version: 0.45.0 - resolution: "@opentelemetry/instrumentation-pino@npm:0.45.0" +"@opentelemetry/instrumentation-pino@npm:^0.49.1": + version: 0.49.1 + resolution: "@opentelemetry/instrumentation-pino@npm:0.49.1" dependencies: - "@opentelemetry/api-logs": "npm:^0.56.0" - "@opentelemetry/core": "npm:^1.25.0" - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/api-logs": "npm:^0.202.0" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/ef56a6bca54e99e66531576727ce236bd743413434fa04bb759ae6ebf8564fba303f3756ee7333fdaaea61a72a7e29f21ae19267c16e6d2d651596970b07ff10 + checksum: 10/2e2bff24bef74f68da8f4b4655d198c3fba7f7425f2f6317077385bd34dfbfb44c3747165859fec7a9f90aa0c77f830148e112e3d8257f6ac35dc28bbe157041 languageName: node linkType: hard -"@opentelemetry/instrumentation-redis-4@npm:^0.45.0": - version: 0.45.0 - resolution: "@opentelemetry/instrumentation-redis-4@npm:0.45.0" +"@opentelemetry/instrumentation-redis@npm:^0.50.0": + version: 0.50.0 + resolution: "@opentelemetry/instrumentation-redis@npm:0.50.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/redis-common": "npm:^0.36.2" + "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/redis-common": "npm:^0.38.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/8e2cd153936681b9a1e13a1e059df18d31a8d2a85c0db11e3d3b00c496f360efe3c864acac9e87de80cc13318f17ce35ecfe0aea22d502b42109cd1164e39619 + checksum: 10/94a05b1401fa071c2483d241a0cffb3fc79492c44ee7a2d2e320118cbc948eb7972c70e93f128bae29febd4958fc95904afff56b5c49da8b5407666e8f26e748 languageName: node linkType: hard -"@opentelemetry/instrumentation-redis@npm:^0.45.0": - version: 0.45.0 - resolution: "@opentelemetry/instrumentation-redis@npm:0.45.0" +"@opentelemetry/instrumentation-restify@npm:^0.48.2": + version: 0.48.2 + resolution: "@opentelemetry/instrumentation-restify@npm:0.48.2" dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/redis-common": "npm:^0.36.2" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/8bb137bc04be5b6d4afef365c5c52d8148c65f40c77af2010173ae2c481a85a24e9d1ffe36821315af7f177ba22f32f05b5d017d1cbcf1c298f4bbf3dff56d8a + checksum: 10/b2a0917248ae9defaff8dd9d5c650fb627241b56355474a776a54fd9541a29b3bbb487b54dba7042ac2f7e15cb0a79f738360fe8a3322e4066ff06728e027292 languageName: node linkType: hard -"@opentelemetry/instrumentation-restify@npm:^0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/instrumentation-restify@npm:0.44.0" +"@opentelemetry/instrumentation-router@npm:^0.47.0": + version: 0.47.0 + resolution: "@opentelemetry/instrumentation-router@npm:0.47.0" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/be37a7f3aeaa085e8ed3f3cddcc8cd6616a73f865337b5a5e086cc05161b258a01ee45cc41e22d2f8c8a9d2254be04c8d3b866b31b76315cf3087a8b0914c864 + checksum: 10/620a54e97e311692c197e86f12bf43bf1a04f2b3009f71dfdf6e0972b5c282606cc649739ba0041f60aea3fae9b027b315378e71a5f764c85952ece2e5b28078 languageName: node linkType: hard -"@opentelemetry/instrumentation-router@npm:^0.43.0": - version: 0.43.0 - resolution: "@opentelemetry/instrumentation-router@npm:0.43.0" +"@opentelemetry/instrumentation-runtime-node@npm:^0.16.0": + version: 0.16.0 + resolution: "@opentelemetry/instrumentation-runtime-node@npm:0.16.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/85d5a1302558fc99d5ec411ef4ea7306bb2c5295807ef3e75f61272b46be1752a19de145d22dbe530803ddfef65a4811ecccb5944183302ff60b8762005de70d + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-socket.io@npm:^0.49.0": + version: 0.49.0 + resolution: "@opentelemetry/instrumentation-socket.io@npm:0.49.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.202.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/3c2670151a24528910b15b85e3f8ec69468170d35ffe574850dbafee1efb055a14c6d231ccda03151870add4976c9da41171190aff859ca993be514fd5c6e104 + checksum: 10/16ab1664c3692267a6b9d0fdaf9256d66d9b705c056c019fb15d261d1f09296d325ddbbc9402b60b0d3b5722b55cdb6c7d0980c8ee965ef4346461742cdf87a1 languageName: node linkType: hard -"@opentelemetry/instrumentation-socket.io@npm:^0.45.0": - version: 0.45.0 - resolution: "@opentelemetry/instrumentation-socket.io@npm:0.45.0" +"@opentelemetry/instrumentation-tedious@npm:^0.21.1": + version: 0.21.1 + resolution: "@opentelemetry/instrumentation-tedious@npm:0.21.1" dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/fa0550011cde7a06f5c25f655c9ea1d0df284d63e2eb61599add27a16225d952b2c573364478654f9a0694f4716334f5618ffa9e8991bb078b202b78041cd7c0 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-tedious@npm:^0.17.0": - version: 0.17.0 - resolution: "@opentelemetry/instrumentation-tedious@npm:0.17.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" "@types/tedious": "npm:^4.0.14" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/62594f42fddc7af06aa14575e70d821551842671d2f2013e583d58d508d6ca8e6749faf89453e8254e6257483390cf8fd9ca0076327850d54e088d436f13ac51 + checksum: 10/5347d20e2cb4e07a1d25275edb25c47dcf87700a19de630fb33b7fc06bda0eb5934fb058fcb15b33be2eeb49a7430720c011f8540405b5ee10d22e689c93b94a languageName: node linkType: hard -"@opentelemetry/instrumentation-undici@npm:^0.9.0": - version: 0.9.0 - resolution: "@opentelemetry/instrumentation-undici@npm:0.9.0" +"@opentelemetry/instrumentation-undici@npm:^0.13.2": + version: 0.13.2 + resolution: "@opentelemetry/instrumentation-undici@npm:0.13.2" dependencies: - "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" peerDependencies: "@opentelemetry/api": ^1.7.0 - checksum: 10/e7300fb0353cc0648cc8a1779287e6780b6aaf3b4f13698a203367cfb9d571677e8f5897b3e79cfede196e72007ac2a1f4fbb8128a61ceb2b85517ebb30effd4 + checksum: 10/9f806706096d629796d1205a0f2a9f18cf3542f61dfb60f20199ca5ba269044145a593f5ee0ad495b046eb01b8a14448a61a8bbe6a18611f6967f3473923d97e languageName: node linkType: hard -"@opentelemetry/instrumentation-winston@npm:^0.43.0": - version: 0.43.0 - resolution: "@opentelemetry/instrumentation-winston@npm:0.43.0" +"@opentelemetry/instrumentation-winston@npm:^0.47.0": + version: 0.47.0 + resolution: "@opentelemetry/instrumentation-winston@npm:0.47.0" dependencies: - "@opentelemetry/api-logs": "npm:^0.56.0" - "@opentelemetry/instrumentation": "npm:^0.56.0" + "@opentelemetry/api-logs": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.202.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/9cd8e4b047bea576e6277c911d1f44df2b880b2c9f01081fff9b5ee4f1131dd6dd481ee11ab887344d22535211af1f6a75bcd9c34185183855acdd12975024ca + checksum: 10/1a046490c1509f77ae70552ef6cdbf6c21baba4f5d6f45e0c473ffa102156b009acd20036dafbf5d29e92132df88c4aad078b62606af3bda0fbdd4ab90073937 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation@npm:0.202.0, @opentelemetry/instrumentation@npm:^0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/instrumentation@npm:0.202.0" + dependencies: + "@opentelemetry/api-logs": "npm:0.202.0" + import-in-the-middle: "npm:^1.8.1" + require-in-the-middle: "npm:^7.1.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/da1db1ebc4ca847cc68d894b2e3a6c6552851d93af8ea793d42474e920f710664575c8991dc269e1a83fcf8f5abdda2cc724fa24e9cc4ae19fa6f70eb68ffc0e languageName: node linkType: hard @@ -14761,19 +13396,15 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/instrumentation@npm:0.56.0, @opentelemetry/instrumentation@npm:^0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/instrumentation@npm:0.56.0" +"@opentelemetry/otlp-exporter-base@npm:0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/otlp-exporter-base@npm:0.202.0" dependencies: - "@opentelemetry/api-logs": "npm:0.56.0" - "@types/shimmer": "npm:^1.2.0" - import-in-the-middle: "npm:^1.8.1" - require-in-the-middle: "npm:^7.1.1" - semver: "npm:^7.5.2" - shimmer: "npm:^1.2.1" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/otlp-transformer": "npm:0.202.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/7c3802eb6b55b39b6904526d052b918619c9cde0f71a35bc2f23ac6c3a10ea66b08a65adf6862cdbaac7b231fb5119204b4d5531be25b96933a9d8b91a9ce062 + checksum: 10/229778895ba1971451a8b1ec8a4787b0cdba337c87f3ca6aaf6d649f5e2b26439ba1befa36d31824fffa3cef62382792680ec70194f57aac6ebe98e95acecc69 languageName: node linkType: hard @@ -14789,15 +13420,17 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/otlp-exporter-base@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/otlp-exporter-base@npm:0.56.0" +"@opentelemetry/otlp-grpc-exporter-base@npm:0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/otlp-grpc-exporter-base@npm:0.202.0" dependencies: - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/otlp-transformer": "npm:0.56.0" + "@grpc/grpc-js": "npm:^1.7.1" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/otlp-exporter-base": "npm:0.202.0" + "@opentelemetry/otlp-transformer": "npm:0.202.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/a640641ae86a9dceeecb84e32e4b8cd56be9d2af202e44ad84022380530bcda4b27b4f7089b87238db8088930256550d385aeca200369021a8085e1288ef34db + checksum: 10/ba52acee1d46c3bca38d6eba8fea41d5c7389c26a34dd7ed4f4fbc46da5fcbf109ae139c5149c9730cd076ba51d4933d9bc06c223470ee8a463a31fed75b4ed6 languageName: node linkType: hard @@ -14815,17 +13448,20 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/otlp-grpc-exporter-base@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/otlp-grpc-exporter-base@npm:0.56.0" +"@opentelemetry/otlp-transformer@npm:0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/otlp-transformer@npm:0.202.0" dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/otlp-exporter-base": "npm:0.56.0" - "@opentelemetry/otlp-transformer": "npm:0.56.0" + "@opentelemetry/api-logs": "npm:0.202.0" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/sdk-logs": "npm:0.202.0" + "@opentelemetry/sdk-metrics": "npm:2.0.1" + "@opentelemetry/sdk-trace-base": "npm:2.0.1" + protobufjs: "npm:^7.3.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/8dd8117357035fff225d23637d0d2298b842f1b5611bf17612aff666a41068ad4d986868818a501a2136f91d788905fb99952b19d60e96fd08f2898422dc22a3 + checksum: 10/67e189af60bf8308a5b93deb85bef9709a5604b8c7915d0d42a7a2bff932b7257dfdfc071812c77913e136955a985ffe838c72b5f10059021087ea0bc52d84cd languageName: node linkType: hard @@ -14846,29 +13482,12 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/otlp-transformer@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/otlp-transformer@npm:0.56.0" - dependencies: - "@opentelemetry/api-logs": "npm:0.56.0" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/resources": "npm:1.29.0" - "@opentelemetry/sdk-logs": "npm:0.56.0" - "@opentelemetry/sdk-metrics": "npm:1.29.0" - "@opentelemetry/sdk-trace-base": "npm:1.29.0" - protobufjs: "npm:^7.3.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/d673b0295fac96b45535e86dea54c7c8e1270c161579de908cf7724fee00f4d5b49652da0a886dcac49c5d12a0d8f2b8be1cf639f4094bc0d16eec7808bddb78 - languageName: node - linkType: hard - -"@opentelemetry/propagation-utils@npm:^0.30.14": - version: 0.30.16 - resolution: "@opentelemetry/propagation-utils@npm:0.30.16" +"@opentelemetry/propagation-utils@npm:^0.31.2": + version: 0.31.2 + resolution: "@opentelemetry/propagation-utils@npm:0.31.2" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/8d282553d08aadfc9f8879ea6fbb33089765a535f960d72ae27fb98e8dab9c7e0394217cb9e2999479f9c2486fa9454cf4ffffeda30f4a96f8fe011276942b75 + checksum: 10/13828caae31651a1c4fa736f254b49a46b62348ff9ece945b19253fb52562db08bd268b5b0944ef3d2cb951f20cd4118ba14158b34a192e63868f0f3aa9c7cc9 languageName: node linkType: hard @@ -14883,14 +13502,14 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/propagator-b3@npm:1.29.0": - version: 1.29.0 - resolution: "@opentelemetry/propagator-b3@npm:1.29.0" +"@opentelemetry/propagator-b3@npm:2.0.1": + version: 2.0.1 + resolution: "@opentelemetry/propagator-b3@npm:2.0.1" dependencies: - "@opentelemetry/core": "npm:1.29.0" + "@opentelemetry/core": "npm:2.0.1" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/002e9400d9b605f1af1140dc4d20579d1494d7eb4c93e72862ee5207cc46cd3ed54f41eb42a0a00f0676b3e8f0f1afb2df36918582d833c97fbf4a23f5632f75 + checksum: 10/9871854b3d7516273c1f867c7a6a8eac5791f77954edf7509cc320ee6c109de8ef44778076edd4a64421b84f31632e4d283cb685f9220c5e7f1fe823c0e81a06 languageName: node linkType: hard @@ -14905,87 +13524,87 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/propagator-jaeger@npm:1.29.0": - version: 1.29.0 - resolution: "@opentelemetry/propagator-jaeger@npm:1.29.0" +"@opentelemetry/propagator-jaeger@npm:2.0.1": + version: 2.0.1 + resolution: "@opentelemetry/propagator-jaeger@npm:2.0.1" dependencies: - "@opentelemetry/core": "npm:1.29.0" + "@opentelemetry/core": "npm:2.0.1" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/75849bcfaa60823cc8b2bb10a4094ef7d933d998c84977f8319930ed7fdba37aba5906ba56f5ed96f28e07bb2cf5e185b7340993c06a36135a1de25de945df74 + checksum: 10/d6a51e7dd58dde7ada4c0fe510a14f9a33f1972f0db11b64fccb867be23652d4d8a55012dd5a295753ad27c456b21d502eb3a24dfbce6a0a3304e1fa6e1f4896 languageName: node linkType: hard -"@opentelemetry/redis-common@npm:^0.36.2": - version: 0.36.2 - resolution: "@opentelemetry/redis-common@npm:0.36.2" - checksum: 10/e7f610f79c95bab9156a9831162c7b55b94ab43c5e47ecb9efcc10c08a236395fdd54b6bb018da981e6641bac9da6fda1b50636fb49db584e87d988750d255e1 +"@opentelemetry/redis-common@npm:^0.38.0": + version: 0.38.0 + resolution: "@opentelemetry/redis-common@npm:0.38.0" + checksum: 10/c7caa450ed27ad02aeefa3e4d643e7d065d76e5f1cd275c8c3d0c7d324bf7d06434666dbd836d219f6b2e1057285f30ccaa89b949438b38118a70786ec31fa52 languageName: node linkType: hard -"@opentelemetry/resource-detector-alibaba-cloud@npm:^0.29.6": - version: 0.29.7 - resolution: "@opentelemetry/resource-detector-alibaba-cloud@npm:0.29.7" +"@opentelemetry/resource-detector-alibaba-cloud@npm:^0.31.2": + version: 0.31.2 + resolution: "@opentelemetry/resource-detector-alibaba-cloud@npm:0.31.2" dependencies: - "@opentelemetry/core": "npm:^1.26.0" - "@opentelemetry/resources": "npm:^1.10.0" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/resources": "npm:^2.0.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/1baba0bef0201b4b6f442e3dd47846051b551e5dd0d9c9aaf6223ff292ae719f9263edb81496487166956a5ad67f79f73dda65ec012912f2fab9758fe1199656 + checksum: 10/1e2498d5dc970addaeb88c9a1c4be85248438fe69876f2223ee526535ccb50d1ffaca366bfc5c03d0c84933050fd5d821726d8ce92ac5829da18f4a91025ce2f languageName: node linkType: hard -"@opentelemetry/resource-detector-aws@npm:^1.9.0": - version: 1.12.0 - resolution: "@opentelemetry/resource-detector-aws@npm:1.12.0" +"@opentelemetry/resource-detector-aws@npm:^2.2.0": + version: 2.2.0 + resolution: "@opentelemetry/resource-detector-aws@npm:2.2.0" dependencies: - "@opentelemetry/core": "npm:^1.0.0" - "@opentelemetry/resources": "npm:^1.10.0" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/resources": "npm:^2.0.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/95702aed5d2a22fab0429439d57ede5c53b3512618af21ffc098b0e6fca86266e9703a32325427082bb5776697d5e74a4e99f6c757e4b6bcf497e32663e705f9 + checksum: 10/91e6b67936d6846ba98b31438a9f32e5486f27153cfcd57978c8b6c5df9cc400c821ea63b06fff158c6a7e97b3ded3396f91f02410f28d4113d6b5bdf75f2bec languageName: node linkType: hard -"@opentelemetry/resource-detector-azure@npm:^0.4.0": - version: 0.4.0 - resolution: "@opentelemetry/resource-detector-azure@npm:0.4.0" +"@opentelemetry/resource-detector-azure@npm:^0.9.0": + version: 0.9.0 + resolution: "@opentelemetry/resource-detector-azure@npm:0.9.0" dependencies: - "@opentelemetry/core": "npm:^1.25.1" - "@opentelemetry/resources": "npm:^1.10.1" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/resources": "npm:^2.0.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/e48408787b9834b0079690d7e5505dd5aad6a2234e61324ae8a84237b26c79c4b585cac93c1aab280be63f4c9ef32b74923858771c7363211036bfebee7a6234 + checksum: 10/93d85781e73f37ab186b8d39ad144c944d80e69d19361ef4f846499e2bf0ba27df2cd00e334b1408415c80488f1633764696e1d29b639ed7829631ec29c92286 languageName: node linkType: hard -"@opentelemetry/resource-detector-container@npm:^0.5.2": - version: 0.5.3 - resolution: "@opentelemetry/resource-detector-container@npm:0.5.3" +"@opentelemetry/resource-detector-container@npm:^0.7.2": + version: 0.7.2 + resolution: "@opentelemetry/resource-detector-container@npm:0.7.2" dependencies: - "@opentelemetry/core": "npm:^1.26.0" - "@opentelemetry/resources": "npm:^1.10.0" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/resources": "npm:^2.0.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/79fd039e3f960a8fa31af9df52bffc948c3bc2f384d574061f0e0c47fa9bdb0d4b95ff34e94f72b33746a92a427cf284c069633ad07bfdca52c62e03e5d005c5 + checksum: 10/d556c4f64737726e31f211c6163ea359e403b4b0c65bca9fff565ec6a579a18340f4c6dc68d78ac707413c44bcb5f2a362eab2794fbd99524faf161517793e0e languageName: node linkType: hard -"@opentelemetry/resource-detector-gcp@npm:^0.31.0": - version: 0.31.0 - resolution: "@opentelemetry/resource-detector-gcp@npm:0.31.0" +"@opentelemetry/resource-detector-gcp@npm:^0.36.0": + version: 0.36.0 + resolution: "@opentelemetry/resource-detector-gcp@npm:0.36.0" dependencies: - "@opentelemetry/core": "npm:^1.0.0" - "@opentelemetry/resources": "npm:^1.10.0" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/resources": "npm:^2.0.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" gcp-metadata: "npm:^6.0.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/3ad40b7633dd106bac3f7ef27265d88b0fbb197f3754469b3a334e0973335da0e432f9b740a18e8b2731358da565f0a330e60fbf446ac00855bc262fcd75e07d + checksum: 10/9bfffd5d21a90509bc8d4dd3ae27554dd56719ca1607fcc20900034cc5776ff4c0025a3d4fddbd7c12e7962dd747516bdca9a9da6ccc59223c8d485609aea732 languageName: node linkType: hard @@ -15001,27 +13620,28 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/resources@npm:1.29.0": - version: 1.29.0 - resolution: "@opentelemetry/resources@npm:1.29.0" +"@opentelemetry/resources@npm:2.0.1, @opentelemetry/resources@npm:^2.0.0": + version: 2.0.1 + resolution: "@opentelemetry/resources@npm:2.0.1" dependencies: - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/semantic-conventions": "npm:1.28.0" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/677b9e3478a380e93383a223a01ccade21dde7381924a4f859b2309ea82e79da9e7257338791a5b2699f763b0c198ec7e0ed6a12f03e467f5e0d8287757f0f66 + "@opentelemetry/api": ">=1.3.0 <1.10.0" + checksum: 10/282f3831de2755d0fda2d8b6e37f9587ea248066d50c7d2f14c803ac9d5262a0f1db98a4185bcdc5acaeeece0b61f4fce43bc3896a79f1da79045ae4928618bf languageName: node linkType: hard -"@opentelemetry/resources@npm:^1.10.0, @opentelemetry/resources@npm:^1.10.1, @opentelemetry/resources@npm:^1.24.0": - version: 1.30.1 - resolution: "@opentelemetry/resources@npm:1.30.1" +"@opentelemetry/sdk-logs@npm:0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/sdk-logs@npm:0.202.0" dependencies: - "@opentelemetry/core": "npm:1.30.1" - "@opentelemetry/semantic-conventions": "npm:1.28.0" + "@opentelemetry/api-logs": "npm:0.202.0" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/resources": "npm:2.0.1" peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/9b7544b639e8fee41315e2646615676ffb1020dba0f6c81e6ec1dd2daf5409fc6ce3d2b629bbd9cd32f85decc3a8bfa5dc8cc52bb72bd84c1777ca25b4301aa0 + "@opentelemetry/api": ">=1.4.0 <1.10.0" + checksum: 10/e1b76647282a41ad7004c86c0058b0e9be70fdcf34aa2761929401e274af79bc5e3c6610a63e14fde4d51d96d8c96adaf3247c10b9a64cb70086d2ede2e421ca languageName: node linkType: hard @@ -15038,19 +13658,6 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/sdk-logs@npm:0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/sdk-logs@npm:0.56.0" - dependencies: - "@opentelemetry/api-logs": "npm:0.56.0" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/resources": "npm:1.29.0" - peerDependencies: - "@opentelemetry/api": ">=1.4.0 <1.10.0" - checksum: 10/36f7ef72ce9f1ccc6f8bb1b99a142801bb3e6e4e2f2dda30de713144213c62b144530bb49590379bc413757c8d170fba874619d933f42d0370c5473c22fdeea0 - languageName: node - linkType: hard - "@opentelemetry/sdk-metrics@npm:1.27.0": version: 1.27.0 resolution: "@opentelemetry/sdk-metrics@npm:1.27.0" @@ -15063,15 +13670,47 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/sdk-metrics@npm:1.29.0": - version: 1.29.0 - resolution: "@opentelemetry/sdk-metrics@npm:1.29.0" +"@opentelemetry/sdk-metrics@npm:2.0.1": + version: 2.0.1 + resolution: "@opentelemetry/sdk-metrics@npm:2.0.1" dependencies: - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/resources": "npm:1.29.0" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/resources": "npm:2.0.1" + peerDependencies: + "@opentelemetry/api": ">=1.9.0 <1.10.0" + checksum: 10/eb23d0657ce7ef0784f6c89af650de83530099782758fce574316a8e82ff2bca0eb3adffa88c5fdd04eaced6150deb53ea0ea05aae06d2783795691734e85473 + languageName: node + linkType: hard + +"@opentelemetry/sdk-node@npm:^0.202.0": + version: 0.202.0 + resolution: "@opentelemetry/sdk-node@npm:0.202.0" + dependencies: + "@opentelemetry/api-logs": "npm:0.202.0" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/exporter-logs-otlp-grpc": "npm:0.202.0" + "@opentelemetry/exporter-logs-otlp-http": "npm:0.202.0" + "@opentelemetry/exporter-logs-otlp-proto": "npm:0.202.0" + "@opentelemetry/exporter-metrics-otlp-grpc": "npm:0.202.0" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.202.0" + "@opentelemetry/exporter-metrics-otlp-proto": "npm:0.202.0" + "@opentelemetry/exporter-prometheus": "npm:0.202.0" + "@opentelemetry/exporter-trace-otlp-grpc": "npm:0.202.0" + "@opentelemetry/exporter-trace-otlp-http": "npm:0.202.0" + "@opentelemetry/exporter-trace-otlp-proto": "npm:0.202.0" + "@opentelemetry/exporter-zipkin": "npm:2.0.1" + "@opentelemetry/instrumentation": "npm:0.202.0" + "@opentelemetry/propagator-b3": "npm:2.0.1" + "@opentelemetry/propagator-jaeger": "npm:2.0.1" + "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/sdk-logs": "npm:0.202.0" + "@opentelemetry/sdk-metrics": "npm:2.0.1" + "@opentelemetry/sdk-trace-base": "npm:2.0.1" + "@opentelemetry/sdk-trace-node": "npm:2.0.1" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/399f28da37d7176f4017cffb2b9c3d3db65bbe397e5f7232c0b2df1a9162f094d7e460e64058c959aca787a80d48e0f98b9b65dfb711b72f41fa04b5b03b0e23 + checksum: 10/80e3c6493bb5d87e5ce9c8b25dc5e2614c879b2ebe4f79f1a373d8237085b6e0bcaaff06acf01528e45849fb27e38cc0961fde6c87d7d2addf90cbcfbea43af7 languageName: node linkType: hard @@ -15101,32 +13740,6 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/sdk-node@npm:^0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/sdk-node@npm:0.56.0" - dependencies: - "@opentelemetry/api-logs": "npm:0.56.0" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/exporter-logs-otlp-grpc": "npm:0.56.0" - "@opentelemetry/exporter-logs-otlp-http": "npm:0.56.0" - "@opentelemetry/exporter-logs-otlp-proto": "npm:0.56.0" - "@opentelemetry/exporter-trace-otlp-grpc": "npm:0.56.0" - "@opentelemetry/exporter-trace-otlp-http": "npm:0.56.0" - "@opentelemetry/exporter-trace-otlp-proto": "npm:0.56.0" - "@opentelemetry/exporter-zipkin": "npm:1.29.0" - "@opentelemetry/instrumentation": "npm:0.56.0" - "@opentelemetry/resources": "npm:1.29.0" - "@opentelemetry/sdk-logs": "npm:0.56.0" - "@opentelemetry/sdk-metrics": "npm:1.29.0" - "@opentelemetry/sdk-trace-base": "npm:1.29.0" - "@opentelemetry/sdk-trace-node": "npm:1.29.0" - "@opentelemetry/semantic-conventions": "npm:1.28.0" - peerDependencies: - "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/ba89ba4ef5ba7b4d75fb167b4bc5247602202d85d0da431c863932a83a313bd82aaad00f0fb3f3ef75a47242c474a5066a967649a585ee0f7cdbec6ddedfa39b - languageName: node - linkType: hard - "@opentelemetry/sdk-trace-base@npm:1.27.0": version: 1.27.0 resolution: "@opentelemetry/sdk-trace-base@npm:1.27.0" @@ -15140,16 +13753,16 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/sdk-trace-base@npm:1.29.0": - version: 1.29.0 - resolution: "@opentelemetry/sdk-trace-base@npm:1.29.0" +"@opentelemetry/sdk-trace-base@npm:2.0.1": + version: 2.0.1 + resolution: "@opentelemetry/sdk-trace-base@npm:2.0.1" dependencies: - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/resources": "npm:1.29.0" - "@opentelemetry/semantic-conventions": "npm:1.28.0" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/97080188cd2ded16cc489ad5414255f0e63a81ca3f8778f604cdb1409bf95691529d7f9233d37788ac14a6ff36b736fcf274ef39bba3fdf421e7201642d2d5b4 + "@opentelemetry/api": ">=1.3.0 <1.10.0" + checksum: 10/9de1e36bbce9bd7c0563e6395765fffc0f8c78806cb33cc95267e98dffd82de33857a51288073a104c10418b934e51560bcb5dcaf4e63e5c9e096f65cadd42cd languageName: node linkType: hard @@ -15169,19 +13782,16 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/sdk-trace-node@npm:1.29.0": - version: 1.29.0 - resolution: "@opentelemetry/sdk-trace-node@npm:1.29.0" +"@opentelemetry/sdk-trace-node@npm:2.0.1": + version: 2.0.1 + resolution: "@opentelemetry/sdk-trace-node@npm:2.0.1" dependencies: - "@opentelemetry/context-async-hooks": "npm:1.29.0" - "@opentelemetry/core": "npm:1.29.0" - "@opentelemetry/propagator-b3": "npm:1.29.0" - "@opentelemetry/propagator-jaeger": "npm:1.29.0" - "@opentelemetry/sdk-trace-base": "npm:1.29.0" - semver: "npm:^7.5.2" + "@opentelemetry/context-async-hooks": "npm:2.0.1" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/sdk-trace-base": "npm:2.0.1" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/4af09076649bdfbd1b3b12a8d0f9fd9569cb8e821f449a9096aa64253970ab57188d93eb7fcb108c9067e8e7deadf79cdd5146939493d35795e0e66fb9d64aae + checksum: 10/7c5b4a902ba88ef03f02da83f6900080d81bb3264752419981dc44faabfe2bf4dbe91d25fec4a671afd5212151eee535a5205137772a8fd2c8d247950eb6556f languageName: node linkType: hard @@ -15199,49 +13809,28 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/semantic-conventions@npm:^1.27.0, @opentelemetry/semantic-conventions@npm:~1.30.0": +"@opentelemetry/semantic-conventions@npm:^1.27.0, @opentelemetry/semantic-conventions@npm:^1.29.0, @opentelemetry/semantic-conventions@npm:^1.30.0, @opentelemetry/semantic-conventions@npm:^1.33.1, @opentelemetry/semantic-conventions@npm:^1.34.0": + version: 1.34.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.34.0" + checksum: 10/1892b4cc69c9e00456c809604a980e32696563e96463ff5f9d07e72d5aca73836a7378090509f28f54445ac6e072d2343a888c9d64d9ce287198e899082ff7aa + languageName: node + linkType: hard + +"@opentelemetry/semantic-conventions@npm:~1.30.0": version: 1.30.0 resolution: "@opentelemetry/semantic-conventions@npm:1.30.0" checksum: 10/78df5976f5bcfd00acaea3e609cf06fdd34517ae8db994ae216aaac16c51af97ac22c534bfcbac5218e0086db83ec5ef6cc045b95626cc6ea807686bea549a41 languageName: node linkType: hard -"@opentelemetry/sql-common@npm:^0.40.1": - version: 0.40.1 - resolution: "@opentelemetry/sql-common@npm:0.40.1" +"@opentelemetry/sql-common@npm:^0.41.0": + version: 0.41.0 + resolution: "@opentelemetry/sql-common@npm:0.41.0" dependencies: - "@opentelemetry/core": "npm:^1.1.0" + "@opentelemetry/core": "npm:^2.0.0" peerDependencies: "@opentelemetry/api": ^1.1.0 - checksum: 10/f887b4135be56c9ef6e29f040c9f75f34709e38c11897d59d284d7e73175a2dd2c6267c18061144e81a0045fc461b7813769db2e49c42a8d6becc58b1456d55c - languageName: node - linkType: hard - -"@oriflame/backstage-plugin-score-card@npm:^0.9.0": - version: 0.9.1 - resolution: "@oriflame/backstage-plugin-score-card@npm:0.9.1" - dependencies: - "@backstage/catalog-model": "npm:^1.4.3" - "@backstage/config": "npm:^1.1.1" - "@backstage/core-components": "npm:^0.13.10" - "@backstage/core-plugin-api": "npm:^1.8.2" - "@backstage/integration": "npm:^1.9.0" - "@backstage/integration-react": "npm:^1.1.24" - "@backstage/plugin-catalog-common": "npm:^1.0.20" - "@backstage/plugin-catalog-react": "npm:^1.9.3" - "@backstage/theme": "npm:^0.5.0" - "@backstage/types": "npm:^1.1.1" - "@backstage/version-bridge": "npm:^1.0.7" - "@material-ui/core": "npm:^4.12.2" - "@material-ui/icons": "npm:^4.11.2" - "@material-ui/lab": "npm:^4.0.0-alpha.57" - "@types/react": "npm:^16.13.1 || ^17.0.0 || ^18.0.0" - react-use: "npm:^17.2.4" - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: 10/549e9d754f5607ebe89a717b75f12333a49b7d87e9b469260675de2c77b7991b1600a11d200b0c6f1922f981df8398abb9389cbceb21ee46646c8604e8892319 + checksum: 10/182915f050b8b685f5499e9fa4ed9a9730fcd404889e5066cfc7d94e9961b1f2780216d776070a4a5c1b94323c287ab426b06f5a8828d5fea3daf664bfd397bb languageName: node linkType: hard @@ -15262,13 +13851,13 @@ __metadata: linkType: hard "@playwright/test@npm:^1.32.3": - version: 1.52.0 - resolution: "@playwright/test@npm:1.52.0" + version: 1.54.2 + resolution: "@playwright/test@npm:1.54.2" dependencies: - playwright: "npm:1.52.0" + playwright: "npm:1.54.2" bin: playwright: cli.js - checksum: 10/e18a4eb626c7bc6cba212ff2e197cf9ae2e4da1c91bfdf08a744d62e27222751173e4b220fa27da72286a89a3b4dea7c09daf384d23708f284b64f98e9a63a88 + checksum: 10/33d6c0780ef6cca12c008cae166ba06978ae975138a1654e14cd8691ad7826dbbc03e492ed42c65889c44ca893d0fd47d8ae4dc8d824020d7f767260b395dc6b languageName: node linkType: hard @@ -15963,119 +14552,929 @@ __metadata: languageName: node linkType: hard -"@react-aria/focus@npm:^3.20.1": - version: 3.20.1 - resolution: "@react-aria/focus@npm:3.20.1" +"@react-aria/autocomplete@npm:3.0.0-beta.5": + version: 3.0.0-beta.5 + resolution: "@react-aria/autocomplete@npm:3.0.0-beta.5" dependencies: - "@react-aria/interactions": "npm:^3.24.1" - "@react-aria/utils": "npm:^3.28.1" - "@react-types/shared": "npm:^3.28.0" + "@react-aria/combobox": "npm:^3.12.5" + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/listbox": "npm:^3.14.6" + "@react-aria/searchfield": "npm:^3.8.6" + "@react-aria/textfield": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/autocomplete": "npm:3.0.0-beta.2" + "@react-stately/combobox": "npm:^3.10.6" + "@react-types/autocomplete": "npm:3.0.0-alpha.32" + "@react-types/button": "npm:^3.12.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/f8b4427b310a4bd221cb0e38b031a4150c306f7d1177926c5a2bea0dd4370db6e49e220ca2358c283ea4c009f0a65611340486d161193f7943659a60e20a1881 + languageName: node + linkType: hard + +"@react-aria/breadcrumbs@npm:^3.5.26": + version: 3.5.26 + resolution: "@react-aria/breadcrumbs@npm:3.5.26" + dependencies: + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/link": "npm:^3.8.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/breadcrumbs": "npm:^3.7.14" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/e0b9fe416cb84281e207ab12644d3bf21d6af82b4ec5d5ac204209ef6aff9abcc242660c23b2f3ef3572aa0f1e72dcc33905066f720afa1ccfc3cf3d4b916c31 + languageName: node + linkType: hard + +"@react-aria/button@npm:^3.13.3": + version: 3.13.3 + resolution: "@react-aria/button@npm:3.13.3" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/toolbar": "npm:3.0.0-beta.18" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/toggle": "npm:^3.8.5" + "@react-types/button": "npm:^3.12.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/c360899930f648c56f16bf5d0d2d8c01b5d59449b87fff4e758e68127aa8e453396ffbdc6ee0fa2fbcf18fce73472041da36d21929b2d28bd55770bbfd75e384 + languageName: node + linkType: hard + +"@react-aria/calendar@npm:^3.8.3": + version: 3.8.3 + resolution: "@react-aria/calendar@npm:3.8.3" + dependencies: + "@internationalized/date": "npm:^3.8.2" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/live-announcer": "npm:^3.4.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/calendar": "npm:^3.8.2" + "@react-types/button": "npm:^3.12.2" + "@react-types/calendar": "npm:^3.7.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/53570c8c95fced3704a908ba218ee316db46f5c58352a5970f3b0a863257d8293dd732b0aaeb983921358f45acd50df623db88afa49df4d6735508d08369212b + languageName: node + linkType: hard + +"@react-aria/checkbox@npm:^3.15.7": + version: 3.15.7 + resolution: "@react-aria/checkbox@npm:3.15.7" + dependencies: + "@react-aria/form": "npm:^3.0.18" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/toggle": "npm:^3.11.5" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/checkbox": "npm:^3.6.15" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/toggle": "npm:^3.8.5" + "@react-types/checkbox": "npm:^3.9.5" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/a041baf3977d41f0f46db184b80da9f4d928209ca0bd764cb43202e5fc2f7a1ed44d7b9963d38f11dab934bf0ac5c0ee9311894b8177ed890c4f174b0640b8c8 + languageName: node + linkType: hard + +"@react-aria/collections@npm:3.0.0-rc.3": + version: 3.0.0-rc.3 + resolution: "@react-aria/collections@npm:3.0.0-rc.3" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/ssr": "npm:^3.9.9" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/a7a6ba15554ba6f3047cf6898a222e800b8317d4f0b05dbe11cf4301bc3aaa249ea2ae855ec4b0afd9f95c33e47f9ad10227299f6c382995155d91a94ce91493 + languageName: node + linkType: hard + +"@react-aria/color@npm:^3.0.9": + version: 3.0.9 + resolution: "@react-aria/color@npm:3.0.9" + dependencies: + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/numberfield": "npm:^3.11.16" + "@react-aria/slider": "npm:^3.7.21" + "@react-aria/spinbutton": "npm:^3.6.16" + "@react-aria/textfield": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.29.1" + "@react-aria/visually-hidden": "npm:^3.8.25" + "@react-stately/color": "npm:^3.8.6" + "@react-stately/form": "npm:^3.1.5" + "@react-types/color": "npm:^3.0.6" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/387b1609bafa89297525384925f5c7e290f5a2b592488daae4482873518401b0b5f1eb712000a3ad823f66888937499d3e0522b85ae9adc1e819ea8761abf19a + languageName: node + linkType: hard + +"@react-aria/combobox@npm:^3.12.5": + version: 3.12.5 + resolution: "@react-aria/combobox@npm:3.12.5" + dependencies: + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/listbox": "npm:^3.14.6" + "@react-aria/live-announcer": "npm:^3.4.3" + "@react-aria/menu": "npm:^3.18.5" + "@react-aria/overlays": "npm:^3.27.3" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/textfield": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/combobox": "npm:^3.10.6" + "@react-stately/form": "npm:^3.1.5" + "@react-types/button": "npm:^3.12.2" + "@react-types/combobox": "npm:^3.13.6" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/171d27bb3a2eefc3c5e3ecdca1d760e44116a68d6169bfa214b05caf840aa879033855a3c1ea273f8d288d066800182f4f8a1299c3dbb7f0dff137db78ad9c41 + languageName: node + linkType: hard + +"@react-aria/datepicker@npm:^3.14.5": + version: 3.14.5 + resolution: "@react-aria/datepicker@npm:3.14.5" + dependencies: + "@internationalized/date": "npm:^3.8.2" + "@internationalized/number": "npm:^3.6.3" + "@internationalized/string": "npm:^3.2.7" + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/form": "npm:^3.0.18" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/spinbutton": "npm:^3.6.16" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/datepicker": "npm:^3.14.2" + "@react-stately/form": "npm:^3.1.5" + "@react-types/button": "npm:^3.12.2" + "@react-types/calendar": "npm:^3.7.2" + "@react-types/datepicker": "npm:^3.12.2" + "@react-types/dialog": "npm:^3.5.19" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/440475577e9aee6c18c5e8f5d2c91f22f5a528ee283b472627eb8cf226c7a2125fa537b30b7e5f20e9d6325631e13c5dfa23f88d6f7e6c1e9fd41eff8ccb7590 + languageName: node + linkType: hard + +"@react-aria/dialog@npm:^3.5.27": + version: 3.5.27 + resolution: "@react-aria/dialog@npm:3.5.27" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/overlays": "npm:^3.27.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/dialog": "npm:^3.5.19" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/21257bb14352ab611e5659ce555cd845d8de51d30eea0cf7882b5119848c2136cd35f796a3031bbbcbc9640bffadeb35eee6f6254b34f2791c5a739c550e60da + languageName: node + linkType: hard + +"@react-aria/disclosure@npm:^3.0.6": + version: 3.0.6 + resolution: "@react-aria/disclosure@npm:3.0.6" + dependencies: + "@react-aria/ssr": "npm:^3.9.9" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/disclosure": "npm:^3.0.5" + "@react-types/button": "npm:^3.12.2" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/c1e602d6bc91b8b4884b2ec4ef4c3f1ea3d3052ead219a73eb4be110348ef0bde8fd3a68af92e7d8af83a59465b208e8a0d363b1ecffde53134516ce13f0361a + languageName: node + linkType: hard + +"@react-aria/dnd@npm:^3.10.1": + version: 3.10.1 + resolution: "@react-aria/dnd@npm:3.10.1" + dependencies: + "@internationalized/string": "npm:^3.2.7" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/live-announcer": "npm:^3.4.3" + "@react-aria/overlays": "npm:^3.27.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/dnd": "npm:^3.6.0" + "@react-types/button": "npm:^3.12.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/bf1058f59d20054571541455d9fa25eff469363b6f65852d237d9479bee4d6323fc3bd8497115d16e93d4396ab2faba0317875416c93d65a1a566881771624c1 + languageName: node + linkType: hard + +"@react-aria/focus@npm:^3.20.5": + version: 3.20.5 + resolution: "@react-aria/focus@npm:3.20.5" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" "@swc/helpers": "npm:^0.5.0" clsx: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/7f4652ebbb19a4ede9abce38bf756a44fdebcd6aa6d095ac304cefbed014f1456c45b3cc4e66d0132ffc55ebd5b9c8ba1ed83f61c39b63007a307d06cc12efc3 + checksum: 10/543c2f18c1d4f10940662608ca67947d1bc22630ae272e73136b36c2aefec61243148668201442132825bf4e9c0ab64add86c9870b9a51cb7bde2dfab7453a6e languageName: node linkType: hard -"@react-aria/i18n@npm:^3.12.7": - version: 3.12.7 - resolution: "@react-aria/i18n@npm:3.12.7" +"@react-aria/form@npm:^3.0.18": + version: 3.0.18 + resolution: "@react-aria/form@npm:3.0.18" dependencies: - "@internationalized/date": "npm:^3.7.0" - "@internationalized/message": "npm:^3.1.6" - "@internationalized/number": "npm:^3.6.0" - "@internationalized/string": "npm:^3.2.5" - "@react-aria/ssr": "npm:^3.9.7" - "@react-aria/utils": "npm:^3.28.1" - "@react-types/shared": "npm:^3.28.0" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/form": "npm:^3.1.5" + "@react-types/shared": "npm:^3.30.0" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/5f086ccc0a7e03637a4c74b2178aa8da9d5992eb15bc55f6ae804692bce01eeca89cc05e967eda6f2705ffb61404c5f9f995f9907063fba212685a7145f54d3d + checksum: 10/8c3edfaf3f2be28a16d7b0dcf00c677926e6e505fe26ebc348201f6f4e9e44aced1f17400a6c19717b18a3378be28438f269b438317a18f2070529fe2ac1e770 languageName: node linkType: hard -"@react-aria/interactions@npm:^3.24.1": - version: 3.24.1 - resolution: "@react-aria/interactions@npm:3.24.1" +"@react-aria/grid@npm:^3.14.2": + version: 3.14.2 + resolution: "@react-aria/grid@npm:3.14.2" dependencies: - "@react-aria/ssr": "npm:^3.9.7" - "@react-aria/utils": "npm:^3.28.1" - "@react-stately/flags": "npm:^3.1.0" - "@react-types/shared": "npm:^3.28.0" + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/live-announcer": "npm:^3.4.3" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/grid": "npm:^3.11.3" + "@react-stately/selection": "npm:^3.20.3" + "@react-types/checkbox": "npm:^3.9.5" + "@react-types/grid": "npm:^3.3.3" + "@react-types/shared": "npm:^3.30.0" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/79e40d68b980a84b0a64d8ebf030303558693b489694d439e46e419c79355d632cf3975a52eed30ddce90fff115d9172f6e3e8af5777b69c1a143f4ad81fef1e + checksum: 10/5f19539b1fe01d82b3693166269acae3fe74467b318f257babca79e5689b932ec83e5d7894ea0cbfd5e568fae3ffa61d04ae049a8967af09384f12d8e75792fa languageName: node linkType: hard -"@react-aria/overlays@npm:^3.26.1": - version: 3.26.1 - resolution: "@react-aria/overlays@npm:3.26.1" +"@react-aria/gridlist@npm:^3.13.2": + version: 3.13.2 + resolution: "@react-aria/gridlist@npm:3.13.2" dependencies: - "@react-aria/focus": "npm:^3.20.1" - "@react-aria/i18n": "npm:^3.12.7" - "@react-aria/interactions": "npm:^3.24.1" - "@react-aria/ssr": "npm:^3.9.7" - "@react-aria/utils": "npm:^3.28.1" - "@react-aria/visually-hidden": "npm:^3.8.21" - "@react-stately/overlays": "npm:^3.6.14" - "@react-types/button": "npm:^3.11.0" - "@react-types/overlays": "npm:^3.8.13" - "@react-types/shared": "npm:^3.28.0" + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/grid": "npm:^3.14.2" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/list": "npm:^3.12.3" + "@react-stately/tree": "npm:^3.9.0" + "@react-types/shared": "npm:^3.30.0" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/25d582fa5905c9ec42339ddae4aa9fce5d606a504e657d243b0b72fb77316b879c105b4d89e9c394de0aa9ea8514c38c42278d36b6fdd5de57233785fbd17a8c + checksum: 10/ea5e2f986fc74b627afe4918f94aa8cc778f146d7781822ce55788bb59a90e5765446d7c0bb5bcc86c1e4dd17ced3bd52ab6f703a50e8253bef25b70d3be0b56 languageName: node linkType: hard -"@react-aria/ssr@npm:^3.9.7": - version: 3.9.7 - resolution: "@react-aria/ssr@npm:3.9.7" +"@react-aria/i18n@npm:^3.12.10": + version: 3.12.10 + resolution: "@react-aria/i18n@npm:3.12.10" + dependencies: + "@internationalized/date": "npm:^3.8.2" + "@internationalized/message": "npm:^3.1.8" + "@internationalized/number": "npm:^3.6.3" + "@internationalized/string": "npm:^3.2.7" + "@react-aria/ssr": "npm:^3.9.9" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/6f331ceb62a1364d32f2336257ed82715f712e27d3e04a173296efc320f6532626561e883c2c3639ef4efdcd03498005e87d7160d8942ea8255d18d57770d3a0 + languageName: node + linkType: hard + +"@react-aria/interactions@npm:^3.25.3": + version: 3.25.3 + resolution: "@react-aria/interactions@npm:3.25.3" + dependencies: + "@react-aria/ssr": "npm:^3.9.9" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/flags": "npm:^3.1.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/21754f3e15c35430262b8e283d00010543c8855bac9b9feeb701c967394940d4063b63380b59637c7da8e9b922f29edb3525d15d6005202a6dc22fd32a0d951f + languageName: node + linkType: hard + +"@react-aria/label@npm:^3.7.19": + version: 3.7.19 + resolution: "@react-aria/label@npm:3.7.19" + dependencies: + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/984a97ac0bca6487031bc006d8b697766d990e78e381d5e230414429542e51d84197459b45b834069615d9f17f5b162e671e464e62f78854ac8fbb458d1b3346 + languageName: node + linkType: hard + +"@react-aria/landmark@npm:^3.0.4": + version: 3.0.4 + resolution: "@react-aria/landmark@npm:3.0.4" + dependencies: + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/ab1d9cc93e83b0f486157dc878474e59b601631220bb688840c55baf6ccfd65d8fe2a04adaaa055f0e2883f3e7428b74d52cc13d32e15f930c0cb1c189309ee8 + languageName: node + linkType: hard + +"@react-aria/link@npm:^3.8.3": + version: 3.8.3 + resolution: "@react-aria/link@npm:3.8.3" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/link": "npm:^3.6.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/978f875a81616f46706da3596dfb52845af93420c3b2ce29d83c8eed5203cd0b5bdd3f988039dff3e3420de668927be46c2971333d628fa7af3f83b7796159f8 + languageName: node + linkType: hard + +"@react-aria/listbox@npm:^3.14.6": + version: 3.14.6 + resolution: "@react-aria/listbox@npm:3.14.6" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/list": "npm:^3.12.3" + "@react-types/listbox": "npm:^3.7.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/068a1e5d56516436dbc8bde6e3a98db7ec277f0a0ce73ce6334e762c196570ae751dc0b0503f1e66624569d89ce412bc9292fa67f5a52db03db3104bb1d2f300 + languageName: node + linkType: hard + +"@react-aria/live-announcer@npm:^3.4.3": + version: 3.4.3 + resolution: "@react-aria/live-announcer@npm:3.4.3" + dependencies: + "@swc/helpers": "npm:^0.5.0" + checksum: 10/73e3fc9bc2796cbccbcd0e01e2b4bd62d0b1a3915b1d0f01d2054141033c6b2a5275a7b7c52bdc0a3b9ef738881b4a84bb5d6df57d1ee066278e5bdf40a8f979 + languageName: node + linkType: hard + +"@react-aria/menu@npm:^3.18.5": + version: 3.18.5 + resolution: "@react-aria/menu@npm:3.18.5" + dependencies: + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/overlays": "npm:^3.27.3" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/menu": "npm:^3.9.5" + "@react-stately/selection": "npm:^3.20.3" + "@react-stately/tree": "npm:^3.9.0" + "@react-types/button": "npm:^3.12.2" + "@react-types/menu": "npm:^3.10.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/409f21cfa7d9cca62ca7c21b047bd72314181782b2d6ea0bb16a33075280653eef1b4d9c4b0ad15ebe72ec59e12103f3b1f86e6e7cd8886571956d6d099ae200 + languageName: node + linkType: hard + +"@react-aria/meter@npm:^3.4.24": + version: 3.4.24 + resolution: "@react-aria/meter@npm:3.4.24" + dependencies: + "@react-aria/progress": "npm:^3.4.24" + "@react-types/meter": "npm:^3.4.10" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/980e46d3b784c59523ea9eb342c28e2a29e0549937da4def5f61ce011abb2da40f833dbe3f14550ffcd384860e08d4e8ee286e58834100b04c7de93fa5587e50 + languageName: node + linkType: hard + +"@react-aria/numberfield@npm:^3.11.16": + version: 3.11.16 + resolution: "@react-aria/numberfield@npm:3.11.16" + dependencies: + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/spinbutton": "npm:^3.6.16" + "@react-aria/textfield": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/numberfield": "npm:^3.9.13" + "@react-types/button": "npm:^3.12.2" + "@react-types/numberfield": "npm:^3.8.12" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/3e83938fe4fe76ef662c543734988b56d60824d8d2d19de5adffe06191106aa98b2b6dd4241ed49fc3e7ad3e1c4c43cbdd81c6fc32d728959e5b377faf8ffdcd + languageName: node + linkType: hard + +"@react-aria/overlays@npm:^3.26.1, @react-aria/overlays@npm:^3.27.3": + version: 3.27.3 + resolution: "@react-aria/overlays@npm:3.27.3" + dependencies: + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/ssr": "npm:^3.9.9" + "@react-aria/utils": "npm:^3.29.1" + "@react-aria/visually-hidden": "npm:^3.8.25" + "@react-stately/overlays": "npm:^3.6.17" + "@react-types/button": "npm:^3.12.2" + "@react-types/overlays": "npm:^3.8.16" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/8cee1a99bc9ff4ecf0577b76f54cccaf684dde2278d0c270e82c683ef6f42bb8100de4596ae411e775643a3765abbd87fdb1c5d46bf05481ed9df9ed9b82276a + languageName: node + linkType: hard + +"@react-aria/progress@npm:^3.4.24": + version: 3.4.24 + resolution: "@react-aria/progress@npm:3.4.24" + dependencies: + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/progress": "npm:^3.5.13" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/e7e4cf6f904f1b35a54704aa6d473c4eb239130ba8fbba71f6298b68a92c067162132fb5adf1e6f8960bdc8a2913004d979e088575761b0ba5252c28cdf62b0b + languageName: node + linkType: hard + +"@react-aria/radio@npm:^3.11.5": + version: 3.11.5 + resolution: "@react-aria/radio@npm:3.11.5" + dependencies: + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/form": "npm:^3.0.18" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/radio": "npm:^3.10.14" + "@react-types/radio": "npm:^3.8.10" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/63bbca9ad7a065453f60bb759c0377a8febeb0e4582807726d7eef36a05c806d6d0c9d86ad7b73c42cc0a1c47bab5a1d0369e0d670fc4f58924662395418b24d + languageName: node + linkType: hard + +"@react-aria/searchfield@npm:^3.8.6": + version: 3.8.6 + resolution: "@react-aria/searchfield@npm:3.8.6" + dependencies: + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/textfield": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/searchfield": "npm:^3.5.13" + "@react-types/button": "npm:^3.12.2" + "@react-types/searchfield": "npm:^3.6.3" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/7db3dd61084e49c9d2c9fa9aa10821087aff91cfadeade78b559cd1877c67a49dcdd238b48a8b2501cd7070b9123d276e8ef6170cb5438cdce28ef6889eea366 + languageName: node + linkType: hard + +"@react-aria/select@npm:^3.15.7": + version: 3.15.7 + resolution: "@react-aria/select@npm:3.15.7" + dependencies: + "@react-aria/form": "npm:^3.0.18" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/listbox": "npm:^3.14.6" + "@react-aria/menu": "npm:^3.18.5" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-aria/visually-hidden": "npm:^3.8.25" + "@react-stately/select": "npm:^3.6.14" + "@react-types/button": "npm:^3.12.2" + "@react-types/select": "npm:^3.9.13" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/8e0974ddc558051c384a7d9da1eeea4623cbe4a61f0f0402aebeb4be23a1cb5875b839608dcc6aaca4ab6c288143c202eb0fa2ee3ed8f62b7e33d142604f2fba + languageName: node + linkType: hard + +"@react-aria/selection@npm:^3.24.3": + version: 3.24.3 + resolution: "@react-aria/selection@npm:3.24.3" + dependencies: + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/selection": "npm:^3.20.3" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/f2575161b69d87e7c5ee7d0d658c96c97e815a0de74d121f3744efcfb62f181efd3250f3842b0347d54e9d27c85b41292728927efb3bcc2bdcf8873d92647eb1 + languageName: node + linkType: hard + +"@react-aria/separator@npm:^3.4.10": + version: 3.4.10 + resolution: "@react-aria/separator@npm:3.4.10" + dependencies: + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/706aed510aa306f70ff98a82061995f3fb1ffa6ca0134b282ac9a776441fe4241ff87f8008fa04761b3edaadf8020c96f55720821a7ff4b5c362cad3eb7ed98a + languageName: node + linkType: hard + +"@react-aria/slider@npm:^3.7.21": + version: 3.7.21 + resolution: "@react-aria/slider@npm:3.7.21" + dependencies: + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/slider": "npm:^3.6.5" + "@react-types/shared": "npm:^3.30.0" + "@react-types/slider": "npm:^3.7.12" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/1a063533b33f224adf782840664e517a8034a28129648908b7391c648c4141c58103dc9733c81f12b1848320669dcca64a059a3279c4114ae81df796a274da59 + languageName: node + linkType: hard + +"@react-aria/spinbutton@npm:^3.6.16": + version: 3.6.16 + resolution: "@react-aria/spinbutton@npm:3.6.16" + dependencies: + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/live-announcer": "npm:^3.4.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/button": "npm:^3.12.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/2f032d3206738902e91661b0e5ff1cdd75005c40b75a219bbf6aab48b3aad16d1d1090c873ab2e1e0400dee74db56c455948ff2ebe0a6c7f1edb200dfea826e5 + languageName: node + linkType: hard + +"@react-aria/ssr@npm:^3.9.9": + version: 3.9.9 + resolution: "@react-aria/ssr@npm:3.9.9" dependencies: "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/a5c8e9ffee1dfd3c5b9f66051a7faab11d53ba001ac7f476b61fa4b38fd8b4835c1a85ff2157ec25fb5b63beb88fbae9e80610fa065a30cbe30875fcbca3114b + checksum: 10/d0675357b51784e9dbe3add97aa0e6acc810ab0ef01d1c7a317ff8ead5eae64d66c60cc751ea3d10f874bb381e445099bb31cb7f1955801848ce6dce91b588a2 languageName: node linkType: hard -"@react-aria/utils@npm:^3.28.1": - version: 3.28.1 - resolution: "@react-aria/utils@npm:3.28.1" +"@react-aria/switch@npm:^3.7.5": + version: 3.7.5 + resolution: "@react-aria/switch@npm:3.7.5" dependencies: - "@react-aria/ssr": "npm:^3.9.7" - "@react-stately/flags": "npm:^3.1.0" - "@react-stately/utils": "npm:^3.10.5" - "@react-types/shared": "npm:^3.28.0" + "@react-aria/toggle": "npm:^3.11.5" + "@react-stately/toggle": "npm:^3.8.5" + "@react-types/shared": "npm:^3.30.0" + "@react-types/switch": "npm:^3.5.12" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/1f716296b444b1db28bd55493ddabbdc403fc48a865cbbd0e383e69e2e42b97e778446383740330cf7e76a4a2dcc0de6f84fed1cddd0f0760b6ffa34118794cb + languageName: node + linkType: hard + +"@react-aria/table@npm:^3.17.5": + version: 3.17.5 + resolution: "@react-aria/table@npm:3.17.5" + dependencies: + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/grid": "npm:^3.14.2" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/live-announcer": "npm:^3.4.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-aria/visually-hidden": "npm:^3.8.25" + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/flags": "npm:^3.1.2" + "@react-stately/table": "npm:^3.14.3" + "@react-types/checkbox": "npm:^3.9.5" + "@react-types/grid": "npm:^3.3.3" + "@react-types/shared": "npm:^3.30.0" + "@react-types/table": "npm:^3.13.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/d10e2432b66b0279413fec031673172ed8c49d93185cd1657511467b04ec780bc089a7e45d75c9ba71c94419355107e961e679acd254da8a19d446e1c9343a70 + languageName: node + linkType: hard + +"@react-aria/tabs@npm:^3.10.5": + version: 3.10.5 + resolution: "@react-aria/tabs@npm:3.10.5" + dependencies: + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/tabs": "npm:^3.8.3" + "@react-types/shared": "npm:^3.30.0" + "@react-types/tabs": "npm:^3.3.16" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/39db9d4d5a0e87e7cbdb91bcdc5385e02fd21cb1e1c181ecf226e6b0fba9c948c03623cceb11d831a87056c6bc3a0528a7e7a9f58292b0c4dfaa6b9cce56f3bb + languageName: node + linkType: hard + +"@react-aria/tag@npm:^3.6.2": + version: 3.6.2 + resolution: "@react-aria/tag@npm:3.6.2" + dependencies: + "@react-aria/gridlist": "npm:^3.13.2" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/list": "npm:^3.12.3" + "@react-types/button": "npm:^3.12.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/fbfb3a9769077bcc23199aacd80c83b42e89f752d0690c6a5751d9a95b6e760365be2915fa134db9ad040355b341b150270f553ece6f791378a27102e90e7fca + languageName: node + linkType: hard + +"@react-aria/textfield@npm:^3.17.5": + version: 3.17.5 + resolution: "@react-aria/textfield@npm:3.17.5" + dependencies: + "@react-aria/form": "npm:^3.0.18" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/shared": "npm:^3.30.0" + "@react-types/textfield": "npm:^3.12.3" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/59f2df9650b8acb961a1eaec59bb8a0b18ef6b0b1f3d8bbc2f398ae4fcfc8b3a2a2c22c357319bb53bc166b4e2c624ab58187104621b164de22f465ab342f72d + languageName: node + linkType: hard + +"@react-aria/toast@npm:^3.0.5": + version: 3.0.5 + resolution: "@react-aria/toast@npm:3.0.5" + dependencies: + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/landmark": "npm:^3.0.4" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/toast": "npm:^3.1.1" + "@react-types/button": "npm:^3.12.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/d674bde2446e29b44fe393386da154d8a467954edf29c9c2cdb922972ea6f962e4f16220c03a410bb0be555b9f96f96f71146762123b9d9187e081a74696bee2 + languageName: node + linkType: hard + +"@react-aria/toggle@npm:^3.11.5": + version: 3.11.5 + resolution: "@react-aria/toggle@npm:3.11.5" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/toggle": "npm:^3.8.5" + "@react-types/checkbox": "npm:^3.9.5" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/6490a53496b317d3f12c93e347b2d9dd0e7e4835d66eb0823612d3918836338566c0ed2ac22bf4d94649c6019bf411d9c9a1b8f49d420d52eab8f737c2c3f8c5 + languageName: node + linkType: hard + +"@react-aria/toolbar@npm:3.0.0-beta.18": + version: 3.0.0-beta.18 + resolution: "@react-aria/toolbar@npm:3.0.0-beta.18" + dependencies: + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/0ea1d0d73dad265773678fc66712800840b20558d3e19f0b6aa1f0953ba57e48be9ad293d1f48db3e609b017998ded11227d1aebfb998bcfbe05467498318ced + languageName: node + linkType: hard + +"@react-aria/tooltip@npm:^3.8.5": + version: 3.8.5 + resolution: "@react-aria/tooltip@npm:3.8.5" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/tooltip": "npm:^3.5.5" + "@react-types/shared": "npm:^3.30.0" + "@react-types/tooltip": "npm:^3.4.18" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/4398c4ca41ab302b8e50a86c07909b461275bf5025c0163ecb5ae57bbe98e15164927ed58a1137a235996626b19d5397916736ee3257d8bc33d09ec0b1d3f70e + languageName: node + linkType: hard + +"@react-aria/tree@npm:^3.1.1": + version: 3.1.1 + resolution: "@react-aria/tree@npm:3.1.1" + dependencies: + "@react-aria/gridlist": "npm:^3.13.2" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/tree": "npm:^3.9.0" + "@react-types/button": "npm:^3.12.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/d387de7a9de81df8f45a16882ddb24ecf8da06638822b8be4d66357b0b3edd67a85aa185f8cf947c4c8f9036c396bea406dd5e277ef0f74828a29d2dd86fd809 + languageName: node + linkType: hard + +"@react-aria/utils@npm:^3.29.1": + version: 3.29.1 + resolution: "@react-aria/utils@npm:3.29.1" + dependencies: + "@react-aria/ssr": "npm:^3.9.9" + "@react-stately/flags": "npm:^3.1.2" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/shared": "npm:^3.30.0" "@swc/helpers": "npm:^0.5.0" clsx: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/80c1ecf6a570a57aec5ef4eaa2a8715b12bcd4d5b2de26934e7e5af5a2c38cdd229cac116624ecd3e7c97271e534111f600d0baf1976675b778799f8711ce9bf + checksum: 10/90af5ee5b9c7db063ba66a28cfecb8594f356c2b0fc4cce2fcee94543f3b160214e4a9c280e83355af89452b88b1def8856493705c770874da7121ba1d3d0cc0 languageName: node linkType: hard -"@react-aria/visually-hidden@npm:^3.8.21": - version: 3.8.21 - resolution: "@react-aria/visually-hidden@npm:3.8.21" +"@react-aria/virtualizer@npm:^4.1.7": + version: 4.1.7 + resolution: "@react-aria/virtualizer@npm:4.1.7" dependencies: - "@react-aria/interactions": "npm:^3.24.1" - "@react-aria/utils": "npm:^3.28.1" - "@react-types/shared": "npm:^3.28.0" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/virtualizer": "npm:^4.4.1" + "@react-types/shared": "npm:^3.30.0" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/50b4598ae295336cd95f38523abdef8b7fbde2b6b52c99a5fa17527474d58e7444199b97d2409dd8c478321c76bf246dd928ddcb1c303ebc8ab04417a5a59516 + checksum: 10/4bb747314ac96da76c64490041ab95f4a0889bab7e5805b0e18cb71bdb290ae73932ddd866657cfcdfac8382d6b3017c3dfb1a91ae415a6d1ab45b11d88f66ec + languageName: node + linkType: hard + +"@react-aria/visually-hidden@npm:^3.8.25": + version: 3.8.25 + resolution: "@react-aria/visually-hidden@npm:3.8.25" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/362a793beac35fde101e598f4cac9cd98394b9a0171309842e2428b25074ee696358c45d2ded8244bc40a9db8471440994be59b0f347d2c643092c6445aaca61 languageName: node linkType: hard @@ -16086,22 +15485,6 @@ __metadata: languageName: node linkType: hard -"@react-hookz/web@npm:^23.0.0": - version: 23.1.0 - resolution: "@react-hookz/web@npm:23.1.0" - dependencies: - "@react-hookz/deep-equal": "npm:^1.0.4" - peerDependencies: - js-cookie: ^3.0.5 - react: ^16.8 || ^17 || ^18 - react-dom: ^16.8 || ^17 || ^18 - peerDependenciesMeta: - js-cookie: - optional: true - checksum: 10/7f345fc286ee3bae74b76b909898213a589250856783805d0356d8ef9ca0e3d1638e7499d8b77ae1dff6350dec230fda468e0e4c8fc05a9997902569d3d81b49 - languageName: node - linkType: hard - "@react-hookz/web@npm:^24.0.0": version: 24.0.4 resolution: "@react-hookz/web@npm:24.0.4" @@ -16118,123 +15501,776 @@ __metadata: languageName: node linkType: hard -"@react-stately/flags@npm:^3.1.0": - version: 3.1.0 - resolution: "@react-stately/flags@npm:3.1.0" +"@react-stately/autocomplete@npm:3.0.0-beta.2": + version: 3.0.0-beta.2 + resolution: "@react-stately/autocomplete@npm:3.0.0-beta.2" dependencies: + "@react-stately/utils": "npm:^3.10.7" "@swc/helpers": "npm:^0.5.0" - checksum: 10/022f183415ad8da9484498c506eed97f7562362b427537eb6325a4f37dcbcf26a52f7d135add1574ea049d6a2baa7b0c7c835fb62ac046ca6e8a3cc43bcaf627 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/836e593688c33ab118547b60fae93ffb1e9e440aed0ffc8c382290f414bfdc313a88964a00a6bd8b7e7b9aca691538b66adfaafee6065b93cedeb80c5aeef675 languageName: node linkType: hard -"@react-stately/overlays@npm:^3.6.14": +"@react-stately/calendar@npm:^3.8.2": + version: 3.8.2 + resolution: "@react-stately/calendar@npm:3.8.2" + dependencies: + "@internationalized/date": "npm:^3.8.2" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/calendar": "npm:^3.7.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/d2858a42dc2c324248c4c1ddb6f6fe66de11bfe2703c85f0fd739075712f668427212207626ed35af2fcbe15cd0ee6e2ad53eafe98766fd7352e0e2f134435c4 + languageName: node + linkType: hard + +"@react-stately/checkbox@npm:^3.6.15": + version: 3.6.15 + resolution: "@react-stately/checkbox@npm:3.6.15" + dependencies: + "@react-stately/form": "npm:^3.1.5" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/checkbox": "npm:^3.9.5" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/4c2b5213395e59432c59609b3cd2ca8316cf77f18716dcee2abe31af5c4a12fac3125408799b0631771e1095fc5924de7850e09c3fa6e53f56c0f79debc0db77 + languageName: node + linkType: hard + +"@react-stately/collections@npm:^3.12.5": + version: 3.12.5 + resolution: "@react-stately/collections@npm:3.12.5" + dependencies: + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/12024f72ec6602c9447e2bc134da566d99da2e29b41ffba207712be31aaf121b805505397dcfb1ee07dd8d234c40d3018c33364adae271f3231a004775a96fd8 + languageName: node + linkType: hard + +"@react-stately/color@npm:^3.8.6": + version: 3.8.6 + resolution: "@react-stately/color@npm:3.8.6" + dependencies: + "@internationalized/number": "npm:^3.6.3" + "@internationalized/string": "npm:^3.2.7" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/numberfield": "npm:^3.9.13" + "@react-stately/slider": "npm:^3.6.5" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/color": "npm:^3.0.6" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/20ec019c71676c5d336c89b703729c0c8098219c942843a3018e5542fee49c6db56c47b746c4c9cddfb9ea48213a3a860861722fc5b41239e3de827c2ef84351 + languageName: node + linkType: hard + +"@react-stately/combobox@npm:^3.10.6": + version: 3.10.6 + resolution: "@react-stately/combobox@npm:3.10.6" + dependencies: + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/list": "npm:^3.12.3" + "@react-stately/overlays": "npm:^3.6.17" + "@react-stately/select": "npm:^3.6.14" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/combobox": "npm:^3.13.6" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/8bc67ee18d1c896620cc11233a8efdaf8ec7f5ec31d068fbbc80e75a54e385617518e2b420f3211c17b905e3cac6026f8b056ae96ee114ea4e897f4ce1951502 + languageName: node + linkType: hard + +"@react-stately/data@npm:^3.13.1": + version: 3.13.1 + resolution: "@react-stately/data@npm:3.13.1" + dependencies: + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/b6eea6a721d4de13ffad7aa12a76012d297e1fbbc919bad35d99f57bf481feb547d56c74467e1dadf8f0cb6be9027e54faa6b10dc4aeb1b46a5c6598218baa2c + languageName: node + linkType: hard + +"@react-stately/datepicker@npm:^3.14.2": + version: 3.14.2 + resolution: "@react-stately/datepicker@npm:3.14.2" + dependencies: + "@internationalized/date": "npm:^3.8.2" + "@internationalized/string": "npm:^3.2.7" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/overlays": "npm:^3.6.17" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/datepicker": "npm:^3.12.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/dd10f99f32e12eef9d361eb183cd52113a1857613be844da9895b5a8954260ace283e43bd1bae3d73a71f1cb5e6381627999f8a24d4ed320903dba50f864183b + languageName: node + linkType: hard + +"@react-stately/disclosure@npm:^3.0.5": + version: 3.0.5 + resolution: "@react-stately/disclosure@npm:3.0.5" + dependencies: + "@react-stately/utils": "npm:^3.10.7" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/b1f3f80e6523a0fb0c325064162a59635f560f41dbadd51fd89db02bbad9bafa843b7b1565420444b3b266f2eb92dcf556916b0dabe06d32bb86a2106849c41c + languageName: node + linkType: hard + +"@react-stately/dnd@npm:^3.6.0": + version: 3.6.0 + resolution: "@react-stately/dnd@npm:3.6.0" + dependencies: + "@react-stately/selection": "npm:^3.20.3" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/9fe59a8d5849af27b5fb0609691bdf0ab14ef8cad8a27405e0c83842d44b171e6d1409716b8e80f12797b187bb553f4acf71a87cd5acd86c76137a3525449194 + languageName: node + linkType: hard + +"@react-stately/flags@npm:^3.1.2": + version: 3.1.2 + resolution: "@react-stately/flags@npm:3.1.2" + dependencies: + "@swc/helpers": "npm:^0.5.0" + checksum: 10/a020c3680c36d9624f765c5916ce95d69959f64887928e8f380f11b5362bb0499a901a5842e4e12eb8e5a776af59212b1ee0c4c6a6681ce75f61dace8b2f9c40 + languageName: node + linkType: hard + +"@react-stately/form@npm:^3.1.5": + version: 3.1.5 + resolution: "@react-stately/form@npm:3.1.5" + dependencies: + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/6c392949ca8ac045163f3716a41f03d6ccbe3fd5704401cbece0ba572d43abbb350f5019534765540d5b511b9aeba70ca2c03dc4ba218074b59a3efbea601564 + languageName: node + linkType: hard + +"@react-stately/grid@npm:^3.11.3": + version: 3.11.3 + resolution: "@react-stately/grid@npm:3.11.3" + dependencies: + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/selection": "npm:^3.20.3" + "@react-types/grid": "npm:^3.3.3" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/09655c28cef0c3007024af0c01982d2c079e558169dcf1994966c2526c6346b442068e2e9700a8abbbbcbd13781cc0eb5b346d974dc9a85dd5c263722d96f5c7 + languageName: node + linkType: hard + +"@react-stately/layout@npm:^4.3.1": + version: 4.3.1 + resolution: "@react-stately/layout@npm:4.3.1" + dependencies: + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/table": "npm:^3.14.3" + "@react-stately/virtualizer": "npm:^4.4.1" + "@react-types/grid": "npm:^3.3.3" + "@react-types/shared": "npm:^3.30.0" + "@react-types/table": "npm:^3.13.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/f7d299038aefba602944b5caa81719388427778edc394582dfe14c70f9f485b74a0da6d0eeb6aab843e3fbdd6af251a3678b1a8c76c77f64adb16d695dbd22cd + languageName: node + linkType: hard + +"@react-stately/list@npm:^3.12.3": + version: 3.12.3 + resolution: "@react-stately/list@npm:3.12.3" + dependencies: + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/selection": "npm:^3.20.3" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/e8edce5e59e061a4b71621ce7617f4e48cbef5e5c5b6377808e0977f4cad4e541726f255bdeb1c18db867b35ab19883981882bbc27ddbc3e5c26722c496b02f8 + languageName: node + linkType: hard + +"@react-stately/menu@npm:^3.9.5": + version: 3.9.5 + resolution: "@react-stately/menu@npm:3.9.5" + dependencies: + "@react-stately/overlays": "npm:^3.6.17" + "@react-types/menu": "npm:^3.10.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/a8163b76de49ec10da7129c306304a96e8f4e689450ffdbf6ab5f56fd8b0316db5beb16ebc58da52c19b4abe28954a7200955c41e1a9088e40f5e5d00f975067 + languageName: node + linkType: hard + +"@react-stately/numberfield@npm:^3.9.13": + version: 3.9.13 + resolution: "@react-stately/numberfield@npm:3.9.13" + dependencies: + "@internationalized/number": "npm:^3.6.3" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/numberfield": "npm:^3.8.12" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/639e2064ed0afd23ec6681b32abca78f9453eb38d11b350247bf4038f6561579f6d444ead6c9c3317c1b1adaaaa22812053cfba8effdd8cb1b199d04b7ce1fbe + languageName: node + linkType: hard + +"@react-stately/overlays@npm:^3.6.17": + version: 3.6.17 + resolution: "@react-stately/overlays@npm:3.6.17" + dependencies: + "@react-stately/utils": "npm:^3.10.7" + "@react-types/overlays": "npm:^3.8.16" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/9c44f181cd8f0b99a40142aebf3d7e1d1df1e96a789b636a5ee877529e60fddece4ebcf32d9812d9848ce0298bf5cb6202df3b680552b32c89f1276bce3f8a7b + languageName: node + linkType: hard + +"@react-stately/radio@npm:^3.10.14": + version: 3.10.14 + resolution: "@react-stately/radio@npm:3.10.14" + dependencies: + "@react-stately/form": "npm:^3.1.5" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/radio": "npm:^3.8.10" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/45ad56b50f3fcd04c1b335f8f64caceaa6f8483fd84717a461ae04ed8718dabf06f88bf8bd89fcca27d2c6bc548b4a38f0e4ef53ad51f3320aeb85c67b288846 + languageName: node + linkType: hard + +"@react-stately/searchfield@npm:^3.5.13": + version: 3.5.13 + resolution: "@react-stately/searchfield@npm:3.5.13" + dependencies: + "@react-stately/utils": "npm:^3.10.7" + "@react-types/searchfield": "npm:^3.6.3" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/6114f1ce0c2e8ddbeedc5b5293783c4e1ecb0cc6f24e09cb6095e32be9b49bdc67619941c16f834850e31acfafd74f4241edaa3529f510ec490a2e6c8e7cdc0b + languageName: node + linkType: hard + +"@react-stately/select@npm:^3.6.14": version: 3.6.14 - resolution: "@react-stately/overlays@npm:3.6.14" + resolution: "@react-stately/select@npm:3.6.14" dependencies: - "@react-stately/utils": "npm:^3.10.5" - "@react-types/overlays": "npm:^3.8.13" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/list": "npm:^3.12.3" + "@react-stately/overlays": "npm:^3.6.17" + "@react-types/select": "npm:^3.9.13" + "@react-types/shared": "npm:^3.30.0" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/84ca6cd1a528fd0c94f5c8d0c6609a915fd7de75d70b222ab6e252456f8d67b0a394787aca97e59a9737b7e3d48bf0600d12e5d9487525e37305eea599a96fcf + checksum: 10/33116af11830f0d7bf7e10cb03e52ed9c36d17d94674d96440fd73ae8d7da1b93edc4de6170a3681493c76c0ad5c8b24c2961f170b2a784447743775dce32a65 languageName: node linkType: hard -"@react-stately/utils@npm:^3.10.5": - version: 3.10.5 - resolution: "@react-stately/utils@npm:3.10.5" +"@react-stately/selection@npm:^3.20.3": + version: 3.20.3 + resolution: "@react-stately/selection@npm:3.20.3" + dependencies: + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/9903810f5f4e2f37381aba8c81525a819de69316c3bd5d66b29fc5f22e49b3d349d26792e811f7167e16857879c8e6ce8d817d9838c69367481a42977a2022a2 + languageName: node + linkType: hard + +"@react-stately/slider@npm:^3.6.5": + version: 3.6.5 + resolution: "@react-stately/slider@npm:3.6.5" + dependencies: + "@react-stately/utils": "npm:^3.10.7" + "@react-types/shared": "npm:^3.30.0" + "@react-types/slider": "npm:^3.7.12" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/121f2f23cad929e7087825ec1a17c51610ac0e62a0b0951d042a6e6a354fcea1751575a7bf6648ae5054a260d89a7613dc0353bf0e9f76553e25c2dce1dba095 + languageName: node + linkType: hard + +"@react-stately/table@npm:^3.14.3": + version: 3.14.3 + resolution: "@react-stately/table@npm:3.14.3" + dependencies: + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/flags": "npm:^3.1.2" + "@react-stately/grid": "npm:^3.11.3" + "@react-stately/selection": "npm:^3.20.3" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/grid": "npm:^3.3.3" + "@react-types/shared": "npm:^3.30.0" + "@react-types/table": "npm:^3.13.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/04cd616d526b9b806808ea09ce596dc1fe5fff931deec5ccce3258d0a76ee02c787320004ccc2d3a2d67e54d39f7ab274673210254c8598bd99b767cec8eda1a + languageName: node + linkType: hard + +"@react-stately/tabs@npm:^3.8.3": + version: 3.8.3 + resolution: "@react-stately/tabs@npm:3.8.3" + dependencies: + "@react-stately/list": "npm:^3.12.3" + "@react-types/shared": "npm:^3.30.0" + "@react-types/tabs": "npm:^3.3.16" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/5fb036bc36b5c7fde32c6e7553795fad0f5b2d999837961298341e68d211444ca2a378554c5f73ccf0909e60299cdcd4b0e8c317e57f8c1662e6384957a1ed4a + languageName: node + linkType: hard + +"@react-stately/toast@npm:^3.1.1": + version: 3.1.1 + resolution: "@react-stately/toast@npm:3.1.1" + dependencies: + "@swc/helpers": "npm:^0.5.0" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/c6ff8b947920e05e3e817cbb478f31c45c64035801456c7c8052c37bea5d966a3ea2c74071c2bed6f2006dec160a6b1204b7cc98965a9c10ca3b4bb4d6765f39 + languageName: node + linkType: hard + +"@react-stately/toggle@npm:^3.8.5": + version: 3.8.5 + resolution: "@react-stately/toggle@npm:3.8.5" + dependencies: + "@react-stately/utils": "npm:^3.10.7" + "@react-types/checkbox": "npm:^3.9.5" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/3d8e6c4a39c64fc904355aaa3980e3a63595c24472e3f557716597ac6f9f26299dec5b0843b327b4fe1416436054a65e45ec79073a3be1fad8f093f0d2940864 + languageName: node + linkType: hard + +"@react-stately/tooltip@npm:^3.5.5": + version: 3.5.5 + resolution: "@react-stately/tooltip@npm:3.5.5" + dependencies: + "@react-stately/overlays": "npm:^3.6.17" + "@react-types/tooltip": "npm:^3.4.18" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/0744283f1dbb811502311370495b6722a59c251fe20ac2e5373aeed8f655ec9f89d92980426a69b7191c048f7c02e3a66c78a7018452ad4638c82e5b7f99b698 + languageName: node + linkType: hard + +"@react-stately/tree@npm:^3.9.0": + version: 3.9.0 + resolution: "@react-stately/tree@npm:3.9.0" + dependencies: + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/selection": "npm:^3.20.3" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/6980382c94f87a663124e9488e3cf5f8ea09b05fc2f0017cbb886aa19ce0d8525d8b6b8221f1e293a67543cc06786ea203813218c3444fe7e14b31d9eae65ca3 + languageName: node + linkType: hard + +"@react-stately/utils@npm:^3.10.7": + version: 3.10.7 + resolution: "@react-stately/utils@npm:3.10.7" dependencies: "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/76133eb64fa945216e51d8a81a0ebd06eeb78aa2d9c91d79eeb80ff44c70a6b0d6d940618b31b499fee8216640b3bf6183391151dc1769e756b56ff6b5e167ec + checksum: 10/b9c1a62d4f179e252d08a2f18120a02ffc8704424f257e81e5e74da1222e3acd1e821ce84220d87d9d7b4b6d4dc2fd58d5bb380546d198bf434b700cda8ac3d8 languageName: node linkType: hard -"@react-types/button@npm:^3.11.0": - version: 3.11.0 - resolution: "@react-types/button@npm:3.11.0" +"@react-stately/virtualizer@npm:^4.4.1": + version: 4.4.1 + resolution: "@react-stately/virtualizer@npm:4.4.1" dependencies: - "@react-types/shared": "npm:^3.28.0" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/6dbcc89f576eb6736deb1d5f92eab3c4de5ee5a78a8034b1cd2748c14d92ce05b40362ff5b629842e363d9843f823b5c044dabb3d7438f897777ff60fc5e3867 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/aab5c3a647dc43dd5120a6db4cdfdf24aa47f7fcabf273e05947e33af8cba172f4f442499729d53537a2aa46ca7292dcb94c6b4b4c5b017542df42c25a97a128 languageName: node linkType: hard -"@react-types/overlays@npm:^3.8.13": - version: 3.8.13 - resolution: "@react-types/overlays@npm:3.8.13" +"@react-types/autocomplete@npm:3.0.0-alpha.32": + version: 3.0.0-alpha.32 + resolution: "@react-types/autocomplete@npm:3.0.0-alpha.32" dependencies: - "@react-types/shared": "npm:^3.28.0" + "@react-types/combobox": "npm:^3.13.6" + "@react-types/searchfield": "npm:^3.6.3" + "@react-types/shared": "npm:^3.30.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/a585a2bf211af8996078dee3b7c179617ca889747049f22b8306a6e48c09afa3828a682a46f7be3c36cc1e2282f6bb98b5095c263c466634f1775a1c01599ecd + checksum: 10/ad617628499db45b2acb07d2f4a4b7aa0569124ee1b7c35b2f1eb9819a2d09a755ab4d3be489243c0d579fc16485aec24950ce3b132a655f83d302eb0d140f96 languageName: node linkType: hard -"@react-types/shared@npm:^3.28.0": - version: 3.28.0 - resolution: "@react-types/shared@npm:3.28.0" +"@react-types/breadcrumbs@npm:^3.7.14": + version: 3.7.14 + resolution: "@react-types/breadcrumbs@npm:3.7.14" + dependencies: + "@react-types/link": "npm:^3.6.2" + "@react-types/shared": "npm:^3.30.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/b8dcc73b950c68fdf22a1173e6e4cfc17f51c579455b3b3239cefd50ebe8bcd6a71db00f643ef8c8f6cd6a2b142780bbce243bda571f5f30be94a04ce30547cb + checksum: 10/106fa21ce01b9ee16796ba46d1133b71b466bfc069e9a90d7f00280e177e1f009791db78701565d83c22506bf72b0469756238164d8ffa36c6712f4e95932eac languageName: node linkType: hard -"@redis/bloom@npm:1.2.0": - version: 1.2.0 - resolution: "@redis/bloom@npm:1.2.0" +"@react-types/button@npm:^3.12.2": + version: 3.12.2 + resolution: "@react-types/button@npm:3.12.2" + dependencies: + "@react-types/shared": "npm:^3.30.0" peerDependencies: - "@redis/client": ^1.0.0 - checksum: 10/a16408f729ddd032a52c9d998661dfa7beabc0e92760d30619c3166c7a53a98c037956d93d230b787005fd8a599a7456461ca7429c1916893c2d13d59a41e0e6 + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/d00f7e23fd6cfa863c2d9f0f91ec47fc017666b55775842af3c001fabcf949dfa55b116674bcb841de568eacaf677c3a25e079b4810dcf857547bd9cf6d60917 languageName: node linkType: hard -"@redis/client@npm:1.6.0": - version: 1.6.0 - resolution: "@redis/client@npm:1.6.0" +"@react-types/calendar@npm:^3.7.2": + version: 3.7.2 + resolution: "@react-types/calendar@npm:3.7.2" + dependencies: + "@internationalized/date": "npm:^3.8.2" + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/40ad15a5f937844f6626028c8a6e7d36f09e946b4cbdfcd95ff4971f6681abd86260f64aa8298cf86fdde5a0ce2de17e327b8011bb435d0f3faf10fcd9c97f38 + languageName: node + linkType: hard + +"@react-types/checkbox@npm:^3.9.5": + version: 3.9.5 + resolution: "@react-types/checkbox@npm:3.9.5" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/25f6c155043f25f55379b613e3e7faa16191f34c04f13acfc75fc5bda5b83d76d3062b177aad2401f9dd1570215714d477ab1d1e3b92a33e24172aa44e9ff79e + languageName: node + linkType: hard + +"@react-types/color@npm:^3.0.6": + version: 3.0.6 + resolution: "@react-types/color@npm:3.0.6" + dependencies: + "@react-types/shared": "npm:^3.30.0" + "@react-types/slider": "npm:^3.7.12" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/33ddf5239ccfdada1ae276a69b020500dae557a0a43c4bb0fb55fcd6122331c7f2adb552ff3706753214964032ea459634708a02f147777dff948045877cc01b + languageName: node + linkType: hard + +"@react-types/combobox@npm:^3.13.6": + version: 3.13.6 + resolution: "@react-types/combobox@npm:3.13.6" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/8aaf970af426d084ba4b954aeca23cc7779de221a19465c1487d7098a5f637c13bcb6446bf695619e221d78d289cf629b94faa2c86de8bb3f949ff05a5448a13 + languageName: node + linkType: hard + +"@react-types/datepicker@npm:^3.12.2": + version: 3.12.2 + resolution: "@react-types/datepicker@npm:3.12.2" + dependencies: + "@internationalized/date": "npm:^3.8.2" + "@react-types/calendar": "npm:^3.7.2" + "@react-types/overlays": "npm:^3.8.16" + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/21d6b72986b18b25b6380bf85a7a12319de05160a1dee5d0e7484378627dee955c6cda9fbb0b6909e9edcfc82181be1d9e744220fcf82d75357d7208ed2978a9 + languageName: node + linkType: hard + +"@react-types/dialog@npm:^3.5.19": + version: 3.5.19 + resolution: "@react-types/dialog@npm:3.5.19" + dependencies: + "@react-types/overlays": "npm:^3.8.16" + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/07bd55d214d8162f1a700f1ac80cd51468d0defc0d9753309d5e58a0fffbc1eb65a588b6c0709593a41fa8d72cc1f8f8dba0a65b2a336968fdc2ff92afec3596 + languageName: node + linkType: hard + +"@react-types/form@npm:^3.7.13": + version: 3.7.13 + resolution: "@react-types/form@npm:3.7.13" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/2c55ceb9f5e52a08b60ae0ae95d1db3e57a67d3783eaee2fb37870602afd612153506c13effec98bf4c7b58728a148e9d966c8c3442db560543779b9ef8dd091 + languageName: node + linkType: hard + +"@react-types/grid@npm:^3.3.3": + version: 3.3.3 + resolution: "@react-types/grid@npm:3.3.3" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/cac232888139186110f7095cbb45ba2525522a296254f44290ad4c87ac2b1e72485d839e2820e7265cbcd6bc4cb8e49c170c52fd57defed16d06a4eb7f7cf047 + languageName: node + linkType: hard + +"@react-types/link@npm:^3.6.2": + version: 3.6.2 + resolution: "@react-types/link@npm:3.6.2" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/093a3011a98f36dd252d7675365effcba219b9a89e53619e176bdf322042520d5d24e7d9dca04bd63fa6785242f097301c3921d111c43ed888072b0938133435 + languageName: node + linkType: hard + +"@react-types/listbox@npm:^3.7.1": + version: 3.7.1 + resolution: "@react-types/listbox@npm:3.7.1" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/2040a31a6eae1a0d0fe44f2d6410dfd6e9f99849eaba4fea4b4819ed162f2642d5e023d0badb4f78b2e0d60c4480cacc2d0745f9881f77b83c20b7c2012adf0b + languageName: node + linkType: hard + +"@react-types/menu@npm:^3.10.2": + version: 3.10.2 + resolution: "@react-types/menu@npm:3.10.2" + dependencies: + "@react-types/overlays": "npm:^3.8.16" + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/9b6cc0818f1d63225f7794ca89d0fa25cd771bad4440fa52c403d81d7027b1f83ba600603ede4ee05dee01b57d1240c9eaa7ed890150aa9dff0c8113dc0c85ab + languageName: node + linkType: hard + +"@react-types/meter@npm:^3.4.10": + version: 3.4.10 + resolution: "@react-types/meter@npm:3.4.10" + dependencies: + "@react-types/progress": "npm:^3.5.13" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/b55f4ca464fc2873662ee50d25cdc1c77995d34d39b6d2831c0b121e47c7b12051017576f51909881a3776003e03263ce899dcf572a9f083794b160320baa754 + languageName: node + linkType: hard + +"@react-types/numberfield@npm:^3.8.12": + version: 3.8.12 + resolution: "@react-types/numberfield@npm:3.8.12" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/98989cdcd7b7bef45129c54e9bdb919d06f09109c786f0783105c617304ee3ce15c9aad01507d4d9aa0e974ee3b50b230c057ad7a48f634247e374c9668080ae + languageName: node + linkType: hard + +"@react-types/overlays@npm:^3.8.16": + version: 3.8.16 + resolution: "@react-types/overlays@npm:3.8.16" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/12bedf8e79d31827cd9868dcb115d07b9fee7954af5e00c1a46fe87b9f2b08e971af4b1b238faa2ce7ed173600786a83c7b9cfcdee5c7464a42bc581d7dee3a6 + languageName: node + linkType: hard + +"@react-types/progress@npm:^3.5.13": + version: 3.5.13 + resolution: "@react-types/progress@npm:3.5.13" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/f666892cff510323e90b1e72e5351eef4f38eb752e568f46cb30250a7de8b9a4435f3506fc590d748d3a0ce30a5f64d449a88a8656369137d06a584ef667d0a8 + languageName: node + linkType: hard + +"@react-types/radio@npm:^3.8.10": + version: 3.8.10 + resolution: "@react-types/radio@npm:3.8.10" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/ba87e61b7a84547c417baa9553bdfebf2fcd03d45038f0d6a0031a5d4e6c94f9d9a81fe8a98579c6830dcffad68c71560ccac8fd842d5f2469a05ce275d92250 + languageName: node + linkType: hard + +"@react-types/searchfield@npm:^3.6.3": + version: 3.6.3 + resolution: "@react-types/searchfield@npm:3.6.3" + dependencies: + "@react-types/shared": "npm:^3.30.0" + "@react-types/textfield": "npm:^3.12.3" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/ffe5a909fb94a28c674c1ae798e511d93be5175c3fc8adcadc88e040c6e5aca4cf55565b321219311537c11c5ce70c005aa214d8dc708a4b1583a1f1a68a75f6 + languageName: node + linkType: hard + +"@react-types/select@npm:^3.9.13": + version: 3.9.13 + resolution: "@react-types/select@npm:3.9.13" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/9fba560d2060eb852289be76f0e0b691bddd48f744f969862da10d38c8963f6931190cb53cc8c5ea01eab3f447b8a680e6da378fe6287ec80d3f48a62d312759 + languageName: node + linkType: hard + +"@react-types/shared@npm:^3.30.0": + version: 3.30.0 + resolution: "@react-types/shared@npm:3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/58af4134275a347bf18ac5d15e3dcfd07dcf623e1d494f140303e06f2149b7aad86408979e163393346df613f6390a3a387f77a2937f0bf1908c6672a44f7b69 + languageName: node + linkType: hard + +"@react-types/slider@npm:^3.7.12": + version: 3.7.12 + resolution: "@react-types/slider@npm:3.7.12" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/38b8a556f87bb64c7f47af482a1a3ce688345a3d4b7a2c71e99522bdcb9668f8ed8e3ea0797b32d5ac1a95d6557c5f9e8eadab51e8eb780f9e66efa89ba50fa0 + languageName: node + linkType: hard + +"@react-types/switch@npm:^3.5.12": + version: 3.5.12 + resolution: "@react-types/switch@npm:3.5.12" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/6552f113b0c3abf52f590b8cedf17f5901e7fa2d0ed94b30408fd2fbda907ee17e7792af6d86985ca742a3cb5b667bcbf6cec725e66265ccdfd201b77e7f524b + languageName: node + linkType: hard + +"@react-types/table@npm:^3.13.1": + version: 3.13.1 + resolution: "@react-types/table@npm:3.13.1" + dependencies: + "@react-types/grid": "npm:^3.3.3" + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/be1bac716d13148b47016f2156ee0769c044d301b538df934f6b8aa5466265a750391e462f9fcdebff7dbb93cf9e373e2117f91eab528b3f5cef062174e547ee + languageName: node + linkType: hard + +"@react-types/tabs@npm:^3.3.16": + version: 3.3.16 + resolution: "@react-types/tabs@npm:3.3.16" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/fae8d98776408b0e9776d0217a545a1ed6035ac976321e413e4f442b525375c56854a328c33e77564efe525e66bc7b79dc89e38ff02373788b996c4b46bb6c5d + languageName: node + linkType: hard + +"@react-types/textfield@npm:^3.12.3": + version: 3.12.3 + resolution: "@react-types/textfield@npm:3.12.3" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/d8bf58fd569b30fa13f98de834a025c9ef71d6de8c669912aa7083c83f88f7261852e0b1b30953e903d377b5b63cc849b848fd549a1466c82d6a74cb6c540cf5 + languageName: node + linkType: hard + +"@react-types/tooltip@npm:^3.4.18": + version: 3.4.18 + resolution: "@react-types/tooltip@npm:3.4.18" + dependencies: + "@react-types/overlays": "npm:^3.8.16" + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/5eed69c83727496a6d06796617da86740ab2629b3bd6a106c6ddfb384aaa74725aed3c3eed44f329dcebe7cf87a261ce33e8aa8a5899549446321c1a86d27b87 + languageName: node + linkType: hard + +"@redis/client@npm:^1.6.0": + version: 1.6.1 + resolution: "@redis/client@npm:1.6.1" dependencies: cluster-key-slot: "npm:1.1.2" generic-pool: "npm:3.9.0" yallist: "npm:4.0.0" - checksum: 10/ad375bd685dc34163304872e611ad31dc09a20f4dc8416e5dd588e8a9d2a42a6882f5c8f83d6388ba04ada71513c461b211c3146bcd8ab4da20b3f7edf08fb63 - languageName: node - linkType: hard - -"@redis/graph@npm:1.1.1": - version: 1.1.1 - resolution: "@redis/graph@npm:1.1.1" - peerDependencies: - "@redis/client": ^1.0.0 - checksum: 10/96b8ee9bec124947465848b56a014805f9639e09704e03c75a92072a319599ac9dcd4f9ace22970a7f72131a241166ad31db4dc6931b34808d22a5ca94649ba5 - languageName: node - linkType: hard - -"@redis/json@npm:1.0.7": - version: 1.0.7 - resolution: "@redis/json@npm:1.0.7" - peerDependencies: - "@redis/client": ^1.0.0 - checksum: 10/df0ac5035608d2e82289237d1adbf541af3743a86692df9958a2c89281bce024eeecfc031db51774d8a46639c5ec34ce9f8b71ebec7bce21865920e36ca3db57 - languageName: node - linkType: hard - -"@redis/search@npm:1.2.0": - version: 1.2.0 - resolution: "@redis/search@npm:1.2.0" - peerDependencies: - "@redis/client": ^1.0.0 - checksum: 10/25bba222c0fb1ec1f2db08fe157d30d56a8ffe234214c72d3a7a991daefe77f18c5e6440ab3aa297aef88bafd5448b04ac9fcd84671f2dadc8989712c06b63b4 - languageName: node - linkType: hard - -"@redis/time-series@npm:1.1.0": - version: 1.1.0 - resolution: "@redis/time-series@npm:1.1.0" - peerDependencies: - "@redis/client": ^1.0.0 - checksum: 10/e1d000eef7f37645f7f4ff94f32dd998384360b12fed1e4d614d828c065b72d7627e27444fab781fac4fdfe301c45f893417550fb1011d3b75237566ff0954e0 + checksum: 10/3ef20235b9b0ecba728bbc7208eabbdfc2eebb50c6fb95b20486a0c14e9d6f3ce620ac0d3d14d7f682ea7cb953b13bf89bd94932b7ab3babeb12ba77136b4291 languageName: node linkType: hard @@ -16393,234 +16429,248 @@ __metadata: languageName: node linkType: hard -"@rollup/pluginutils@npm:^4.2.1": - version: 4.2.1 - resolution: "@rollup/pluginutils@npm:4.2.1" - dependencies: - estree-walker: "npm:^2.0.1" - picomatch: "npm:^2.2.2" - checksum: 10/503a6f0a449e11a2873ac66cfdfb9a3a0b77ffa84c5cad631f5e4bc1063c850710e8d5cd5dab52477c0d66cda2ec719865726dbe753318cd640bab3fff7ca476 - languageName: node - linkType: hard - -"@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.0.5, @rollup/pluginutils@npm:^5.1.0": - version: 5.1.0 - resolution: "@rollup/pluginutils@npm:5.1.0" +"@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.0.2, @rollup/pluginutils@npm:^5.0.5, @rollup/pluginutils@npm:^5.1.0": + version: 5.2.0 + resolution: "@rollup/pluginutils@npm:5.2.0" dependencies: "@types/estree": "npm:^1.0.0" estree-walker: "npm:^2.0.2" - picomatch: "npm:^2.3.1" + picomatch: "npm:^4.0.2" peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true - checksum: 10/abb15eaec5b36f159ec351b48578401bedcefdfa371d24a914cfdbb1e27d0ebfbf895299ec18ccc343d247e71f2502cba21202bc1362d7ef27d5ded699e5c2b2 + checksum: 10/15e98a9e7ebeb9fdbbf072ad40e72947654abf98bcd389d6e54dcffe28f7eb93d9653037d5e18b703b0160e04210a1995cf08fc2bf45601ce77b17e4461f55c0 languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.27.4" +"@rollup/rollup-android-arm-eabi@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.46.2" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-android-arm64@npm:4.27.4" +"@rollup/rollup-android-arm64@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-android-arm64@npm:4.46.2" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-darwin-arm64@npm:4.27.4" +"@rollup/rollup-darwin-arm64@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-darwin-arm64@npm:4.46.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-darwin-x64@npm:4.27.4" +"@rollup/rollup-darwin-x64@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-darwin-x64@npm:4.46.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.27.4" +"@rollup/rollup-freebsd-arm64@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.46.2" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-freebsd-x64@npm:4.27.4" +"@rollup/rollup-freebsd-x64@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-freebsd-x64@npm:4.46.2" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.27.4" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.46.2" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.27.4" +"@rollup/rollup-linux-arm-musleabihf@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.46.2" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.27.4" +"@rollup/rollup-linux-arm64-gnu@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.46.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.27.4" +"@rollup/rollup-linux-arm64-musl@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.46.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-powerpc64le-gnu@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.27.4" +"@rollup/rollup-linux-loongarch64-gnu@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.46.2" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.46.2" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.27.4" +"@rollup/rollup-linux-riscv64-gnu@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.46.2" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.27.4" +"@rollup/rollup-linux-riscv64-musl@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.46.2" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.46.2" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.27.4" +"@rollup/rollup-linux-x64-gnu@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.46.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.27.4" +"@rollup/rollup-linux-x64-musl@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.46.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.27.4" +"@rollup/rollup-win32-arm64-msvc@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.46.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.27.4" +"@rollup/rollup-win32-ia32-msvc@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.46.2" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.27.4": - version: 4.27.4 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.27.4" +"@rollup/rollup-win32-x64-msvc@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.46.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rspack/binding-darwin-arm64@npm:1.3.8": - version: 1.3.8 - resolution: "@rspack/binding-darwin-arm64@npm:1.3.8" +"@rspack/binding-darwin-arm64@npm:1.4.11": + version: 1.4.11 + resolution: "@rspack/binding-darwin-arm64@npm:1.4.11" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rspack/binding-darwin-x64@npm:1.3.8": - version: 1.3.8 - resolution: "@rspack/binding-darwin-x64@npm:1.3.8" +"@rspack/binding-darwin-x64@npm:1.4.11": + version: 1.4.11 + resolution: "@rspack/binding-darwin-x64@npm:1.4.11" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rspack/binding-linux-arm64-gnu@npm:1.3.8": - version: 1.3.8 - resolution: "@rspack/binding-linux-arm64-gnu@npm:1.3.8" +"@rspack/binding-linux-arm64-gnu@npm:1.4.11": + version: 1.4.11 + resolution: "@rspack/binding-linux-arm64-gnu@npm:1.4.11" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rspack/binding-linux-arm64-musl@npm:1.3.8": - version: 1.3.8 - resolution: "@rspack/binding-linux-arm64-musl@npm:1.3.8" +"@rspack/binding-linux-arm64-musl@npm:1.4.11": + version: 1.4.11 + resolution: "@rspack/binding-linux-arm64-musl@npm:1.4.11" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rspack/binding-linux-x64-gnu@npm:1.3.8": - version: 1.3.8 - resolution: "@rspack/binding-linux-x64-gnu@npm:1.3.8" +"@rspack/binding-linux-x64-gnu@npm:1.4.11": + version: 1.4.11 + resolution: "@rspack/binding-linux-x64-gnu@npm:1.4.11" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rspack/binding-linux-x64-musl@npm:1.3.8": - version: 1.3.8 - resolution: "@rspack/binding-linux-x64-musl@npm:1.3.8" +"@rspack/binding-linux-x64-musl@npm:1.4.11": + version: 1.4.11 + resolution: "@rspack/binding-linux-x64-musl@npm:1.4.11" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rspack/binding-win32-arm64-msvc@npm:1.3.8": - version: 1.3.8 - resolution: "@rspack/binding-win32-arm64-msvc@npm:1.3.8" +"@rspack/binding-wasm32-wasi@npm:1.4.11": + version: 1.4.11 + resolution: "@rspack/binding-wasm32-wasi@npm:1.4.11" + dependencies: + "@napi-rs/wasm-runtime": "npm:^1.0.1" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@rspack/binding-win32-arm64-msvc@npm:1.4.11": + version: 1.4.11 + resolution: "@rspack/binding-win32-arm64-msvc@npm:1.4.11" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rspack/binding-win32-ia32-msvc@npm:1.3.8": - version: 1.3.8 - resolution: "@rspack/binding-win32-ia32-msvc@npm:1.3.8" +"@rspack/binding-win32-ia32-msvc@npm:1.4.11": + version: 1.4.11 + resolution: "@rspack/binding-win32-ia32-msvc@npm:1.4.11" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rspack/binding-win32-x64-msvc@npm:1.3.8": - version: 1.3.8 - resolution: "@rspack/binding-win32-x64-msvc@npm:1.3.8" +"@rspack/binding-win32-x64-msvc@npm:1.4.11": + version: 1.4.11 + resolution: "@rspack/binding-win32-x64-msvc@npm:1.4.11" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rspack/binding@npm:1.3.8": - version: 1.3.8 - resolution: "@rspack/binding@npm:1.3.8" +"@rspack/binding@npm:1.4.11": + version: 1.4.11 + resolution: "@rspack/binding@npm:1.4.11" dependencies: - "@rspack/binding-darwin-arm64": "npm:1.3.8" - "@rspack/binding-darwin-x64": "npm:1.3.8" - "@rspack/binding-linux-arm64-gnu": "npm:1.3.8" - "@rspack/binding-linux-arm64-musl": "npm:1.3.8" - "@rspack/binding-linux-x64-gnu": "npm:1.3.8" - "@rspack/binding-linux-x64-musl": "npm:1.3.8" - "@rspack/binding-win32-arm64-msvc": "npm:1.3.8" - "@rspack/binding-win32-ia32-msvc": "npm:1.3.8" - "@rspack/binding-win32-x64-msvc": "npm:1.3.8" + "@rspack/binding-darwin-arm64": "npm:1.4.11" + "@rspack/binding-darwin-x64": "npm:1.4.11" + "@rspack/binding-linux-arm64-gnu": "npm:1.4.11" + "@rspack/binding-linux-arm64-musl": "npm:1.4.11" + "@rspack/binding-linux-x64-gnu": "npm:1.4.11" + "@rspack/binding-linux-x64-musl": "npm:1.4.11" + "@rspack/binding-wasm32-wasi": "npm:1.4.11" + "@rspack/binding-win32-arm64-msvc": "npm:1.4.11" + "@rspack/binding-win32-ia32-msvc": "npm:1.4.11" + "@rspack/binding-win32-x64-msvc": "npm:1.4.11" dependenciesMeta: "@rspack/binding-darwin-arm64": optional: true @@ -16634,61 +16684,59 @@ __metadata: optional: true "@rspack/binding-linux-x64-musl": optional: true + "@rspack/binding-wasm32-wasi": + optional: true "@rspack/binding-win32-arm64-msvc": optional: true "@rspack/binding-win32-ia32-msvc": optional: true "@rspack/binding-win32-x64-msvc": optional: true - checksum: 10/2b4427240e97899845b08f592af4f806884b49e192fb2b5a93f450f11c97a3ecbef581bb32b9cf1b2bfca48fd65426a978011f04fc7484307e84005ffd174211 + checksum: 10/8bb94774204f41888ff442afec06f019d008abba79964b74d566acf64f7216a148a1842f90c44b3bf680e69b697d8e5cd0f1cca6fd0b8a94df5f97c2a3f05510 languageName: node linkType: hard -"@rspack/core@npm:^1.0.10": - version: 1.3.8 - resolution: "@rspack/core@npm:1.3.8" +"@rspack/core@npm:^1.4.11": + version: 1.4.11 + resolution: "@rspack/core@npm:1.4.11" dependencies: - "@module-federation/runtime-tools": "npm:0.13.0" - "@rspack/binding": "npm:1.3.8" + "@module-federation/runtime-tools": "npm:0.17.1" + "@rspack/binding": "npm:1.4.11" "@rspack/lite-tapable": "npm:1.0.1" - caniuse-lite: "npm:^1.0.30001715" peerDependencies: "@swc/helpers": ">=0.5.1" peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/e2f3fd21d512faa3865a784f219f92b78497713e0e9dd2fd03c4ec6c9bcfa33c53c301f9d5c5546dd7e86e8e46309b0aef3ab6f1661fed10c622482b8717b588 + checksum: 10/77d463bd90feb2d24f7bc56df198f0b7ad310a9eb676070eac8d78014d151e783943c5b44c64700a51a36708c626a341eeaa9b3287e358616d09dfe25ab04e77 languageName: node linkType: hard -"@rspack/dev-server@npm:^1.0.9": - version: 1.1.1 - resolution: "@rspack/dev-server@npm:1.1.1" +"@rspack/dev-server@npm:^1.1.4": + version: 1.1.4 + resolution: "@rspack/dev-server@npm:1.1.4" dependencies: chokidar: "npm:^3.6.0" - express: "npm:^4.21.2" - http-proxy-middleware: "npm:^2.0.7" - mime-types: "npm:^2.1.35" + http-proxy-middleware: "npm:^2.0.9" p-retry: "npm:^6.2.0" - webpack-dev-middleware: "npm:^7.4.2" - webpack-dev-server: "npm:5.2.0" + webpack-dev-server: "npm:5.2.2" ws: "npm:^8.18.0" peerDependencies: "@rspack/core": "*" - checksum: 10/a712c1ed6819da26984d106e7fdede3ed720ef177bf8fa976e91eb95988b862b306c354829486dce8eabde420cb5a96cb25ecf397c9532f87f3b913e9a9da40c + checksum: 10/9409af5b9cd6f3de70caea7f82595a5e99e39ec203ef93fc9973fefdcacf6b7d264d07e927a2331d73d7d606f211865d82386bdde410f6be506ff91a1af89d99 languageName: node linkType: hard -"@rspack/lite-tapable@npm:1.0.1": +"@rspack/lite-tapable@npm:1.0.1, @rspack/lite-tapable@npm:^1.0.1": version: 1.0.1 resolution: "@rspack/lite-tapable@npm:1.0.1" checksum: 10/240b7832965bca5a52d1f03a8539dab5810958ce24b5a670405b2505d81350f10d668f4055648f5918bc18ac033e637bcb7f92189345f0f2f671b546019c2f9e languageName: node linkType: hard -"@rspack/plugin-react-refresh@npm:^1.0.0": - version: 1.4.1 - resolution: "@rspack/plugin-react-refresh@npm:1.4.1" +"@rspack/plugin-react-refresh@npm:^1.4.3": + version: 1.4.3 + resolution: "@rspack/plugin-react-refresh@npm:1.4.3" dependencies: error-stack-parser: "npm:^2.1.4" html-entities: "npm:^2.6.0" @@ -16698,7 +16746,7 @@ __metadata: peerDependenciesMeta: webpack-hot-middleware: optional: true - checksum: 10/0d6ee27a6577947399b7d457b54dbc65dc2c1199cacd257baf9b16ecd4621b288ae4c73a47e753ee8ff62a51b11fe2cac4ac0d168543299593cb59fa35c38119 + checksum: 10/ddbe4268f0c5eb1e6c4384db16cad42628596ff7ca97707b5075be894f60bb87de2483fe2735129dc624380e46b7999735af7c418123a6bb2d01280ca5fea2c1 languageName: node linkType: hard @@ -16895,30 +16943,48 @@ __metadata: languageName: node linkType: hard -"@shikijs/engine-oniguruma@npm:^1.27.2": - version: 1.29.2 - resolution: "@shikijs/engine-oniguruma@npm:1.29.2" +"@shikijs/engine-oniguruma@npm:^3.9.1": + version: 3.9.2 + resolution: "@shikijs/engine-oniguruma@npm:3.9.2" dependencies: - "@shikijs/types": "npm:1.29.2" - "@shikijs/vscode-textmate": "npm:^10.0.1" - checksum: 10/bb3e2c01da84d573251ebc289b1ecf815261024dea5bddb93ad56c3504a71cde3630db070be401ed3bbcd23a8a839ec78984a82317f9c9d0bba58daed935b781 + "@shikijs/types": "npm:3.9.2" + "@shikijs/vscode-textmate": "npm:^10.0.2" + checksum: 10/1ffb3501df871aea7fcafd9ed1cbe49e8897d479f90448fba892fd00aee7b2bd2a0afedabb0cb48e6038be91c7582d6aab98b95873f2fd38176be66c3160271b languageName: node linkType: hard -"@shikijs/types@npm:1.29.2, @shikijs/types@npm:^1.27.2": - version: 1.29.2 - resolution: "@shikijs/types@npm:1.29.2" +"@shikijs/langs@npm:^3.9.1": + version: 3.9.2 + resolution: "@shikijs/langs@npm:3.9.2" dependencies: - "@shikijs/vscode-textmate": "npm:^10.0.1" + "@shikijs/types": "npm:3.9.2" + checksum: 10/eef3380d6296179280bebd742b586a7388cc120388e4dcff61dc93e71ee5dc50bc9219e7b579744f79f1132de0f141706fc8e5f3772c13a4ef62a3db2eb31744 + languageName: node + linkType: hard + +"@shikijs/themes@npm:^3.9.1": + version: 3.9.2 + resolution: "@shikijs/themes@npm:3.9.2" + dependencies: + "@shikijs/types": "npm:3.9.2" + checksum: 10/29836bdbc75560772472597eb173f0fbb07a7dbd161f854b89adcd6ed3a9aab46a829cb3919d4b519a2604dde1e0021a538917995e0d3f1efb608ce812f1338f + languageName: node + linkType: hard + +"@shikijs/types@npm:3.9.2, @shikijs/types@npm:^3.9.1": + version: 3.9.2 + resolution: "@shikijs/types@npm:3.9.2" + dependencies: + "@shikijs/vscode-textmate": "npm:^10.0.2" "@types/hast": "npm:^3.0.4" - checksum: 10/579e64b6e8cb83023232b8060b08f51cff3909b199d0d1a0c58ed500c898dd34b74bf0457336fa2e6bee1005889e198d7d924347ad616eee30c6ae4c89a67ab8 + checksum: 10/d21020066f12c618be759cdeb01984d8236bfa3aec96343e2886040748aaf3c7fe07a2cbeebe125afed3d57136dcae35eb28f2ba6985f0b6327cbc90ba387472 languageName: node linkType: hard -"@shikijs/vscode-textmate@npm:^10.0.1": - version: 10.0.1 - resolution: "@shikijs/vscode-textmate@npm:10.0.1" - checksum: 10/8bb005efbf472a9cac19bb5fb0bb1a2b612e128122e5a9d7b2bb2af674c29912db0e59c547ec458e5333f8f7ac7a8f054a24cb653f11dc20951e299933b416e4 +"@shikijs/vscode-textmate@npm:^10.0.2": + version: 10.0.2 + resolution: "@shikijs/vscode-textmate@npm:10.0.2" + checksum: 10/d924cba8a01cd9ca12f56ba097d628fdb81455abb85884c8d8a5ae85b628a37dd5907e7691019b97107bd6608c866adf91ba04a1c3bba391281c88e386c044ea languageName: node linkType: hard @@ -17185,14 +17251,14 @@ __metadata: linkType: hard "@slack/web-api@npm:^7.5.0": - version: 7.8.0 - resolution: "@slack/web-api@npm:7.8.0" + version: 7.9.3 + resolution: "@slack/web-api@npm:7.9.3" dependencies: "@slack/logger": "npm:^4.0.0" "@slack/types": "npm:^2.9.0" "@types/node": "npm:>=18.0.0" "@types/retry": "npm:0.12.0" - axios: "npm:^1.7.8" + axios: "npm:^1.8.3" eventemitter3: "npm:^5.0.1" form-data: "npm:^4.0.0" is-electron: "npm:2.2.2" @@ -17200,7 +17266,7 @@ __metadata: p-queue: "npm:^6" p-retry: "npm:^4" retry: "npm:^0.13.1" - checksum: 10/f2a698f853d0aaab11a23e9ce659e2cf3e43792eeb6861ef05258bbd2bb5cfe3bad8fc7bff4ee6fe471e67ec9fb6b22ef16be4478ab68f139df730337ebe15bb + checksum: 10/dc8aa95c8127a1155f0c0756bc819185da0b471feb5953dc118301e577e67f45ad4ef64d9459ff430b44f769344a6347d1636ad59267483e9ac0104611263a05 languageName: node linkType: hard @@ -18007,8 +18073,8 @@ __metadata: linkType: hard "@stoplight/spectral-core@npm:^1.18.0, @stoplight/spectral-core@npm:^1.18.3, @stoplight/spectral-core@npm:^1.19.2, @stoplight/spectral-core@npm:^1.19.4, @stoplight/spectral-core@npm:^1.8.1": - version: 1.19.5 - resolution: "@stoplight/spectral-core@npm:1.19.5" + version: 1.20.0 + resolution: "@stoplight/spectral-core@npm:1.20.0" dependencies: "@stoplight/better-ajv-errors": "npm:1.0.3" "@stoplight/json": "npm:~3.21.0" @@ -18031,7 +18097,7 @@ __metadata: pony-cause: "npm:^1.1.1" simple-eval: "npm:1.0.1" tslib: "npm:^2.8.1" - checksum: 10/db0f6c1c0ca67bc4a49c480259d261283ccb72958cd3a2a761dccbbbb1fdd04911df9c0a05f7ce4453e303de737206fbf3e799a1ace5db49d433a2848abe13b2 + checksum: 10/63628dcf4b1556db166a020dd78d17033908fa7d1c67c62739e35848e5d0e095714524b4c142aa7d6524fe556152f4dbb8cb388b36bc6e37026bf07c31a0e69b languageName: node linkType: hard @@ -18048,8 +18114,8 @@ __metadata: linkType: hard "@stoplight/spectral-formatters@npm:^1.1.0": - version: 1.4.3 - resolution: "@stoplight/spectral-formatters@npm:1.4.3" + version: 1.5.0 + resolution: "@stoplight/spectral-formatters@npm:1.5.0" dependencies: "@stoplight/path": "npm:^1.3.2" "@stoplight/spectral-core": "npm:^1.19.4" @@ -18064,13 +18130,13 @@ __metadata: strip-ansi: "npm:6.0" text-table: "npm:^0.2.0" tslib: "npm:^2.8.1" - checksum: 10/38cdb6dd756a5b859f0768bd7a6730d9ff816a8e21880385e75d0b663a98a2ca62283b6ab50bcce92384e0117d354813591faec9d5da85a3d6ed491afd5b1830 + checksum: 10/565f939d4363d6de357cb6292fce3f656ea106097b675562a958a2fb0b351f2ebc8516602ebdad83f57a73d6da178ce3e18e8a484b565d5c46e24aa588e7f04b languageName: node linkType: hard "@stoplight/spectral-functions@npm:^1.6.1, @stoplight/spectral-functions@npm:^1.7.2, @stoplight/spectral-functions@npm:^1.9.1": - version: 1.9.4 - resolution: "@stoplight/spectral-functions@npm:1.9.4" + version: 1.10.1 + resolution: "@stoplight/spectral-functions@npm:1.10.1" dependencies: "@stoplight/better-ajv-errors": "npm:1.0.3" "@stoplight/json": "npm:^3.17.1" @@ -18083,7 +18149,7 @@ __metadata: ajv-formats: "npm:~2.1.1" lodash: "npm:~4.17.21" tslib: "npm:^2.8.1" - checksum: 10/8da84358ccfcf60afc2f61db3296eef5718e9f837cfaec99a006faf42f5e1f7a6f47fbbe02161e39ffe6a5fe9dc5cacf1270ff59b22a8ba6264d6381d6a94d65 + checksum: 10/dceb3aa64ef5342183a1175ccf6d1a81e6c44dd0dd128a2b0b00f7d8b0eb467a47faac96b2a3a42554e99116af0e5a1d8be3d51ae82c3a96fc683d53fb0127ca languageName: node linkType: hard @@ -18113,8 +18179,8 @@ __metadata: linkType: hard "@stoplight/spectral-rulesets@npm:^1.14.1, @stoplight/spectral-rulesets@npm:^1.18.0": - version: 1.21.4 - resolution: "@stoplight/spectral-rulesets@npm:1.21.4" + version: 1.22.0 + resolution: "@stoplight/spectral-rulesets@npm:1.22.0" dependencies: "@asyncapi/specs": "npm:^6.8.0" "@stoplight/better-ajv-errors": "npm:1.0.3" @@ -18131,7 +18197,7 @@ __metadata: leven: "npm:3.1.0" lodash: "npm:~4.17.21" tslib: "npm:^2.8.1" - checksum: 10/f5154e70285e3e7caac24a33fec0904d342923402f4bc2fb0a4d184d1e9f8a70d555d7ed9118dda004b8fd5ad51357e57abc68b657315934a03beb0e55d842d5 + checksum: 10/1e93a302436b62532324dadb5c0cd1f89606533a8fefb1437288b6dec6c28c350d41723718b978c0c4c806cffe9019a2b8b0df5f71f78349a23f98a2f847c036 languageName: node linkType: hard @@ -18199,9 +18265,9 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-actions@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-actions@npm:8.6.12" +"@storybook/addon-actions@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/addon-actions@npm:8.6.14" dependencies: "@storybook/global": "npm:^5.0.0" "@types/uuid": "npm:^9.0.1" @@ -18209,257 +18275,231 @@ __metadata: polished: "npm:^4.2.2" uuid: "npm:^9.0.0" peerDependencies: - storybook: ^8.6.12 - checksum: 10/c5eaaf5274bfe382877720ba9c379c0d7f6ec6173addb8bb94e80ba7318363359c677b57f25ca7582dd0007b4564683d627c51f69466e7a54faffdccae19f31f + storybook: ^8.6.14 + checksum: 10/5d96c1519ffb78ce663612e8ad850523fefe6f8aae3add662ab461015dfdd4e64c62720655526d471893018efdc5049807c3ec49998fb51dfab6cfb5aed30b73 languageName: node linkType: hard -"@storybook/addon-backgrounds@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-backgrounds@npm:8.6.12" +"@storybook/addon-backgrounds@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/addon-backgrounds@npm:8.6.14" dependencies: "@storybook/global": "npm:^5.0.0" memoizerific: "npm:^1.11.3" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.6.12 - checksum: 10/cb4793843140f6b454cb11bf9ef65f9d68ac001544538744b2a0564c30d6b82a144f788ede3d8e86ab23951e4b1fa157e4b78b74f8ab1cc461c8e696532eb8a6 + storybook: ^8.6.14 + checksum: 10/67343f7b2567b1831db44f3fe6872c9d72261da20eb43390a020ad4ec4420b5e44f1e8c60594bf39ddd0d290a6d05319874167692bd2676615a4f3902b7c897e languageName: node linkType: hard -"@storybook/addon-controls@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-controls@npm:8.6.12" +"@storybook/addon-controls@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/addon-controls@npm:8.6.14" dependencies: "@storybook/global": "npm:^5.0.0" dequal: "npm:^2.0.2" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.6.12 - checksum: 10/2de79406c572f8706a7a31871f7c7bc6498cde48bcbcbc00984277e6861defcad9ba895d90a66ed0ffc526bcf3b950561ff6d51837192625fb7cb5a9bb763ce4 + storybook: ^8.6.14 + checksum: 10/2b63d6fb516886eec1342e18191dfc61f32203313be75bce547f30d69f8bc5084e002aae16275a0c355ffe49d476fe48497738f7b8c50272b1aac0e823b415c6 languageName: node linkType: hard -"@storybook/addon-docs@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-docs@npm:8.6.12" +"@storybook/addon-docs@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/addon-docs@npm:8.6.14" dependencies: "@mdx-js/react": "npm:^3.0.0" - "@storybook/blocks": "npm:8.6.12" - "@storybook/csf-plugin": "npm:8.6.12" - "@storybook/react-dom-shim": "npm:8.6.12" + "@storybook/blocks": "npm:8.6.14" + "@storybook/csf-plugin": "npm:8.6.14" + "@storybook/react-dom-shim": "npm:8.6.14" react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.6.12 - checksum: 10/df52e5c77a3e2b380864fdc5a6fcd0726e309f8c0556b9981cfe0899ea778531f286663be4f7988cd41ae0a813cb2de57f9fd09281ee0fadd8977354125b7f17 + storybook: ^8.6.14 + checksum: 10/a3d08b6418c93776091bd1619f0c821abd5dc620973f0f52025cc342c5d38377455ccd69a1df3f68f7e4e82dc57e039e627a1f4b6a4e856c4eef68a33fe0cbb4 languageName: node linkType: hard "@storybook/addon-essentials@npm:^8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-essentials@npm:8.6.12" + version: 8.6.14 + resolution: "@storybook/addon-essentials@npm:8.6.14" dependencies: - "@storybook/addon-actions": "npm:8.6.12" - "@storybook/addon-backgrounds": "npm:8.6.12" - "@storybook/addon-controls": "npm:8.6.12" - "@storybook/addon-docs": "npm:8.6.12" - "@storybook/addon-highlight": "npm:8.6.12" - "@storybook/addon-measure": "npm:8.6.12" - "@storybook/addon-outline": "npm:8.6.12" - "@storybook/addon-toolbars": "npm:8.6.12" - "@storybook/addon-viewport": "npm:8.6.12" + "@storybook/addon-actions": "npm:8.6.14" + "@storybook/addon-backgrounds": "npm:8.6.14" + "@storybook/addon-controls": "npm:8.6.14" + "@storybook/addon-docs": "npm:8.6.14" + "@storybook/addon-highlight": "npm:8.6.14" + "@storybook/addon-measure": "npm:8.6.14" + "@storybook/addon-outline": "npm:8.6.14" + "@storybook/addon-toolbars": "npm:8.6.14" + "@storybook/addon-viewport": "npm:8.6.14" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.6.12 - checksum: 10/88cc2f1687186a5b4b0d509f65610d0ae89318740d4186020bb8879d5837ecdb5be75fed6cecf161614180eb91c75e4411f9c902f25c5452230121ecce00eae1 + storybook: ^8.6.14 + checksum: 10/d32f2bad60736a277f5deef02adde28ec3aa188322760fe6ff4a0d726d1a9286e27e5442f3e7c55580514b0b64b08f3cd37eb12516cb7f035bafd4e26675e3bc languageName: node linkType: hard -"@storybook/addon-highlight@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-highlight@npm:8.6.12" +"@storybook/addon-highlight@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/addon-highlight@npm:8.6.14" dependencies: "@storybook/global": "npm:^5.0.0" peerDependencies: - storybook: ^8.6.12 - checksum: 10/04bdb057ed40e36af2b4a73a6220aa8c95f5322bb00c62f293118b174885476c8fb23019cfba53b9ffcfc28ff77ecbf38164c3a0fc6a717507f91d80e9b83655 + storybook: ^8.6.14 + checksum: 10/bc3f9770c525ace543bd2b1967f11e460c11ff6379c488908d57fd0593cac66171cd5643809cca878559af2db51bb7b7c8556cfa847363b177c40d4e8435629e languageName: node linkType: hard "@storybook/addon-interactions@npm:^8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-interactions@npm:8.6.12" + version: 8.6.14 + resolution: "@storybook/addon-interactions@npm:8.6.14" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/instrumenter": "npm:8.6.12" - "@storybook/test": "npm:8.6.12" + "@storybook/instrumenter": "npm:8.6.14" + "@storybook/test": "npm:8.6.14" polished: "npm:^4.2.2" ts-dedent: "npm:^2.2.0" peerDependencies: - storybook: ^8.6.12 - checksum: 10/eb467f2976d670cf3e35d30d665366e1860e750c6d57c9418781418a1138c8ed3124f534f8fab0b358998957daca27d0c924e9dc61500646b2ff0ed04c7855b4 + storybook: ^8.6.14 + checksum: 10/e23724e61be80eae962c90ea60356b9a969f84ecf509a58e2a9bd8845a1fc284488b90d2376f514abc42de947b41231a5a3b470036e01e36d89346b18338fa81 languageName: node linkType: hard -"@storybook/addon-measure@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-measure@npm:8.6.12" +"@storybook/addon-links@npm:^8.6.12": + version: 8.6.14 + resolution: "@storybook/addon-links@npm:8.6.14" + dependencies: + "@storybook/global": "npm:^5.0.0" + ts-dedent: "npm:^2.0.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + storybook: ^8.6.14 + peerDependenciesMeta: + react: + optional: true + checksum: 10/d807623e9578f8793a71dc9a3d6988fc22784e4bfebfdd3dc1293cece3e6020caf711cf8fe5171b5ef4340856b50abcddb3a4b89adba0d16582b5f910dbdf884 + languageName: node + linkType: hard + +"@storybook/addon-measure@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/addon-measure@npm:8.6.14" dependencies: "@storybook/global": "npm:^5.0.0" tiny-invariant: "npm:^1.3.1" peerDependencies: - storybook: ^8.6.12 - checksum: 10/ea4eced8d28d4cf9a7a5d3184d78030efcc31dd2ecab9096e2b17e5e690a711067fc3488677e2dde33405406c7d659aef542cb9a6dd6310971481c9119b24b1a + storybook: ^8.6.14 + checksum: 10/13fba65df34858fab4976d9dde00ada07a6a80825ec3dc0cf43ccc1fdfbb64d2df4d188a61478b4f648609ddb63de0152ec93666c737bca37dfbb5823b8051fc languageName: node linkType: hard -"@storybook/addon-outline@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-outline@npm:8.6.12" +"@storybook/addon-outline@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/addon-outline@npm:8.6.14" dependencies: "@storybook/global": "npm:^5.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.6.12 - checksum: 10/57fcde63feae3c0b755afa8036ab832d8f414acaffa7c6e7ee0039caf8b5074e53182e8c61802071f5d858e9c64d4e02ed5680a5a28c34f824f118f65a0c7607 + storybook: ^8.6.14 + checksum: 10/8d0351f86cb974fd90cf26041aebbb3683b9ed9b2088d4a3e72939921dde4f81825a3454210b537733ee615c2f113a37f153a510ab8abdd465ab0b97e70abfd0 languageName: node linkType: hard -"@storybook/addon-styling-webpack@npm:^1.0.1": - version: 1.0.1 - resolution: "@storybook/addon-styling-webpack@npm:1.0.1" +"@storybook/addon-storysource@npm:^8.6.12": + version: 8.6.14 + resolution: "@storybook/addon-storysource@npm:8.6.14" dependencies: - "@storybook/node-logger": "npm:^8.0.0-alpha.10" + "@storybook/source-loader": "npm:8.6.14" + estraverse: "npm:^5.2.0" + tiny-invariant: "npm:^1.3.1" peerDependencies: - webpack: ^5.0.0 - checksum: 10/449682f8897cc7c7a8b0098d8c6399455f2c87c625cc2e2bd5a6cf3b747a0c16c4e09d90d0cab6acf8a5a022e7e223b915c02aad988a990ae0357144bf09a50b + storybook: ^8.6.14 + checksum: 10/46ade211014668fb028f810d4d82fe1ba1ebed7bff89e5e5a96fc189be7d5406ae5598da07b358a77551809b4659a951b025daf732a20f781c4dbfbf487c5716 languageName: node linkType: hard "@storybook/addon-themes@npm:^8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-themes@npm:8.6.12" + version: 8.6.14 + resolution: "@storybook/addon-themes@npm:8.6.14" dependencies: ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.6.12 - checksum: 10/2df945accd7c0546785f71e2adc4ed68d71a057bc54aaef53f16ce8fcbc5a494380d60f9dd58520fe7d93b021a1b09e3d73553766d4bf2a1d07a3d731132b939 + storybook: ^8.6.14 + checksum: 10/b2f5e813bf95afc9b3c050cd4e06f620392a8cdb2102c60fae26ec6aa61bc11e1a16c5a2ce2bf0eaf6a608411c60c601df65958f0b9ff7af0131e9d5b83d0948 languageName: node linkType: hard -"@storybook/addon-toolbars@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-toolbars@npm:8.6.12" +"@storybook/addon-toolbars@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/addon-toolbars@npm:8.6.14" peerDependencies: - storybook: ^8.6.12 - checksum: 10/f94c3bcc8886ead315eb919cee18fb2143134ca9e59058e1d7a35c0e348e0bdcf51971b2a51ebd7a7250ec0e5873519dea72e699f83d771288b6bde873720e1f + storybook: ^8.6.14 + checksum: 10/890ea37b9270580ea531209d58a3552b87810364ed16f8a6898e339d00a9af6da755421aad2ae6e306ba0272f9cebe2e3d666db6135f7aa3108b2aa3c13bd351 languageName: node linkType: hard -"@storybook/addon-viewport@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/addon-viewport@npm:8.6.12" +"@storybook/addon-viewport@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/addon-viewport@npm:8.6.14" dependencies: memoizerific: "npm:^1.11.3" peerDependencies: - storybook: ^8.6.12 - checksum: 10/b7b9fe1bc9d51b33b67ba35f5a219910db88c99323baad6e7e5ee9ec6ddd5b5f2f4124ff99729571c4306f35fcb36ee388bece437c648e9f2ce94cbfb44c4800 + storybook: ^8.6.14 + checksum: 10/4b3d1ead7fa827de9c97fea41ae4926ab27980dac5651c209a28d8b9fad69799f230e3fafc38b98a1b6d95c0e6b51d5ca00b413da2d7f43eed23fd3c9c794e4e languageName: node linkType: hard -"@storybook/addon-webpack5-compiler-swc@npm:^3.0.0": - version: 3.0.0 - resolution: "@storybook/addon-webpack5-compiler-swc@npm:3.0.0" - dependencies: - "@swc/core": "npm:^1.10.8" - swc-loader: "npm:^0.2.6" - checksum: 10/c2b0c26d8bf54875297c82d2159aeb62846048dc71d8e953a6d6301d8a5aedc4eb979cc75877b24a2f7e1667a5baa7116286c1da29bff4a1510d2b6ec657392c - languageName: node - linkType: hard - -"@storybook/blocks@npm:8.6.12, @storybook/blocks@npm:^8.6.12": - version: 8.6.12 - resolution: "@storybook/blocks@npm:8.6.12" +"@storybook/blocks@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/blocks@npm:8.6.14" dependencies: "@storybook/icons": "npm:^1.2.12" ts-dedent: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^8.6.12 + storybook: ^8.6.14 peerDependenciesMeta: react: optional: true react-dom: optional: true - checksum: 10/636172d0512f85913f1bed23eafcb3d827e4c924f65377c0c5d60ccce0241056caf9ab1fa3eb718cae9c02fb063f446294e32d1d5163377774d9225d97f210a0 + checksum: 10/9bf85f9dd40d8eeb0f012d13a7c88d3d828b680f47c86e8472b76edda3127a87e9dbbf4e08cd8478a855558e5e58204479a08102a97ddf7b834850e760ba3f1c languageName: node linkType: hard -"@storybook/builder-webpack5@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/builder-webpack5@npm:8.6.12" +"@storybook/builder-vite@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/builder-vite@npm:8.6.14" dependencies: - "@storybook/core-webpack": "npm:8.6.12" - "@types/semver": "npm:^7.3.4" + "@storybook/csf-plugin": "npm:8.6.14" browser-assert: "npm:^1.2.1" - case-sensitive-paths-webpack-plugin: "npm:^2.4.0" - cjs-module-lexer: "npm:^1.2.3" - constants-browserify: "npm:^1.0.0" - css-loader: "npm:^6.7.1" - es-module-lexer: "npm:^1.5.0" - fork-ts-checker-webpack-plugin: "npm:^8.0.0" - html-webpack-plugin: "npm:^5.5.0" - magic-string: "npm:^0.30.5" - path-browserify: "npm:^1.0.1" - process: "npm:^0.11.10" - semver: "npm:^7.3.7" - style-loader: "npm:^3.3.1" - terser-webpack-plugin: "npm:^5.3.1" ts-dedent: "npm:^2.0.0" - url: "npm:^0.11.0" - util: "npm:^0.12.4" - util-deprecate: "npm:^1.0.2" - webpack: "npm:5" - webpack-dev-middleware: "npm:^6.1.2" - webpack-hot-middleware: "npm:^2.25.1" - webpack-virtual-modules: "npm:^0.6.0" peerDependencies: - storybook: ^8.6.12 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10/787c602308775e69e71925ec79de02b88d092a02e0c76584543dda1ebe3b769c07d7ccd3df1976f64d3562b9049cb92ed3f375bc1e9563505a0064c0c0bf3ea0 + storybook: ^8.6.14 + vite: ^4.0.0 || ^5.0.0 || ^6.0.0 + checksum: 10/d49212238277b911c205b06cff12ea0c65158d089a4f29b0e5184c7e484f0b3d2e83845cf99df48b5f427e12b436d69bb9d8f9535e115a0ba8727bd2c8961bc4 languageName: node linkType: hard -"@storybook/components@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/components@npm:8.6.12" +"@storybook/components@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/components@npm:8.6.14" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/ccc6af275bdfbc66de8afb272f59b2b4b6b76bb2961903335cb62e7defae4a2368bd8f5a2008f8598dd8417e5368d824bce836ca96c3c6ff659dbf2f081ec0dd + checksum: 10/d3647505510313aa3c32fd1f8f202eda723ad99bd353b20aa929c5ecab4edc3c86ad06d0eac02f3c6d948e88f13f65ca5fa35ef27c079ef1b92abe8692f23699 languageName: node linkType: hard -"@storybook/core-webpack@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/core-webpack@npm:8.6.12" +"@storybook/core@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/core@npm:8.6.14" dependencies: - ts-dedent: "npm:^2.0.0" - peerDependencies: - storybook: ^8.6.12 - checksum: 10/2c4d7b51480b16499d13fd5611d4c98345b0251061cfd457ff5ea185ddaa29746b9936c79b1a9b0b39a3b2e527b7c62d1da2cf32cbc5c7a5e42ae50776175ca3 - languageName: node - linkType: hard - -"@storybook/core@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/core@npm:8.6.12" - dependencies: - "@storybook/theming": "npm:8.6.12" + "@storybook/theming": "npm:8.6.14" better-opn: "npm:^3.0.2" browser-assert: "npm:^1.2.1" esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0" @@ -18475,27 +18515,27 @@ __metadata: peerDependenciesMeta: prettier: optional: true - checksum: 10/78776f51b9eae00f9387421b33b646b1dc67ef833fd6272de03399daa7f0ffa248c65b5f24d5d2a9af923a029d06d84d5425e3455302ece542bf47c7a9ec0df6 + checksum: 10/8f8c811edd4ea8dcedcc63a79b3168dc83aa0401e3760990cb5995ea6beaac4026c6ccc1046182f41eed7c09333005f3348d8b42f89fc5dd5b2ce011a9d2a48f languageName: node linkType: hard -"@storybook/csf-plugin@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/csf-plugin@npm:8.6.12" +"@storybook/csf-plugin@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/csf-plugin@npm:8.6.14" dependencies: unplugin: "npm:^1.3.1" peerDependencies: - storybook: ^8.6.12 - checksum: 10/05dc3d5eb567c396f4773faed8283255526e60d7ed05452acd399edfb0d23beba886d9042cb705f76f8055108821eeae8dd2124635b5b47412f279b515affcc3 + storybook: ^8.6.14 + checksum: 10/a0983268e6e77ff1bd6b06ad5a895d22ae534fab9cec74960e70a70023dcee44b2d6be4a77ad692a8ecb2e05e3d65bf4ef58dd0bdc52329b0599ba1e093a07c7 languageName: node linkType: hard "@storybook/csf@npm:^0.1.11": - version: 0.1.12 - resolution: "@storybook/csf@npm:0.1.12" + version: 0.1.13 + resolution: "@storybook/csf@npm:0.1.13" dependencies: type-fest: "npm:^2.19.0" - checksum: 10/f661709de5bd68bfd4ced67df31ef26341168d6679bc13564cb024cfdbc8fdfa94d384267c20b3c858a3058b1ee8dbd71cea169245fcf7b28298890d6c3e1da4 + checksum: 10/8a590703c44180798869fd12c1f314cb96de18349415a33bcfe30ef6af11fdc1cdb755ea620dedfd5eb7666cf05af5647b77fe28b63000aa52b53b0dc3c77bb5 languageName: node linkType: hard @@ -18516,351 +18556,134 @@ __metadata: languageName: node linkType: hard -"@storybook/instrumenter@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/instrumenter@npm:8.6.12" +"@storybook/instrumenter@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/instrumenter@npm:8.6.14" dependencies: "@storybook/global": "npm:^5.0.0" "@vitest/utils": "npm:^2.1.1" peerDependencies: - storybook: ^8.6.12 - checksum: 10/11f608406a2d83a500a9270fda57bbec4aa1f97a3d95a4b52f44dce4efbfd6d198b33a0077cee2b48251af191722d2fdbfd1fe89255ded27656372214e37cb7c + storybook: ^8.6.14 + checksum: 10/7e2381ffbbd9065c021892f6f24fbf950aa57857da33a215cd7ffd662b76a282df3da7c664bf3eff14858995e44dc649b4d62069b694d8c69b8de0e43d31efc7 languageName: node linkType: hard -"@storybook/manager-api@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/manager-api@npm:8.6.12" +"@storybook/manager-api@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/manager-api@npm:8.6.14" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/d1c44c6649a024c4007461c12a15337f5d13532dbaccc4c02f71bd99599fb973e2574eb8f1bc2d93e05da24e4ae43fa47ec637a7c4cccf5ffc67045cafbf087c + checksum: 10/4544b317050b81574f1cd6f911dcb99ee2adbd7190555209171a7dd2233cd331165fa11c16a5977ebe58eeaf26c86bbfcb23f701cfd79a10f0d034dae65197bd languageName: node linkType: hard -"@storybook/node-logger@npm:^8.0.0-alpha.10": - version: 8.4.5 - resolution: "@storybook/node-logger@npm:8.4.5" +"@storybook/preview-api@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/preview-api@npm:8.6.14" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/5b7d49a799def4041d11d70c657bf91bff2d7cda7a6afbfe51d8da58f18c8167b636a99efc749246e1bf76eb29cf6b531ff0a671590e39e4d15fbe179e87287c + checksum: 10/9b77288f2f627a7c70cfd3e88bdc4348c1425fab6b333ed77efb913e45881bcdf2ca67f1174e47fe978a7993c2a653fb8accf518ba51441cfb9450145101e9d8 languageName: node linkType: hard -"@storybook/preset-react-webpack@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/preset-react-webpack@npm:8.6.12" +"@storybook/react-dom-shim@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/react-dom-shim@npm:8.6.14" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + storybook: ^8.6.14 + checksum: 10/d14d970b98dc8266d4df80225538fb537449c15877f0f54465dfe52242f0c3f041c975824cc2894f72a3a8023fdffa0a281d90325b95297858e3684913b3cc15 + languageName: node + linkType: hard + +"@storybook/react-vite@npm:^8.6.12": + version: 8.6.14 + resolution: "@storybook/react-vite@npm:8.6.14" dependencies: - "@storybook/core-webpack": "npm:8.6.12" - "@storybook/react": "npm:8.6.12" - "@storybook/react-docgen-typescript-plugin": "npm:1.0.6--canary.9.0c3f3b7.0" - "@types/semver": "npm:^7.3.4" + "@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.5.0" + "@rollup/pluginutils": "npm:^5.0.2" + "@storybook/builder-vite": "npm:8.6.14" + "@storybook/react": "npm:8.6.14" find-up: "npm:^5.0.0" - magic-string: "npm:^0.30.5" + magic-string: "npm:^0.30.0" react-docgen: "npm:^7.0.0" resolve: "npm:^1.22.8" - semver: "npm:^7.3.7" tsconfig-paths: "npm:^4.2.0" - webpack: "npm:5" peerDependencies: + "@storybook/test": 8.6.14 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.12 + storybook: ^8.6.14 + vite: ^4.0.0 || ^5.0.0 || ^6.0.0 peerDependenciesMeta: - typescript: + "@storybook/test": optional: true - checksum: 10/7f6ae4fe8584cc46c9d1d4959f69983e5fef08814e24a801f377c04c781cc86a3ee5785e2ef2e4e66ffd82a4b0324f604704af3531362bd0760ba944871c1551 + checksum: 10/061be89410b3835bb19e82e6770c93dee14cb2fa00616321cdd360e4f808f9927d951bca2e714be255e8b1aaff72dbc1af97e3e7c18878f0685858fd3cc85ec7 languageName: node linkType: hard -"@storybook/preview-api@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/preview-api@npm:8.6.12" - peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/d24f11e4e54e9e51297b0f87e1d462b3f14a974b4681f31d93b62b0706ce5b5ed4ffaaac521ec049dcb0e08e7aa7590f2e039aee4bbe9f85033d69474d982f23 - languageName: node - linkType: hard - -"@storybook/react-docgen-typescript-plugin@npm:1.0.6--canary.9.0c3f3b7.0": - version: 1.0.6--canary.9.0c3f3b7.0 - resolution: "@storybook/react-docgen-typescript-plugin@npm:1.0.6--canary.9.0c3f3b7.0" +"@storybook/react@npm:8.6.14, @storybook/react@npm:^8.6.12": + version: 8.6.14 + resolution: "@storybook/react@npm:8.6.14" dependencies: - debug: "npm:^4.1.1" - endent: "npm:^2.0.1" - find-cache-dir: "npm:^3.3.1" - flat-cache: "npm:^3.0.4" - micromatch: "npm:^4.0.2" - react-docgen-typescript: "npm:^2.2.2" - tslib: "npm:^2.0.0" - peerDependencies: - typescript: ">= 4.x" - webpack: ">= 4" - checksum: 10/5d3c64b022d10d7316b600a41eec2cf38ba460e11bf9a01ae976e8d0efe6959633423d2a3546a5d3f9f04bcc946e83774e8efdf9ccfedb76a7065ec08e7ec809 - languageName: node - linkType: hard - -"@storybook/react-dom-shim@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/react-dom-shim@npm:8.6.12" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.12 - checksum: 10/7677b4fae978209af239471f9eb609db4318815bd4249a0f48b9875482d9ae910b93fbe4db5d7f794ecc2a1249edf40da26af9de673c941c48fccc4007819c96 - languageName: node - linkType: hard - -"@storybook/react-webpack5@npm:^8.6.12": - version: 8.6.12 - resolution: "@storybook/react-webpack5@npm:8.6.12" - dependencies: - "@storybook/builder-webpack5": "npm:8.6.12" - "@storybook/preset-react-webpack": "npm:8.6.12" - "@storybook/react": "npm:8.6.12" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.12 - typescript: ">= 4.2.x" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10/c4ce4baa00837c9f90cb740f916c778418a6bcf9b338db33a9c6b154d4de7ce041bf142beed5eade77998eb77992a5928a870f6a184f01b20ee6f7e796ada547 - languageName: node - linkType: hard - -"@storybook/react@npm:8.6.12, @storybook/react@npm:^8.6.12": - version: 8.6.12 - resolution: "@storybook/react@npm:8.6.12" - dependencies: - "@storybook/components": "npm:8.6.12" + "@storybook/components": "npm:8.6.14" "@storybook/global": "npm:^5.0.0" - "@storybook/manager-api": "npm:8.6.12" - "@storybook/preview-api": "npm:8.6.12" - "@storybook/react-dom-shim": "npm:8.6.12" - "@storybook/theming": "npm:8.6.12" + "@storybook/manager-api": "npm:8.6.14" + "@storybook/preview-api": "npm:8.6.14" + "@storybook/react-dom-shim": "npm:8.6.14" + "@storybook/theming": "npm:8.6.14" peerDependencies: - "@storybook/test": 8.6.12 + "@storybook/test": 8.6.14 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.12 + storybook: ^8.6.14 typescript: ">= 4.2.x" peerDependenciesMeta: "@storybook/test": optional: true typescript: optional: true - checksum: 10/d8258c82743906f48a872a781f3e5a63e9ce3fda2ba3b911e959cf62ebda43989b23746257d97905addb7142ac7e53d0089dc178bcbeea48ed4d37d025dd047b + checksum: 10/a8710dcb80da9df4a78cfb3560a8c0c31c6f6c04902aa95446d4e40f6a9179770ead6f9a0a06fa44d00677cd1cd92eb60fdc57f433035ec722275847139671a4 languageName: node linkType: hard -"@storybook/test@npm:8.6.12, @storybook/test@npm:^8.6.12": - version: 8.6.12 - resolution: "@storybook/test@npm:8.6.12" +"@storybook/source-loader@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/source-loader@npm:8.6.14" + dependencies: + es-toolkit: "npm:^1.22.0" + estraverse: "npm:^5.2.0" + prettier: "npm:^3.1.1" + peerDependencies: + storybook: ^8.6.14 + checksum: 10/8c96c3e39f19944db297d1ea8a8560b101f3b7be6ab8e2ac1222155454f8fb3ff3a4db3c6646bad514eaf5c28fce51b037f3cc55f34edd419c4d8502caec88ea + languageName: node + linkType: hard + +"@storybook/test@npm:8.6.14, @storybook/test@npm:^8.6.12": + version: 8.6.14 + resolution: "@storybook/test@npm:8.6.14" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/instrumenter": "npm:8.6.12" + "@storybook/instrumenter": "npm:8.6.14" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.5.0" "@testing-library/user-event": "npm:14.5.2" "@vitest/expect": "npm:2.0.5" "@vitest/spy": "npm:2.0.5" peerDependencies: - storybook: ^8.6.12 - checksum: 10/495409d95a6c649c54afd7304d429f1d7ef29ef9ac40415550ce60115d3f4210a228d7ab927dcd3229f63954e5a282f407cb8bc5816c6cfc9a45fcbc8e30bae8 + storybook: ^8.6.14 + checksum: 10/47f65b441389b497a01cd3c3830cc46eb984836bb3c13848799c6074529f5609b284eb16f5473ac9029c7d857def1f13e998325083c4d09bb48a0b171d4221a3 languageName: node linkType: hard -"@storybook/theming@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/theming@npm:8.6.12" +"@storybook/theming@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/theming@npm:8.6.14" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/c811d9dbb9eaaa680b922111fca126a2985f2238dfb01c1cd23184323eea12899dc9f079063ac42c5e63b0c83de326bd9cc17241e4060ff04e860c57a55fb8b9 - languageName: node - linkType: hard - -"@sucrase/webpack-loader@npm:^2.0.0": - version: 2.0.0 - resolution: "@sucrase/webpack-loader@npm:2.0.0" - dependencies: - loader-utils: "npm:^1.1.0" - peerDependencies: - sucrase: ^3 - checksum: 10/16578991b1b888ac5bec5628bd24db9e21651bbbe30de076aece8787f115d8971ac87a20bc75446187c73c3185851ec2233d5b6f18c4a2dd53fbbb1ed4e488b4 - languageName: node - linkType: hard - -"@svgr/babel-plugin-add-jsx-attribute@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:6.5.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/cab83832830a57735329ed68f67c03b57ca21fa037b0134847b0c5c0ef4beca89956d7dacfbf7b2a10fd901e7009e877512086db2ee918b8c69aee7742ae32c0 - languageName: node - linkType: hard - -"@svgr/babel-plugin-remove-jsx-attribute@npm:*": - version: 8.0.0 - resolution: "@svgr/babel-plugin-remove-jsx-attribute@npm:8.0.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/ff992893c6c4ac802713ba3a97c13be34e62e6d981c813af40daabcd676df68a72a61bd1e692bb1eda3587f1b1d700ea462222ae2153bb0f46886632d4f88d08 - languageName: node - linkType: hard - -"@svgr/babel-plugin-remove-jsx-empty-expression@npm:*": - version: 8.0.0 - resolution: "@svgr/babel-plugin-remove-jsx-empty-expression@npm:8.0.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/0fb691b63a21bac00da3aa2dccec50d0d5a5b347ff408d60803b84410d8af168f2656e4ba1ee1f24dab0ae4e4af77901f2928752bb0434c1f6788133ec599ec8 - languageName: node - linkType: hard - -"@svgr/babel-plugin-replace-jsx-attribute-value@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:6.5.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/b7d2125758e766e1ebd14b92216b800bdc976959bc696dbfa1e28682919147c1df4bb8b1b5fd037d7a83026e27e681fea3b8d3741af8d3cf4c9dfa3d412125df - languageName: node - linkType: hard - -"@svgr/babel-plugin-svg-dynamic-title@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:6.5.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/0fd42ebf127ae9163ef341e84972daa99bdcb9e6ed3f83aabd95ee173fddc43e40e02fa847fbc0a1058cf5549f72b7960a2c5e22c3e4ac18f7e3ac81277852ae - languageName: node - linkType: hard - -"@svgr/babel-plugin-svg-em-dimensions@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:6.5.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/c1550ee9f548526fa66fd171e3ffb5696bfc4e4cd108a631d39db492c7410dc10bba4eb5a190e9df824bf806130ccc586ae7d2e43c547e6a4f93bbb29a18f344 - languageName: node - linkType: hard - -"@svgr/babel-plugin-transform-react-native-svg@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:6.5.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/4c924af22b948b812629e80efb90ad1ec8faae26a232d8ca8a06b46b53e966a2c415a57806a3ff0ea806a622612e546422719b69ec6839717a7755dac19171d9 - languageName: node - linkType: hard - -"@svgr/babel-plugin-transform-svg-component@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-plugin-transform-svg-component@npm:6.5.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/a4ddd3cf8b1a7a0542ff2c6a3eb7a75d6f79a86a62210306d94fb05e59699bb5da4ddde9ce98ef477b9cd528007fb728dc4d388d413b3aa25f48ed92b1f0a1c1 - languageName: node - linkType: hard - -"@svgr/babel-preset@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/babel-preset@npm:6.5.1" - dependencies: - "@svgr/babel-plugin-add-jsx-attribute": "npm:^6.5.1" - "@svgr/babel-plugin-remove-jsx-attribute": "npm:*" - "@svgr/babel-plugin-remove-jsx-empty-expression": "npm:*" - "@svgr/babel-plugin-replace-jsx-attribute-value": "npm:^6.5.1" - "@svgr/babel-plugin-svg-dynamic-title": "npm:^6.5.1" - "@svgr/babel-plugin-svg-em-dimensions": "npm:^6.5.1" - "@svgr/babel-plugin-transform-react-native-svg": "npm:^6.5.1" - "@svgr/babel-plugin-transform-svg-component": "npm:^6.5.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/9f124be39a8e64f909162f925b3a63ddaa5a342a5e24fc0b7f7d9d4d7f7e3b916596c754fb557dc259928399cad5366a27cb231627a0d2dcc4b13ac521cf05af - languageName: node - linkType: hard - -"@svgr/core@npm:6.5.x, @svgr/core@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/core@npm:6.5.1" - dependencies: - "@babel/core": "npm:^7.19.6" - "@svgr/babel-preset": "npm:^6.5.1" - "@svgr/plugin-jsx": "npm:^6.5.1" - camelcase: "npm:^6.2.0" - cosmiconfig: "npm:^7.0.1" - checksum: 10/0aa3078eefb969d93fb5639c2d64c8868cf65134f0e36a1733dc595acc990081cbad62295e34b860150ce6baa21516d71410c5527579a1a0950cdc35a765873a - languageName: node - linkType: hard - -"@svgr/hast-util-to-babel-ast@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/hast-util-to-babel-ast@npm:6.5.1" - dependencies: - "@babel/types": "npm:^7.20.0" - entities: "npm:^4.4.0" - checksum: 10/0410c6e5bf98fe31729ab1785642b915e7645e65c7ee5b2dd292a4603f8a1377402b95237c550b10dbdcc0bf084df1546ac7e98004d1fe5982cb8508147b47bb - languageName: node - linkType: hard - -"@svgr/plugin-jsx@npm:6.5.x, @svgr/plugin-jsx@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/plugin-jsx@npm:6.5.1" - dependencies: - "@babel/core": "npm:^7.19.6" - "@svgr/babel-preset": "npm:^6.5.1" - "@svgr/hast-util-to-babel-ast": "npm:^6.5.1" - svg-parser: "npm:^2.0.4" - peerDependencies: - "@svgr/core": ^6.0.0 - checksum: 10/42f22847a6bdf930514d7bedd3c5e1fd8d53eb3594779f9db16cb94c762425907c375cd8ec789114e100a4d38068aca6c7ab5efea4c612fba63f0630c44cc859 - languageName: node - linkType: hard - -"@svgr/plugin-svgo@npm:6.5.x, @svgr/plugin-svgo@npm:^6.5.1": - version: 6.5.1 - resolution: "@svgr/plugin-svgo@npm:6.5.1" - dependencies: - cosmiconfig: "npm:^7.0.1" - deepmerge: "npm:^4.2.2" - svgo: "npm:^2.8.0" - peerDependencies: - "@svgr/core": "*" - checksum: 10/cd2833530ac0485221adc2146fd992ab20d79f4b12eebcd45fa859721dd779483158e11dfd9a534858fe468416b9412416e25cbe07ac7932c44ed5fa2021c72e - languageName: node - linkType: hard - -"@svgr/rollup@npm:6.5.x": - version: 6.5.1 - resolution: "@svgr/rollup@npm:6.5.1" - dependencies: - "@babel/core": "npm:^7.19.6" - "@babel/plugin-transform-react-constant-elements": "npm:^7.18.12" - "@babel/preset-env": "npm:^7.19.4" - "@babel/preset-react": "npm:^7.18.6" - "@babel/preset-typescript": "npm:^7.18.6" - "@rollup/pluginutils": "npm:^4.2.1" - "@svgr/core": "npm:^6.5.1" - "@svgr/plugin-jsx": "npm:^6.5.1" - "@svgr/plugin-svgo": "npm:^6.5.1" - checksum: 10/809198a655c280b434d762829aeab0c48e545daaa7a520ac87d5e7cfe96402eb4d0c01f8b25959fcc37a2ce4aa1a53c9e1c4ccb1206cd5833883a34db5799dd4 - languageName: node - linkType: hard - -"@svgr/webpack@npm:6.5.x": - version: 6.5.1 - resolution: "@svgr/webpack@npm:6.5.1" - dependencies: - "@babel/core": "npm:^7.19.6" - "@babel/plugin-transform-react-constant-elements": "npm:^7.18.12" - "@babel/preset-env": "npm:^7.19.4" - "@babel/preset-react": "npm:^7.18.6" - "@babel/preset-typescript": "npm:^7.18.6" - "@svgr/core": "npm:^6.5.1" - "@svgr/plugin-jsx": "npm:^6.5.1" - "@svgr/plugin-svgo": "npm:^6.5.1" - checksum: 10/2748acc94839a2da09d73fe23bd9df85e08d52d823425591c960e8a25b83861ca2f49dbb1d66ea318da8160f16ce6248c8854229bd6316565517356c74c3440f + checksum: 10/6936ea3348968fe598ad47421c11a78c6ee2ce62336ea1ce9cb8257e9faa2553d3ac3e443f8a36d35a41b0d60eb169231516649c710582ec68fdead4f23ffc0e languageName: node linkType: hard @@ -19400,90 +19223,90 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-darwin-arm64@npm:1.11.21" +"@swc/core-darwin-arm64@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-darwin-arm64@npm:1.11.24" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-darwin-x64@npm:1.11.21" +"@swc/core-darwin-x64@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-darwin-x64@npm:1.11.24" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.21" +"@swc/core-linux-arm-gnueabihf@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.24" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-linux-arm64-gnu@npm:1.11.21" +"@swc/core-linux-arm64-gnu@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-linux-arm64-gnu@npm:1.11.24" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-linux-arm64-musl@npm:1.11.21" +"@swc/core-linux-arm64-musl@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-linux-arm64-musl@npm:1.11.24" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-linux-x64-gnu@npm:1.11.21" +"@swc/core-linux-x64-gnu@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-linux-x64-gnu@npm:1.11.24" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-linux-x64-musl@npm:1.11.21" +"@swc/core-linux-x64-musl@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-linux-x64-musl@npm:1.11.24" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-win32-arm64-msvc@npm:1.11.21" +"@swc/core-win32-arm64-msvc@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-win32-arm64-msvc@npm:1.11.24" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-win32-ia32-msvc@npm:1.11.21" +"@swc/core-win32-ia32-msvc@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-win32-ia32-msvc@npm:1.11.24" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.11.21": - version: 1.11.21 - resolution: "@swc/core-win32-x64-msvc@npm:1.11.21" +"@swc/core-win32-x64-msvc@npm:1.11.24": + version: 1.11.24 + resolution: "@swc/core-win32-x64-msvc@npm:1.11.24" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@swc/core@npm:^1.10.8, @swc/core@npm:^1.3.46": - version: 1.11.21 - resolution: "@swc/core@npm:1.11.21" +"@swc/core@npm:^1.3.46": + version: 1.11.24 + resolution: "@swc/core@npm:1.11.24" dependencies: - "@swc/core-darwin-arm64": "npm:1.11.21" - "@swc/core-darwin-x64": "npm:1.11.21" - "@swc/core-linux-arm-gnueabihf": "npm:1.11.21" - "@swc/core-linux-arm64-gnu": "npm:1.11.21" - "@swc/core-linux-arm64-musl": "npm:1.11.21" - "@swc/core-linux-x64-gnu": "npm:1.11.21" - "@swc/core-linux-x64-musl": "npm:1.11.21" - "@swc/core-win32-arm64-msvc": "npm:1.11.21" - "@swc/core-win32-ia32-msvc": "npm:1.11.21" - "@swc/core-win32-x64-msvc": "npm:1.11.21" + "@swc/core-darwin-arm64": "npm:1.11.24" + "@swc/core-darwin-x64": "npm:1.11.24" + "@swc/core-linux-arm-gnueabihf": "npm:1.11.24" + "@swc/core-linux-arm64-gnu": "npm:1.11.24" + "@swc/core-linux-arm64-musl": "npm:1.11.24" + "@swc/core-linux-x64-gnu": "npm:1.11.24" + "@swc/core-linux-x64-musl": "npm:1.11.24" + "@swc/core-win32-arm64-msvc": "npm:1.11.24" + "@swc/core-win32-ia32-msvc": "npm:1.11.24" + "@swc/core-win32-x64-msvc": "npm:1.11.24" "@swc/counter": "npm:^0.1.3" "@swc/types": "npm:^0.1.21" peerDependencies: @@ -19512,7 +19335,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/98a0f201a0a4aa026b0b07c61c8da49d94d4ac3b059b466416c90b9b2cb4f457d836ae55ccfe90b0a89c7bdde36526ba5b7747c4e616c0d556c733e3728e9dd4 + checksum: 10/0b3e883f8a5652a7ab221a777386ccc8a65fc5b53d533bad15b703b22984eb3b449efd907b1872263f1a9990a9a50612f3c6deb619894a43f03cd974ec9bd1b7 languageName: node linkType: hard @@ -19533,15 +19356,15 @@ __metadata: linkType: hard "@swc/jest@npm:^0.2.22": - version: 0.2.37 - resolution: "@swc/jest@npm:0.2.37" + version: 0.2.38 + resolution: "@swc/jest@npm:0.2.38" dependencies: "@jest/create-cache-key-function": "npm:^29.7.0" "@swc/counter": "npm:^0.1.3" jsonc-parser: "npm:^3.2.0" peerDependencies: "@swc/core": "*" - checksum: 10/bbec37079b4f5c1ff1c95aeec07d08277c646a0c5e16e057ea3a8fe5c6e2bd59bbfc4312e53ddd05d25fa4de20a03607be274f560f28bb5e229dd08124780e16 + checksum: 10/3aaf557425e806890ebefea35334b7795e9f8ddf6f82d634d865ef917333cca4208190af1a9610c134c0e3b7a6a1aea4ec77a659e3ca5965be7aace65ce80c97 languageName: node linkType: hard @@ -19622,7 +19445,7 @@ __metadata: languageName: unknown linkType: soft -"@testing-library/dom@npm:10.4.0, @testing-library/dom@npm:^10.0.0": +"@testing-library/dom@npm:10.4.0": version: 10.4.0 resolution: "@testing-library/dom@npm:10.4.0" dependencies: @@ -19638,6 +19461,22 @@ __metadata: languageName: node linkType: hard +"@testing-library/dom@npm:^10.0.0": + version: 10.4.1 + resolution: "@testing-library/dom@npm:10.4.1" + dependencies: + "@babel/code-frame": "npm:^7.10.4" + "@babel/runtime": "npm:^7.12.5" + "@types/aria-query": "npm:^5.0.1" + aria-query: "npm:5.3.0" + dom-accessibility-api: "npm:^0.5.9" + lz-string: "npm:^1.5.0" + picocolors: "npm:1.1.1" + pretty-format: "npm:^27.0.2" + checksum: 10/7f93e09ea015f151f8b8f42cbab0b2b858999b5445f15239a72a612ef7716e672b14c40c421218194cf191cbecbde0afa6f3dc2cc83dda93ff6a4fb0237df6e6 + languageName: node + linkType: hard + "@testing-library/jest-dom@npm:6.5.0": version: 6.5.0 resolution: "@testing-library/jest-dom@npm:6.5.0" @@ -19654,17 +19493,17 @@ __metadata: linkType: hard "@testing-library/jest-dom@npm:^6.0.0": - version: 6.6.3 - resolution: "@testing-library/jest-dom@npm:6.6.3" + version: 6.6.4 + resolution: "@testing-library/jest-dom@npm:6.6.4" dependencies: "@adobe/css-tools": "npm:^4.4.0" aria-query: "npm:^5.0.0" - chalk: "npm:^3.0.0" css.escape: "npm:^1.5.1" dom-accessibility-api: "npm:^0.6.3" lodash: "npm:^4.17.21" + picocolors: "npm:^1.1.1" redent: "npm:^3.0.0" - checksum: 10/1f3427e45870eab9dcc59d6504b780d4a595062fe1687762ae6e67d06a70bf439b40ab64cf58cbace6293a99e3764d4647fdc8300a633b721764f5ce39dade18 + checksum: 10/5e67112c789f884fb75b279c2cddfdd0995a012a7847a03c474e4134f0d213934ee70c97433bca26b45e3a5ffa56faafe6499c8e57841179c4f2bd80eef429cd languageName: node linkType: hard @@ -19691,8 +19530,8 @@ __metadata: linkType: hard "@testing-library/react@npm:^16.0.0": - version: 16.1.0 - resolution: "@testing-library/react@npm:16.1.0" + version: 16.3.0 + resolution: "@testing-library/react@npm:16.3.0" dependencies: "@babel/runtime": "npm:^7.12.5" peerDependencies: @@ -19706,7 +19545,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10/2a20e0dbfadbc93d45a84e82281ed47deed54a6a5fc1461a523172d7fbc0481e8502cf98a2080f38aba94290b3d745671a1c9e320e6f76ad6afcca67c580b963 + checksum: 10/0ee9e31dd0d2396a924682d0e61a4ecc6bfab8eaff23dbf8a72c3c2ce22c116fa578148baeb4de75b968ef99d22e6e6aa0a00dba40286f71184918bb6bb5b06a languageName: node linkType: hard @@ -19728,6 +19567,17 @@ __metadata: languageName: node linkType: hard +"@tokenizer/inflate@npm:^0.2.7": + version: 0.2.7 + resolution: "@tokenizer/inflate@npm:0.2.7" + dependencies: + debug: "npm:^4.4.0" + fflate: "npm:^0.8.2" + token-types: "npm:^6.0.0" + checksum: 10/6cee1857e47ca0fc053d6cd87773b7c21857ab84cb847c7d9437a76d923e265c88f8e99a4ac9643c2f989f4b9791259ca17128f0480191449e2b412821a1b9a7 + languageName: node + linkType: hard + "@tokenizer/token@npm:^0.3.0": version: 0.3.0 resolution: "@tokenizer/token@npm:0.3.0" @@ -19791,6 +19641,41 @@ __metadata: languageName: node linkType: hard +"@ts-graphviz/adapter@npm:^2.0.6": + version: 2.0.6 + resolution: "@ts-graphviz/adapter@npm:2.0.6" + dependencies: + "@ts-graphviz/common": "npm:^2.1.5" + checksum: 10/6f15c14a166126e29dddb13f2ad012d8ac4d883e6fa789263b05ee50e0b47f2c09cf4c7f09b549e711563c30d6a6c2e45b52df0fd2701db2953dff112c0e9ea6 + languageName: node + linkType: hard + +"@ts-graphviz/ast@npm:^2.0.7": + version: 2.0.7 + resolution: "@ts-graphviz/ast@npm:2.0.7" + dependencies: + "@ts-graphviz/common": "npm:^2.1.5" + checksum: 10/ca2e0054cb97106e5993d642b8277b60b29a1d227ff75e576d56704a8364790d3fb535788243ac2ec7bc46f077cd760da61fa4a76ddfda5877d40d77b857c3c5 + languageName: node + linkType: hard + +"@ts-graphviz/common@npm:^2.1.5": + version: 2.1.5 + resolution: "@ts-graphviz/common@npm:2.1.5" + checksum: 10/421ea1a0d32917d6f26bceaf7b8e99db9f405afd3bbbb83bb9d2c759ee4bdcf15d5e611b3590becf55615f0ff35a9766fb19268c8c01c3dee0b02a74d0ef1a27 + languageName: node + linkType: hard + +"@ts-graphviz/core@npm:^2.0.7": + version: 2.0.7 + resolution: "@ts-graphviz/core@npm:2.0.7" + dependencies: + "@ts-graphviz/ast": "npm:^2.0.7" + "@ts-graphviz/common": "npm:^2.1.5" + checksum: 10/a2884db12a822ba0fa22845d382459332add43b8297c78d6e7223d89403aacf83508133a00bbf2122c6097b52351bfc5bcf856539379c6d35c9f1439d39cb89b + languageName: node + linkType: hard + "@ts-morph/common@npm:~0.25.0": version: 0.25.0 resolution: "@ts-morph/common@npm:0.25.0" @@ -19847,6 +19732,15 @@ __metadata: languageName: node linkType: hard +"@tybys/wasm-util@npm:^0.10.0": + version: 0.10.0 + resolution: "@tybys/wasm-util@npm:0.10.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/779d047a77e8a619b6e26b6fe556f413316d846e9a35438668a15510a4d6e7294388c998f65911f6f1a13838745575d7793cb1d27182752f6f95991725b15d45 + languageName: node + linkType: hard + "@types/ansi-regex@npm:^5.0.0": version: 5.0.4 resolution: "@types/ansi-regex@npm:5.0.4" @@ -19879,10 +19773,10 @@ __metadata: languageName: node linkType: hard -"@types/aws-lambda@npm:8.10.143, @types/aws-lambda@npm:^8.10.83": - version: 8.10.143 - resolution: "@types/aws-lambda@npm:8.10.143" - checksum: 10/326a6af5f26e6abab5ec0479829998714280656ea4f9e8623d1f18b159db7993116acbe2e5b0351e8e34601ff283c79b2e8c7c8c0c874f43d2649d9b00c50942 +"@types/aws-lambda@npm:8.10.150, @types/aws-lambda@npm:^8.10.83": + version: 8.10.150 + resolution: "@types/aws-lambda@npm:8.10.150" + checksum: 10/d837ae0b94fb16c7cfec3711126f0e7fb109fc3e6d14d994d083d2039c4f2bc52a75f256ee31a8010f023ce78ddcf3746ad43d552592a0f56720167f6d3111be languageName: node linkType: hard @@ -19928,11 +19822,11 @@ __metadata: linkType: hard "@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6, @types/babel__traverse@npm:^7.18.0": - version: 7.20.6 - resolution: "@types/babel__traverse@npm:7.20.6" + version: 7.28.0 + resolution: "@types/babel__traverse@npm:7.28.0" dependencies: - "@babel/types": "npm:^7.20.7" - checksum: 10/63d13a3789aa1e783b87a8b03d9fb2c2c90078de7782422feff1631b8c2a25db626e63a63ac5a1465d47359201c73069dacb4b52149d17c568187625da3064ae + "@babel/types": "npm:^7.28.2" + checksum: 10/371c5e1b40399ef17570e630b2943617b84fafde2860a56f0ebc113d8edb1d0534ade0175af89eda1ae35160903c33057ed42457e165d4aa287fedab2c82abcf languageName: node linkType: hard @@ -19971,12 +19865,12 @@ __metadata: languageName: node linkType: hard -"@types/bunyan@npm:1.8.9": - version: 1.8.9 - resolution: "@types/bunyan@npm:1.8.9" +"@types/bunyan@npm:1.8.11": + version: 1.8.11 + resolution: "@types/bunyan@npm:1.8.11" dependencies: "@types/node": "npm:*" - checksum: 10/5d6e818cc324eee60ad4658fc169bad2a7ec794de383de5333327951c7be74804cf70230ac4a93b4e6ce5d86106e81e6fcc8c617454617bad6a721011b619435 + checksum: 10/8906ec6c0573f563139681344c2a1d220c394e4aa5b88ff99b011894734f74aafc9d0b3a71758dda3b0ee30e9b6370a8aa48ac26b5585f3c8d7e0fe8db6ca844 languageName: node linkType: hard @@ -20008,6 +19902,15 @@ __metadata: languageName: node linkType: hard +"@types/chai@npm:^5.2.2": + version: 5.2.2 + resolution: "@types/chai@npm:5.2.2" + dependencies: + "@types/deep-eql": "npm:*" + checksum: 10/de425e7b02cc1233a93923866e019dffbafa892774813940b780ebb1ac9f8a8c57b7438c78686bf4e5db05cd3fc8a970fedf6b83638543995ecca88ef2060668 + languageName: node + linkType: hard + "@types/classnames@npm:^2.2.9": version: 2.3.0 resolution: "@types/classnames@npm:2.3.0" @@ -20079,28 +19982,28 @@ __metadata: languageName: node linkType: hard -"@types/connect@npm:*, @types/connect@npm:3.4.36": - version: 3.4.36 - resolution: "@types/connect@npm:3.4.36" +"@types/connect@npm:*, @types/connect@npm:3.4.38": + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" dependencies: "@types/node": "npm:*" - checksum: 10/4dee3d966fb527b98f0cbbdcf6977c9193fc3204ed539b7522fe5e64dfa45f9017bdda4ffb1f760062262fce7701a0ee1c2f6ce2e50af36c74d4e37052303172 + checksum: 10/7eb1bc5342a9604facd57598a6c62621e244822442976c443efb84ff745246b10d06e8b309b6e80130026a396f19bf6793b7cecd7380169f369dac3bfc46fb99 languageName: node linkType: hard "@types/content-type@npm:^1.1.8": - version: 1.1.8 - resolution: "@types/content-type@npm:1.1.8" - checksum: 10/2dd15e51925db7208b0d989c3a93d805a0e5e0942aa9edd70a1c3520896b772526d8280e344a674ae68a96a24aa8fce290843a07512460176f36a3020d99c792 + version: 1.1.9 + resolution: "@types/content-type@npm:1.1.9" + checksum: 10/ac2d419877661f25c6b2c174f42e142ee542cae7778af0a519d4787545ea190d2ff91a40acf68e5e8c52d22919be9ff0345e38a393866546ae9d45b8a79e94f6 languageName: node linkType: hard "@types/cookie-parser@npm:^1.4.2": - version: 1.4.8 - resolution: "@types/cookie-parser@npm:1.4.8" + version: 1.4.9 + resolution: "@types/cookie-parser@npm:1.4.9" peerDependencies: "@types/express": "*" - checksum: 10/b26ace5560adce1a746c059a75f18227ee3e3614a49c70dd29b8af204696079ced22eaa9d6cb084e3433ec433bff845cb47ffb890ae9af96a4aab2ff85e40da7 + checksum: 10/6192a4899b5412a4c3be0f47158321aef73a4cd7e7a4f7b2a37e2e1045f11a21209681cb1bc5335f250ee2a6ce64d8a3fefb851181a98e6415d3716ef9ed1f62 languageName: node linkType: hard @@ -20133,11 +20036,11 @@ __metadata: linkType: hard "@types/cors@npm:^2.8.6": - version: 2.8.17 - resolution: "@types/cors@npm:2.8.17" + version: 2.8.19 + resolution: "@types/cors@npm:2.8.19" dependencies: "@types/node": "npm:*" - checksum: 10/469bd85e29a35977099a3745c78e489916011169a664e97c4c3d6538143b0a16e4cc72b05b407dc008df3892ed7bf595f9b7c0f1f4680e169565ee9d64966bde + checksum: 10/9545cc532c9218754443f48a0c98c1a9ba4af1fe54a3425c95de75ff3158147bb39e666cb7c6bf98cc56a9c6dc7b4ce5b2cbdae6b55d5942e50c81b76ed6b825 languageName: node linkType: hard @@ -20208,6 +20111,13 @@ __metadata: languageName: node linkType: hard +"@types/deep-eql@npm:*": + version: 4.0.2 + resolution: "@types/deep-eql@npm:4.0.2" + checksum: 10/249a27b0bb22f6aa28461db56afa21ec044fa0e303221a62dff81831b20c8530502175f1a49060f7099e7be06181078548ac47c668de79ff9880241968d43d0c + languageName: node + linkType: hard + "@types/docker-modem@npm:*": version: 3.0.2 resolution: "@types/docker-modem@npm:3.0.2" @@ -20218,7 +20128,7 @@ __metadata: languageName: node linkType: hard -"@types/dockerode@npm:^3.3.0, @types/dockerode@npm:^3.3.29": +"@types/dockerode@npm:^3.3.29": version: 3.3.38 resolution: "@types/dockerode@npm:3.3.38" dependencies: @@ -20288,10 +20198,10 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5, @types/estree@npm:^1.0.6": - version: 1.0.7 - resolution: "@types/estree@npm:1.0.7" - checksum: 10/419c845ece767ad4b21171e6e5b63dabb2eb46b9c0d97361edcd9cabbf6a95fcadb91d89b5fa098d1336fa0b8fceaea82fca97a2ef3971f5c86e53031e157b21 +"@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10/25a4c16a6752538ffde2826c2cc0c6491d90e69cd6187bef4a006dd2c3c45469f049e643d7e516c515f21484dc3d48fd5c870be158a5beb72f5baf3dc43e4099 languageName: node linkType: hard @@ -20302,13 +20212,6 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:1.0.6": - version: 1.0.6 - resolution: "@types/estree@npm:1.0.6" - checksum: 10/9d35d475095199c23e05b431bcdd1f6fec7380612aed068b14b2a08aa70494de8a9026765a5a91b1073f636fb0368f6d8973f518a31391d519e20c59388ed88d - languageName: node - linkType: hard - "@types/expect@npm:^1.20.4": version: 1.20.4 resolution: "@types/expect@npm:1.20.4" @@ -20316,19 +20219,7 @@ __metadata: languageName: node linkType: hard -"@types/express-serve-static-core@npm:*": - version: 5.0.0 - resolution: "@types/express-serve-static-core@npm:5.0.0" - dependencies: - "@types/node": "npm:*" - "@types/qs": "npm:*" - "@types/range-parser": "npm:*" - "@types/send": "npm:*" - checksum: 10/fc40cdeae61113d8b2335f4b0f9334a7a64388a0931f2e98f8fc9bdadd0b13b501a70da14c256ae4aa140db49bd2eff75a99a683266d561e62540784a61dc489 - languageName: node - linkType: hard - -"@types/express-serve-static-core@npm:^4.17.21, @types/express-serve-static-core@npm:^4.17.33, @types/express-serve-static-core@npm:^4.17.5": +"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.21, @types/express-serve-static-core@npm:^4.17.33, @types/express-serve-static-core@npm:^4.17.5": version: 4.19.6 resolution: "@types/express-serve-static-core@npm:4.19.6" dependencies: @@ -20341,23 +20232,23 @@ __metadata: linkType: hard "@types/express-session@npm:^1.17.2": - version: 1.18.1 - resolution: "@types/express-session@npm:1.18.1" + version: 1.18.2 + resolution: "@types/express-session@npm:1.18.2" dependencies: "@types/express": "npm:*" - checksum: 10/54316f13e789b758f9ca8209c54aff0bbcf50c9ea50a208d4ba6c3f95d21a60ddaa98bcf459bef02641b38e7244bfbc43aa724a8b607bb13c1c1bab4a139351a + checksum: 10/c1711976d089fd6da58c0262e2b21b71ee64d508ee0f9f1b90e7658b712fc59874e03f799757843d2300c1dd127813cd16ce806c208bc4975f0f7ed700f2db94 languageName: node linkType: hard "@types/express@npm:*, @types/express@npm:^4.16.1, @types/express@npm:^4.17.21, @types/express@npm:^4.17.6": - version: 4.17.21 - resolution: "@types/express@npm:4.17.21" + version: 4.17.23 + resolution: "@types/express@npm:4.17.23" dependencies: "@types/body-parser": "npm:*" "@types/express-serve-static-core": "npm:^4.17.33" "@types/qs": "npm:*" "@types/serve-static": "npm:*" - checksum: 10/7a6d26cf6f43d3151caf4fec66ea11c9d23166e4f3102edfe45a94170654a54ea08cf3103d26b3928d7ebcc24162c90488e33986b7e3a5f8941225edd5eb18c7 + checksum: 10/cf4d540bbd90801cdc79a46107b8873404698a7fd0c3e8dd42989d52d3bd7f5b8768672e54c20835e41e27349c319bb47a404ad14c0f8db0e9d055ba1cb8a05b languageName: node linkType: hard @@ -20463,9 +20354,9 @@ __metadata: linkType: hard "@types/http-errors@npm:*, @types/http-errors@npm:^2.0.0": - version: 2.0.4 - resolution: "@types/http-errors@npm:2.0.4" - checksum: 10/1f3d7c3b32c7524811a45690881736b3ef741bf9849ae03d32ad1ab7062608454b150a4e7f1351f83d26a418b2d65af9bdc06198f1c079d75578282884c4e8e3 + version: 2.0.5 + resolution: "@types/http-errors@npm:2.0.5" + checksum: 10/a88da669366bc483e8f3b3eb3d34ada5f8d13eeeef851b1204d77e2ba6fc42aba4566d877cca5c095204a3f4349b87fe397e3e21288837bdd945dd514120755b languageName: node linkType: hard @@ -20495,12 +20386,12 @@ __metadata: linkType: hard "@types/inquirer@npm:^8.1.3": - version: 8.2.11 - resolution: "@types/inquirer@npm:8.2.11" + version: 8.2.12 + resolution: "@types/inquirer@npm:8.2.12" dependencies: "@types/through": "npm:*" rxjs: "npm:^7.2.0" - checksum: 10/3648c8b76fa9d49e1b4e06a42ab5df00e4fdf9e498056b73d0154dcf46388f8ba034a80d7a45fcae7e7c6499ba24376bd4772ad4dfd3a7a9763413b4ee1ab393 + checksum: 10/b382693011a5f33c92ac1f90a16f3d6fa600c2a294433ca5dc65ab108eccd658b9293e968c34a738ef60d5cd1f2f150ebef7da731aa302201d9978abad1fcb94 languageName: node linkType: hard @@ -20691,9 +20582,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.151": - version: 4.17.16 - resolution: "@types/lodash@npm:4.17.16" - checksum: 10/9a8bb7471a7521bd65d528e1bd14f79819a3eeb6f8a35a8a44649a7d773775c0813e93fd93bd32ccf350bb076c0bf02c6d47877c4625f526f6dd4d283c746aec + version: 4.17.20 + resolution: "@types/lodash@npm:4.17.20" + checksum: 10/8cd8ad3bd78d2e06a93ae8d6c9907981d5673655fec7cb274a4d9a59549aab5bb5b3017361280773b8990ddfccf363e14d1b37c97af8a9fe363de677f9a61524 languageName: node linkType: hard @@ -20817,12 +20708,12 @@ __metadata: languageName: node linkType: hard -"@types/mysql@npm:2.15.26": - version: 2.15.26 - resolution: "@types/mysql@npm:2.15.26" +"@types/mysql@npm:2.15.27": + version: 2.15.27 + resolution: "@types/mysql@npm:2.15.27" dependencies: "@types/node": "npm:*" - checksum: 10/8f205eeaca8f94e998ce4707354bfd02b6ca0da5b7c22289f8f6ff864d549bfb95ca7ddc2f2ebe69eb8f7e3d1f5d8a5b9a2f98aee13824dbc48051bf53a1664d + checksum: 10/a8c743501036f494bb8b26ee04ce914c9cce88955d9ba48ff77d2b5b1bc403d4d99804dbf02238c1491fa93e2242983b1492ad8c2b39755dabd68683dada9e8f languageName: node linkType: hard @@ -20847,11 +20738,11 @@ __metadata: linkType: hard "@types/node-forge@npm:^1.3.0": - version: 1.3.11 - resolution: "@types/node-forge@npm:1.3.11" + version: 1.3.13 + resolution: "@types/node-forge@npm:1.3.13" dependencies: "@types/node": "npm:*" - checksum: 10/670c9b377c48189186ec415e3c8ed371f141ecc1a79ab71b213b20816adeffecba44dae4f8406cc0d09e6349a4db14eb8c5893f643d8e00fa19fc035cf49dee0 + checksum: 10/4d62a6b0cedeb45145de6b05df0082b0ba32675aeb1bf8dbe003804eb61be412a613e82f56b65ba1051594abda4f4c9c0aa9aac009cf106af6faf6217eee8681 languageName: node linkType: hard @@ -20901,7 +20792,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^20.1.1, @types/node@npm:^20.16.0": +"@types/node@npm:^20.16.0": version: 20.17.30 resolution: "@types/node@npm:20.17.30" dependencies: @@ -20942,6 +20833,15 @@ __metadata: languageName: node linkType: hard +"@types/oracledb@npm:6.5.2": + version: 6.5.2 + resolution: "@types/oracledb@npm:6.5.2" + dependencies: + "@types/node": "npm:*" + checksum: 10/de4537f1718a03401d2726afb289cb9d3e421645b9024001458e8be626d6cdc01328c2c89f74c345e180506b334057915252221b15b253dcb0a60f337b5715fc + languageName: node + linkType: hard + "@types/parse-json@npm:^4.0.0": version: 4.0.0 resolution: "@types/parse-json@npm:4.0.0" @@ -21026,25 +20926,14 @@ __metadata: languageName: node linkType: hard -"@types/pg@npm:*": - version: 8.11.10 - resolution: "@types/pg@npm:8.11.10" - dependencies: - "@types/node": "npm:*" - pg-protocol: "npm:*" - pg-types: "npm:^4.0.1" - checksum: 10/65b7d7ca9c90b7cb94aa94ad94bb1883356845e1f64688bc824ecd499da25f63a2400f0a58500554637048a7cc8b91055bbfe14301c082ce9669afd0c18e414c - languageName: node - linkType: hard - -"@types/pg@npm:8.6.1": - version: 8.6.1 - resolution: "@types/pg@npm:8.6.1" +"@types/pg@npm:*, @types/pg@npm:8.15.4": + version: 8.15.4 + resolution: "@types/pg@npm:8.15.4" dependencies: "@types/node": "npm:*" pg-protocol: "npm:*" pg-types: "npm:^2.2.0" - checksum: 10/bf1134ea194ad9cb8bfe0aab7a532713c63bae1d95909fa45e8dc1945e44ede74f2d4c5b2cd2f9712c6b970896929e0d82480f9c9da79addf405c089b590e562 + checksum: 10/dd9203ae6732acad4892513fc99eb2bc699935a95b62e9fdbdcc6d1a90f63881b5fd89d4cac1729790ab3d0bb7c64130b144c65abef34e6bbed063ff43a739a6 languageName: node linkType: hard @@ -21077,9 +20966,9 @@ __metadata: linkType: hard "@types/prop-types@npm:*, @types/prop-types@npm:^15.0.0, @types/prop-types@npm:^15.7.12, @types/prop-types@npm:^15.7.3": - version: 15.7.14 - resolution: "@types/prop-types@npm:15.7.14" - checksum: 10/d0c5407b9ccc3dd5fae0ccf9b1007e7622ba5e6f1c18399b4f24dff33619d469da4b9fa918a374f19dc0d9fe6a013362aab0b844b606cfc10676efba3f5f736d + version: 15.7.15 + resolution: "@types/prop-types@npm:15.7.15" + checksum: 10/31aa2f59b28f24da6fb4f1d70807dae2aedfce090ec63eaf9ea01727a9533ef6eaf017de5bff99fbccad7d1c9e644f52c6c2ba30869465dd22b1a7221c29f356 languageName: node linkType: hard @@ -21093,9 +20982,9 @@ __metadata: linkType: hard "@types/qs@npm:*, @types/qs@npm:^6.9.6": - version: 6.9.18 - resolution: "@types/qs@npm:6.9.18" - checksum: 10/152fab96efd819cc82ae67c39f089df415da6deddb48f1680edaaaa4e86a2a597de7b2ff0ad391df66d11a07006a08d52c9405e86b8cb8f3d5ba15881fe56cc7 + version: 6.14.0 + resolution: "@types/qs@npm:6.14.0" + checksum: 10/1909205514d22b3cbc7c2314e2bd8056d5f05dfb21cf4377f0730ee5e338ea19957c41735d5e4806c746176563f50005bbab602d8358432e25d900bdf4970826 languageName: node linkType: hard @@ -21116,11 +21005,11 @@ __metadata: linkType: hard "@types/react-dom@npm:^18.0.0": - version: 18.3.6 - resolution: "@types/react-dom@npm:18.3.6" + version: 18.3.7 + resolution: "@types/react-dom@npm:18.3.7" peerDependencies: "@types/react": ^18.0.0 - checksum: 10/ae179355401c64423d39946eda22c5f7f74c94ce61c21505024d4d82c33853ec40bc9b370f75e4a7750b0524aba4d95a43fcc328d8d14684dc2abb41ba529de8 + checksum: 10/317569219366d487a3103ba1e5e47154e95a002915fdcf73a44162c48fe49c3a57fcf7f57fc6979e70d447112681e6b13c6c3c1df289db8b544df4aab2d318f3 languageName: node linkType: hard @@ -21181,15 +21070,6 @@ __metadata: languageName: node linkType: hard -"@types/react-text-truncate@npm:^0.14.0": - version: 0.14.4 - resolution: "@types/react-text-truncate@npm:0.14.4" - dependencies: - "@types/react": "npm:*" - checksum: 10/bbefba6aebb607fd0c5121b587f9604cd7c8a0b676666f85cbd8e6c64d17e72f633288601386b82cc97227d39e0a4e8b37de437e524295520641a4873fa1bff5 - languageName: node - linkType: hard - "@types/react-transition-group@npm:^4.2.0, @types/react-transition-group@npm:^4.4.10": version: 4.4.10 resolution: "@types/react-transition-group@npm:4.4.10" @@ -21218,12 +21098,12 @@ __metadata: linkType: hard "@types/react@npm:^18.0.0": - version: 18.3.20 - resolution: "@types/react@npm:18.3.20" + version: 18.3.23 + resolution: "@types/react@npm:18.3.23" dependencies: "@types/prop-types": "npm:*" csstype: "npm:^3.0.2" - checksum: 10/020c51e63b60862e6d772f0cdea0b9441182eedab6289dabd8add0708ded62003834c4e7c6f23a1ccd3ca9486b46296057c3f881c34261a0483765351f8d0bc3 + checksum: 10/4b965dffe34a1f8aac8e2d7e976f113373f38134f9e37239f7e75d7ac6b3c2e1333a8df21febf1fe7749640f8de5708f7668cdfc70bffebda1cc4d3346724fd5 languageName: node linkType: hard @@ -21245,7 +21125,7 @@ __metadata: languageName: node linkType: hard -"@types/request@npm:^2.47.1, @types/request@npm:^2.48.8": +"@types/request@npm:^2.48.8": version: 2.48.12 resolution: "@types/request@npm:2.48.12" dependencies: @@ -21319,13 +21199,20 @@ __metadata: languageName: node linkType: hard -"@types/semver@npm:7.5.8, @types/semver@npm:^7.1.0, @types/semver@npm:^7.3.12, @types/semver@npm:^7.3.4": +"@types/semver@npm:7.5.8": version: 7.5.8 resolution: "@types/semver@npm:7.5.8" checksum: 10/3496808818ddb36deabfe4974fd343a78101fa242c4690044ccdc3b95dcf8785b494f5d628f2f47f38a702f8db9c53c67f47d7818f2be1b79f2efb09692e1178 languageName: node linkType: hard +"@types/semver@npm:^7.1.0, @types/semver@npm:^7.3.12": + version: 7.7.0 + resolution: "@types/semver@npm:7.7.0" + checksum: 10/ee4514c6c852b1c38f951239db02f9edeea39f5310fad9396a00b51efa2a2d96b3dfca1ae84c88181ea5b7157c57d32d7ef94edacee36fbf975546396b85ba5b + languageName: node + linkType: hard + "@types/send@npm:*": version: 0.17.1 resolution: "@types/send@npm:0.17.1" @@ -21656,7 +21543,7 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:*, @types/ws@npm:^8.0.0, @types/ws@npm:^8.5.10, @types/ws@npm:^8.5.3, @types/ws@npm:^8.5.4": +"@types/ws@npm:*, @types/ws@npm:^8.0.0, @types/ws@npm:^8.5.10, @types/ws@npm:^8.5.4": version: 8.18.1 resolution: "@types/ws@npm:8.18.1" dependencies: @@ -21757,6 +21644,19 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/project-service@npm:8.35.0": + version: 8.35.0 + resolution: "@typescript-eslint/project-service@npm:8.35.0" + dependencies: + "@typescript-eslint/tsconfig-utils": "npm:^8.35.0" + "@typescript-eslint/types": "npm:^8.35.0" + debug: "npm:^4.3.4" + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + checksum: 10/a9419da92231aa27f75078fcffab1d02398b50fdb7d5399775a414ba02570682b4b60cdfafb544a021b0dc2372f029c4195f5ae17c50deb11c25661b2ac18a74 + languageName: node + linkType: hard + "@typescript-eslint/scope-manager@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/scope-manager@npm:5.62.0" @@ -21787,6 +21687,15 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/tsconfig-utils@npm:8.35.0, @typescript-eslint/tsconfig-utils@npm:^8.35.0": + version: 8.35.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.35.0" + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + checksum: 10/4160928313ccbe8b169a009b9c1220826c7df7aab427f960c31f3b838931bc7a121ebee8040118481e4528e2e3cf1b26da047c6ac1d802ecff2ef7206026ea6b + languageName: node + linkType: hard + "@typescript-eslint/type-utils@npm:8.29.1": version: 8.29.1 resolution: "@typescript-eslint/type-utils@npm:8.29.1" @@ -21823,6 +21732,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:8.35.0, @typescript-eslint/types@npm:^8.35.0": + version: 8.35.0 + resolution: "@typescript-eslint/types@npm:8.35.0" + checksum: 10/34b5e6da2c59ea84cd528608fff0cc14b102fd23f5517dfee4ef38c9372861d80b5bf92445c9679674f0a4f8dc4ded5066c1bca2bc5569c47515f94568984f35 + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" @@ -21878,6 +21794,26 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/typescript-estree@npm:^8.23.0": + version: 8.35.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.35.0" + dependencies: + "@typescript-eslint/project-service": "npm:8.35.0" + "@typescript-eslint/tsconfig-utils": "npm:8.35.0" + "@typescript-eslint/types": "npm:8.35.0" + "@typescript-eslint/visitor-keys": "npm:8.35.0" + debug: "npm:^4.3.4" + fast-glob: "npm:^3.3.2" + is-glob: "npm:^4.0.3" + minimatch: "npm:^9.0.4" + semver: "npm:^7.6.0" + ts-api-utils: "npm:^2.1.0" + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + checksum: 10/4dff7c5a8853c8f4e30d35565c62d3ad5bf8445309bd465d94e9bca725853012bb9f58896a04207c30e10b6669511caac8c0f080ed781c93a3db81d5808195aa + languageName: node + linkType: hard + "@typescript-eslint/utils@npm:8.29.1, @typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.8.1": version: 8.29.1 resolution: "@typescript-eslint/utils@npm:8.29.1" @@ -21955,9 +21891,19 @@ __metadata: languageName: node linkType: hard -"@uiw/codemirror-extensions-basic-setup@npm:4.23.7": - version: 4.23.7 - resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.23.7" +"@typescript-eslint/visitor-keys@npm:8.35.0": + version: 8.35.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.35.0" + dependencies: + "@typescript-eslint/types": "npm:8.35.0" + eslint-visitor-keys: "npm:^4.2.1" + checksum: 10/c0acb13aac3a2be5e82844f7d2e86137347efdd04661dbf9fa69ef04a19dd2f1eb2f1eb6bfbfbaada78a46884308d2c0e0b5d0d1a094c84f2dfb670b67ac2b3b + languageName: node + linkType: hard + +"@uiw/codemirror-extensions-basic-setup@npm:4.24.2": + version: 4.24.2 + resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.24.2" dependencies: "@codemirror/autocomplete": "npm:^6.0.0" "@codemirror/commands": "npm:^6.0.0" @@ -21974,19 +21920,19 @@ __metadata: "@codemirror/search": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: 10/7b8cbb9ccc931bc1a6909d009e653f78797e4b851aa9fb8e11f90ae1cea233e643a1951ad4ad9e10123ef166808acee1acff24732ea9fc15cd77164f6b179b91 + checksum: 10/077f64b4bbb6178038d30a3cb2961c0ff89c0836b73210d0ac3fca828c3ba8fbce61e42635ec2e2997cdffff5472b75ad58541c4e62f98d52b83d475da0c56eb languageName: node linkType: hard "@uiw/react-codemirror@npm:^4.9.3": - version: 4.23.7 - resolution: "@uiw/react-codemirror@npm:4.23.7" + version: 4.24.2 + resolution: "@uiw/react-codemirror@npm:4.24.2" dependencies: "@babel/runtime": "npm:^7.18.6" "@codemirror/commands": "npm:^6.1.0" "@codemirror/state": "npm:^6.1.1" "@codemirror/theme-one-dark": "npm:^6.0.0" - "@uiw/codemirror-extensions-basic-setup": "npm:4.23.7" + "@uiw/codemirror-extensions-basic-setup": "npm:4.24.2" codemirror: "npm:^6.0.0" peerDependencies: "@babel/runtime": ">=7.11.0" @@ -21994,9 +21940,9 @@ __metadata: "@codemirror/theme-one-dark": ">=6.0.0" "@codemirror/view": ">=6.0.0" codemirror: ">=6.0.0" - react: ">=16.8.0" - react-dom: ">=16.8.0" - checksum: 10/dc6f1fd2f1863120e29ea331b2e279054bf508f00656d3fed97779f17818ae00b6cf9d74d3b0996800174d444b59702da74ca88c303db563daad85c0824fc952 + react: ">=17.0.0" + react-dom: ">=17.0.0" + checksum: 10/f0816fcf40c451c8bc15319d62f5b8d62a04c12c2bc053569212b31edba7c7164e45fd161ebb683a03fd5a50697b91a6ac4cab2d2d1a45da5f2626d959c399a0 languageName: node linkType: hard @@ -22017,24 +21963,24 @@ __metadata: languageName: node linkType: hard -"@useoptic/json-pointer-helpers@npm:1.0.6": - version: 1.0.6 - resolution: "@useoptic/json-pointer-helpers@npm:1.0.6" +"@useoptic/json-pointer-helpers@npm:1.0.8": + version: 1.0.8 + resolution: "@useoptic/json-pointer-helpers@npm:1.0.8" dependencies: jsonpointer: "npm:^5.0.1" minimatch: "npm:9.0.3" - checksum: 10/a4a0b2175a7a95c1c658f384d26aed2bc0e9db53aba9d44e03e4be239d3fce8b5e20b089dbb3a3bdfcd9cdf3951d46eeec662f6c47156c14ac8dc0a81463b30e + checksum: 10/075142e33ab89de448283ef2199438e84942ad285600df33d6a8f3553e4d3ab60d139c080c0dcd667f60ee607cadedeceb904739b6d7b03d99c2558413f1b223 languageName: node linkType: hard -"@useoptic/openapi-io@npm:1.0.6": - version: 1.0.6 - resolution: "@useoptic/openapi-io@npm:1.0.6" +"@useoptic/openapi-io@npm:1.0.8": + version: 1.0.8 + resolution: "@useoptic/openapi-io@npm:1.0.8" dependencies: "@apidevtools/json-schema-ref-parser": "npm:9.0.9" "@jsdevtools/ono": "npm:^7.1.3" - "@useoptic/json-pointer-helpers": "npm:1.0.6" - "@useoptic/openapi-utilities": "npm:1.0.6" + "@useoptic/json-pointer-helpers": "npm:1.0.8" + "@useoptic/openapi-utilities": "npm:1.0.8" ajv: "npm:8.17.1" ajv-errors: "npm:~3.0.0" ajv-formats: "npm:~2.1.0" @@ -22052,15 +21998,15 @@ __metadata: upath: "npm:^2.0.1" yaml: "npm:^2.3.2" yaml-ast-parser: "npm:^0.0.43" - checksum: 10/6c91f614d49840c7913a70d60bbdd9b43271765da3c109ea4efcf8191d01206e0ac3729d111bf76dfa5287a3be0a75b5f63b5c88d3c5aca97e8556ba002b6920 + checksum: 10/1d228bf7191ed8496b1ddcb5478ff051f5d7d2014e8dfa4d0e60e3d2def1284dc607e35f6ccba3b36a2d6263ce5fdea0b573bf9a7164f122036f2d54831db159 languageName: node linkType: hard -"@useoptic/openapi-utilities@npm:1.0.6": - version: 1.0.6 - resolution: "@useoptic/openapi-utilities@npm:1.0.6" +"@useoptic/openapi-utilities@npm:1.0.8": + version: 1.0.8 + resolution: "@useoptic/openapi-utilities@npm:1.0.8" dependencies: - "@useoptic/json-pointer-helpers": "npm:1.0.6" + "@useoptic/json-pointer-helpers": "npm:1.0.8" ajv: "npm:8.17.1" ajv-errors: "npm:^3.0.0" ajv-formats: "npm:^3.0.1" @@ -22077,7 +22023,7 @@ __metadata: ts-invariant: "npm:^0.9.3" url-join: "npm:^4.0.1" yaml-ast-parser: "npm:^0.0.43" - checksum: 10/e7aebd5fb1548da8d10512aaf9c54ca2e398ceaca4d3f754393082aa9f6b301823f0a0e24cf6369a12eb13dd520a9e0ad9ade1839820de06a9319c6611f3aae4 + checksum: 10/0c0b7ec345ca9b13553c096dbf39dc90e719fc4c21855134783b7c98896bd16b2bd7e19c5419a345e4e44705e928e8dafea574d20a075028ae50a5871042a5ea languageName: node linkType: hard @@ -22107,8 +22053,8 @@ __metadata: linkType: hard "@useoptic/optic@npm:^1.0.0": - version: 1.0.6 - resolution: "@useoptic/optic@npm:1.0.6" + version: 1.0.8 + resolution: "@useoptic/optic@npm:1.0.8" dependencies: "@babel/runtime": "npm:^7.20.6" "@httptoolkit/httpolyglot": "npm:^2.0.1" @@ -22118,10 +22064,10 @@ __metadata: "@sentry/node": "npm:^7.74.0" "@sinclair/typebox": "npm:0.31.28" "@stoplight/spectral-core": "npm:^1.8.1" - "@useoptic/openapi-io": "npm:1.0.6" - "@useoptic/openapi-utilities": "npm:1.0.6" - "@useoptic/rulesets-base": "npm:1.0.6" - "@useoptic/standard-rulesets": "npm:1.0.6" + "@useoptic/openapi-io": "npm:1.0.8" + "@useoptic/openapi-utilities": "npm:1.0.8" + "@useoptic/rulesets-base": "npm:1.0.8" + "@useoptic/standard-rulesets": "npm:1.0.8" ajv: "npm:8.17.1" ajv-formats: "npm:~2.1.0" async-exit-hook: "npm:^2.0.1" @@ -22178,34 +22124,34 @@ __metadata: yaml: "npm:^2.3.4" bin: optic: build/index.js - checksum: 10/16dd6b98a84eff68cefde74688c4dfc9a219d8b9d960945b4da3a893cf7113f80a05ee9a12a6dd9f907bd4c653bfc40a57c998062dda482595f23ab443c38548 + checksum: 10/7d9275697e068ffe72656bf38044d5724cc18b707e9507c65babe28f1b84017cc7be33abef4fa696fa9290e843342778d0131004aee76417fc902aa25d68038a languageName: node linkType: hard -"@useoptic/rulesets-base@npm:1.0.6": - version: 1.0.6 - resolution: "@useoptic/rulesets-base@npm:1.0.6" +"@useoptic/rulesets-base@npm:1.0.8": + version: 1.0.8 + resolution: "@useoptic/rulesets-base@npm:1.0.8" dependencies: "@stoplight/spectral-core": "npm:^1.8.1" "@stoplight/spectral-rulesets": "npm:^1.14.1" - "@useoptic/json-pointer-helpers": "npm:1.0.6" - "@useoptic/openapi-utilities": "npm:1.0.6" + "@useoptic/json-pointer-helpers": "npm:1.0.8" + "@useoptic/openapi-utilities": "npm:1.0.8" ajv: "npm:^8.6.0" lodash.pick: "npm:^4.4.0" node-fetch: "npm:^2.6.7" semver: "npm:^7.5.4" bin: rulesets-base: build/index.js - checksum: 10/ecf1844f569aa52ece6ae118f1d2df89f1b2d19f28f620bad2dfaa99d8e477d64b9b884718c5e8f62c41a7768129c54e13aae6117b3dd289d458a6edc213591a + checksum: 10/d92a2f72dca642f61ea0a4f0318b0f9e689c647b30c90896c49cfd294ab635b096906c697e57cb28486cc647154025ede0328c72dfc785dd7e873429e316a5db languageName: node linkType: hard -"@useoptic/standard-rulesets@npm:1.0.6": - version: 1.0.6 - resolution: "@useoptic/standard-rulesets@npm:1.0.6" +"@useoptic/standard-rulesets@npm:1.0.8": + version: 1.0.8 + resolution: "@useoptic/standard-rulesets@npm:1.0.8" dependencies: - "@useoptic/openapi-utilities": "npm:1.0.6" - "@useoptic/rulesets-base": "npm:1.0.6" + "@useoptic/openapi-utilities": "npm:1.0.8" + "@useoptic/rulesets-base": "npm:1.0.8" ajv: "npm:^8.6.0" ajv-draft-04: "npm:^1.0.0" ajv-formats: "npm:~2.1.0" @@ -22216,7 +22162,7 @@ __metadata: whatwg-mimetype: "npm:^3.0.0" bin: standard-rulesets: build/index.js - checksum: 10/e1e7d59f5cdd953486583cc9cb9a4a06e3821bb2ff21506a27a261c1c3173ed91f35409297a4f5fed102bd084486c36b60358dbacf3a3c45f6cb59dbf143070e + checksum: 10/63f60ac25fb8cccd0ea89097336cf5bc7ad59022ad6ef49fd382e7555f5ea9d88ca144991315eb07646aa309f5f38f3d056c4b7c869ab6b5f66fa9e93d96f4ef languageName: node linkType: hard @@ -22233,14 +22179,15 @@ __metadata: linkType: hard "@vitest/expect@npm:>1.6.0": - version: 3.0.9 - resolution: "@vitest/expect@npm:3.0.9" + version: 3.2.4 + resolution: "@vitest/expect@npm:3.2.4" dependencies: - "@vitest/spy": "npm:3.0.9" - "@vitest/utils": "npm:3.0.9" + "@types/chai": "npm:^5.2.2" + "@vitest/spy": "npm:3.2.4" + "@vitest/utils": "npm:3.2.4" chai: "npm:^5.2.0" tinyrainbow: "npm:^2.0.0" - checksum: 10/09fc02ae3a639d5db23705a393ef571001f7f1006f7527529ec7807699b739788d5b54b71cb917c56379874b006f2de49933585694927b23c0d50787f96b9e94 + checksum: 10/dc69ce886c13714dfbbff78f2d2cb7eb536017e82301a73c42d573a9e9d2bf91005ac7abd9b977adf0a3bd431209f45a8ac2418029b68b0a377e092607c843ce languageName: node linkType: hard @@ -22262,12 +22209,12 @@ __metadata: languageName: node linkType: hard -"@vitest/pretty-format@npm:3.0.9": - version: 3.0.9 - resolution: "@vitest/pretty-format@npm:3.0.9" +"@vitest/pretty-format@npm:3.2.4": + version: 3.2.4 + resolution: "@vitest/pretty-format@npm:3.2.4" dependencies: tinyrainbow: "npm:^2.0.0" - checksum: 10/cfcdda2c72cf16a5e76ad2c9b014a4e36fea3988389613497cad5a2491ebc380ded4397afc95c32a2bd2734b0386996df76f6c5cbfc6be561262b8d112fb7a27 + checksum: 10/8dd30cbf956e01fbab042fe651fb5175d9f0cd00b7b569a46cd98df89c4fec47dab12916201ad6e09a4f25f2a2ec8927a4bfdc61118593097f759c90b18a51d4 languageName: node linkType: hard @@ -22280,12 +22227,12 @@ __metadata: languageName: node linkType: hard -"@vitest/spy@npm:3.0.9": - version: 3.0.9 - resolution: "@vitest/spy@npm:3.0.9" +"@vitest/spy@npm:3.2.4": + version: 3.2.4 + resolution: "@vitest/spy@npm:3.2.4" dependencies: - tinyspy: "npm:^3.0.2" - checksum: 10/967b403293c9325292be4843753bf8ae516ec158df2372a14bec98c9bfb233fa6bbf76cb319cf1a9ea1b5ab795e3abff68ca66fa7523045562d7449a95ed8bf9 + tinyspy: "npm:^4.0.3" + checksum: 10/7d38c299f42a8c7e5e41652b203af98ca54e63df69c3b072d0e401d5a57fbbba3e39d8538ac1b3022c26718a6388d0bcc222bc2f07faab75942543b9247c007d languageName: node linkType: hard @@ -22301,14 +22248,14 @@ __metadata: languageName: node linkType: hard -"@vitest/utils@npm:3.0.9": - version: 3.0.9 - resolution: "@vitest/utils@npm:3.0.9" +"@vitest/utils@npm:3.2.4": + version: 3.2.4 + resolution: "@vitest/utils@npm:3.2.4" dependencies: - "@vitest/pretty-format": "npm:3.0.9" - loupe: "npm:^3.1.3" + "@vitest/pretty-format": "npm:3.2.4" + loupe: "npm:^3.1.4" tinyrainbow: "npm:^2.0.0" - checksum: 10/c77e2a4a5c62dabc57c0d27536428e6b4f9a7998b59161deb82cf797e1d6cb61a7531bef19f079c4bdca7b48fd656b48e4d1bcfb4a5bdf3c177931670a287163 + checksum: 10/7f12ef63bd8ee13957744d1f336b0405f164ade4358bf9dfa531f75bbb58ffac02bf61aba65724311ddbc50b12ba54853a169e59c6b837c16086173b9a480710 languageName: node linkType: hard @@ -22323,154 +22270,211 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.12.1": - version: 1.12.1 - resolution: "@webassemblyjs/ast@npm:1.12.1" +"@vue/compiler-core@npm:3.5.17": + version: 3.5.17 + resolution: "@vue/compiler-core@npm:3.5.17" dependencies: - "@webassemblyjs/helper-numbers": "npm:1.11.6" - "@webassemblyjs/helper-wasm-bytecode": "npm:1.11.6" - checksum: 10/a775b0559437ae122d14fec0cfe59fdcaf5ca2d8ff48254014fd05d6797e20401e0f1518e628f9b06819aa085834a2534234977f9608b3f2e51f94b6e8b0bc43 + "@babel/parser": "npm:^7.27.5" + "@vue/shared": "npm:3.5.17" + entities: "npm:^4.5.0" + estree-walker: "npm:^2.0.2" + source-map-js: "npm:^1.2.1" + checksum: 10/b7900f0498cc1f0496e003e0e783ae09ad3fde25258ac635372b704533a14105a04d690b97d196ded5f2cf04ced078a59a53533242acfdaa4508432296372f54 languageName: node linkType: hard -"@webassemblyjs/floating-point-hex-parser@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.11.6" - checksum: 10/29b08758841fd8b299c7152eda36b9eb4921e9c584eb4594437b5cd90ed6b920523606eae7316175f89c20628da14326801090167cc7fbffc77af448ac84b7e2 - languageName: node - linkType: hard - -"@webassemblyjs/helper-api-error@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/helper-api-error@npm:1.11.6" - checksum: 10/e8563df85161096343008f9161adb138a6e8f3c2cc338d6a36011aa55eabb32f2fd138ffe63bc278d009ada001cc41d263dadd1c0be01be6c2ed99076103689f - languageName: node - linkType: hard - -"@webassemblyjs/helper-buffer@npm:1.12.1": - version: 1.12.1 - resolution: "@webassemblyjs/helper-buffer@npm:1.12.1" - checksum: 10/1d8705daa41f4d22ef7c6d422af4c530b84d69d0c253c6db5adec44d511d7caa66837803db5b1addcea611a1498fd5a67d2cf318b057a916283ae41ffb85ba8a - languageName: node - linkType: hard - -"@webassemblyjs/helper-numbers@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/helper-numbers@npm:1.11.6" +"@vue/compiler-dom@npm:3.5.17": + version: 3.5.17 + resolution: "@vue/compiler-dom@npm:3.5.17" dependencies: - "@webassemblyjs/floating-point-hex-parser": "npm:1.11.6" - "@webassemblyjs/helper-api-error": "npm:1.11.6" + "@vue/compiler-core": "npm:3.5.17" + "@vue/shared": "npm:3.5.17" + checksum: 10/4370ce4c31c578ec864e25eddbeac23b9498036912a82ba26545346dde4cfc378624bda2865db5104fd6a57321de6f527525af77cd5f83f7d1cd67b00bf1c6de + languageName: node + linkType: hard + +"@vue/compiler-sfc@npm:^3.5.13": + version: 3.5.17 + resolution: "@vue/compiler-sfc@npm:3.5.17" + dependencies: + "@babel/parser": "npm:^7.27.5" + "@vue/compiler-core": "npm:3.5.17" + "@vue/compiler-dom": "npm:3.5.17" + "@vue/compiler-ssr": "npm:3.5.17" + "@vue/shared": "npm:3.5.17" + estree-walker: "npm:^2.0.2" + magic-string: "npm:^0.30.17" + postcss: "npm:^8.5.6" + source-map-js: "npm:^1.2.1" + checksum: 10/32a0e611615e1a9f553a3744084d15b388a48ec4218d1159ab5bf57897e38779fe408fc4e63ed43c134134e301d6a32a812f4e3f58a9c7e0fddba9d96f86a537 + languageName: node + linkType: hard + +"@vue/compiler-ssr@npm:3.5.17": + version: 3.5.17 + resolution: "@vue/compiler-ssr@npm:3.5.17" + dependencies: + "@vue/compiler-dom": "npm:3.5.17" + "@vue/shared": "npm:3.5.17" + checksum: 10/ed92e187d7fd29350c6a267a0ca8989f626b1b90e46ab2dbf9492a7164c4b32a7fd7fb5001dd43de0200c5f4cd3b3152d5cd4f55e39fe6f9837a30ce944876b1 + languageName: node + linkType: hard + +"@vue/shared@npm:3.5.17": + version: 3.5.17 + resolution: "@vue/shared@npm:3.5.17" + checksum: 10/d33b7435df4af16586679e713a0c15abb8c79a49067f558cbea6645d69f64ee2fd5b669a834abce2a084cb24a0d35a4a20164ba54625fbab70b3cc06b6913f72 + languageName: node + linkType: hard + +"@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.12.1, @webassemblyjs/ast@npm:^1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/ast@npm:1.14.1" + dependencies: + "@webassemblyjs/helper-numbers": "npm:1.13.2" + "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" + checksum: 10/f83e6abe38057f5d87c1fb356513a371a8b43c9b87657f2790741a66b1ef8ecf958d1391bc42f27c5fb33f58ab8286a38ea849fdd21f433cd4df1307424bab45 + languageName: node + linkType: hard + +"@webassemblyjs/floating-point-hex-parser@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.13.2" + checksum: 10/e866ec8433f4a70baa511df5e8f2ebcd6c24f4e2cc6274c7c5aabe2bcce3459ea4680e0f35d450e1f3602acf3913b6b8e4f15069c8cfd34ae8609fb9a7d01795 + languageName: node + linkType: hard + +"@webassemblyjs/helper-api-error@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/helper-api-error@npm:1.13.2" + checksum: 10/48b5df7fd3095bb252f59a139fe2cbd999a62ac9b488123e9a0da3906ad8a2f2da7b2eb21d328c01a90da987380928706395c2897d1f3ed9e2125b6d75a920d0 + languageName: node + linkType: hard + +"@webassemblyjs/helper-buffer@npm:1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/helper-buffer@npm:1.14.1" + checksum: 10/9690afeafa5e765a34620aa6216e9d40f9126d4e37e9726a2594bf60cab6b211ef20ab6670fd3c4449dd4a3497e69e49b2b725c8da0fb213208c7f45f15f5d5b + languageName: node + linkType: hard + +"@webassemblyjs/helper-numbers@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/helper-numbers@npm:1.13.2" + dependencies: + "@webassemblyjs/floating-point-hex-parser": "npm:1.13.2" + "@webassemblyjs/helper-api-error": "npm:1.13.2" "@xtuc/long": "npm:4.2.2" - checksum: 10/9ffd258ad809402688a490fdef1fd02222f20cdfe191c895ac215a331343292164e5033dbc0347f0f76f2447865c0b5c2d2e3304ee948d44f7aa27857028fd08 + checksum: 10/e4c7d0b09811e1cda8eec644a022b560b28f4e974f50195375ccd007df5ee48a922a6dcff5ac40b6a8ec850d56d0ea6419318eee49fec7819ede14e90417a6a4 languageName: node linkType: hard -"@webassemblyjs/helper-wasm-bytecode@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.11.6" - checksum: 10/4ebf03e9c1941288c10e94e0f813f413f972bfaa1f09be2cc2e5577f300430906b61aa24d52f5ef2f894e8e24e61c6f7c39871d7e3d98bc69460e1b8e00bb20b +"@webassemblyjs/helper-wasm-bytecode@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.13.2" + checksum: 10/3edd191fff7296df1ef3b023bdbe6cb5ea668f6386fd197ccfce46015c6f2a8cc9763cfb86503a0b94973ad27996645afff2252ee39a236513833259a47af6ed languageName: node linkType: hard -"@webassemblyjs/helper-wasm-section@npm:1.12.1": - version: 1.12.1 - resolution: "@webassemblyjs/helper-wasm-section@npm:1.12.1" +"@webassemblyjs/helper-wasm-section@npm:1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/helper-wasm-section@npm:1.14.1" dependencies: - "@webassemblyjs/ast": "npm:1.12.1" - "@webassemblyjs/helper-buffer": "npm:1.12.1" - "@webassemblyjs/helper-wasm-bytecode": "npm:1.11.6" - "@webassemblyjs/wasm-gen": "npm:1.12.1" - checksum: 10/e91e6b28114e35321934070a2db8973a08a5cd9c30500b817214c683bbf5269ed4324366dd93ad83bf2fba0d671ac8f39df1c142bf58f70c57a827eeba4a3d2f + "@webassemblyjs/ast": "npm:1.14.1" + "@webassemblyjs/helper-buffer": "npm:1.14.1" + "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" + "@webassemblyjs/wasm-gen": "npm:1.14.1" + checksum: 10/6b73874f906532512371181d7088460f767966f26309e836060c5a8e4e4bfe6d523fb5f4c034b34aa22ebb1192815f95f0e264298769485c1f0980fdd63ae0ce languageName: node linkType: hard -"@webassemblyjs/ieee754@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/ieee754@npm:1.11.6" +"@webassemblyjs/ieee754@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/ieee754@npm:1.13.2" dependencies: "@xtuc/ieee754": "npm:^1.2.0" - checksum: 10/13574b8e41f6ca39b700e292d7edf102577db5650fe8add7066a320aa4b7a7c09a5056feccac7a74eb68c10dea9546d4461412af351f13f6b24b5f32379b49de + checksum: 10/d7e3520baa37a7309fa7db4d73d69fb869878853b1ebd4b168821bd03fcc4c0e1669c06231315b0039035d9a7a462e53de3ad982da4a426a4b0743b5888e8673 languageName: node linkType: hard -"@webassemblyjs/leb128@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/leb128@npm:1.11.6" +"@webassemblyjs/leb128@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/leb128@npm:1.13.2" dependencies: "@xtuc/long": "npm:4.2.2" - checksum: 10/ec3b72db0e7ce7908fe08ec24395bfc97db486063824c0edc580f0973a4cfbadf30529569d9c7db663a56513e45b94299cca03be9e1992ea3308bb0744164f3d + checksum: 10/3a10542c86807061ec3230bac8ee732289c852b6bceb4b88ebd521a12fbcecec7c432848284b298154f28619e2746efbed19d6904aef06c49ef20a0b85f650cf languageName: node linkType: hard -"@webassemblyjs/utf8@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/utf8@npm:1.11.6" - checksum: 10/361a537bd604101b320a5604c3c96d1038d83166f1b9fb86cedadc7e81bae54c3785ae5d90bf5b1842f7da08194ccaf0f44a64fcca0cbbd6afe1a166196986d6 +"@webassemblyjs/utf8@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/utf8@npm:1.13.2" + checksum: 10/27885e5d19f339501feb210867d69613f281eda695ac508f04d69fa3398133d05b6870969c0242b054dc05420ed1cc49a64dea4fe0588c18d211cddb0117cc54 languageName: node linkType: hard -"@webassemblyjs/wasm-edit@npm:^1.12.1": - version: 1.12.1 - resolution: "@webassemblyjs/wasm-edit@npm:1.12.1" +"@webassemblyjs/wasm-edit@npm:^1.12.1, @webassemblyjs/wasm-edit@npm:^1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/wasm-edit@npm:1.14.1" dependencies: - "@webassemblyjs/ast": "npm:1.12.1" - "@webassemblyjs/helper-buffer": "npm:1.12.1" - "@webassemblyjs/helper-wasm-bytecode": "npm:1.11.6" - "@webassemblyjs/helper-wasm-section": "npm:1.12.1" - "@webassemblyjs/wasm-gen": "npm:1.12.1" - "@webassemblyjs/wasm-opt": "npm:1.12.1" - "@webassemblyjs/wasm-parser": "npm:1.12.1" - "@webassemblyjs/wast-printer": "npm:1.12.1" - checksum: 10/5678ae02dbebba2f3a344e25928ea5a26a0df777166c9be77a467bfde7aca7f4b57ef95587e4bd768a402cdf2fddc4c56f0a599d164cdd9fe313520e39e18137 + "@webassemblyjs/ast": "npm:1.14.1" + "@webassemblyjs/helper-buffer": "npm:1.14.1" + "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" + "@webassemblyjs/helper-wasm-section": "npm:1.14.1" + "@webassemblyjs/wasm-gen": "npm:1.14.1" + "@webassemblyjs/wasm-opt": "npm:1.14.1" + "@webassemblyjs/wasm-parser": "npm:1.14.1" + "@webassemblyjs/wast-printer": "npm:1.14.1" + checksum: 10/c62c50eadcf80876713f8c9f24106b18cf208160ab842fcb92060fd78c37bf37e7fcf0b7cbf1afc05d230277c2ce0f3f728432082c472dd1293e184a95f9dbdd languageName: node linkType: hard -"@webassemblyjs/wasm-gen@npm:1.12.1": - version: 1.12.1 - resolution: "@webassemblyjs/wasm-gen@npm:1.12.1" +"@webassemblyjs/wasm-gen@npm:1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/wasm-gen@npm:1.14.1" dependencies: - "@webassemblyjs/ast": "npm:1.12.1" - "@webassemblyjs/helper-wasm-bytecode": "npm:1.11.6" - "@webassemblyjs/ieee754": "npm:1.11.6" - "@webassemblyjs/leb128": "npm:1.11.6" - "@webassemblyjs/utf8": "npm:1.11.6" - checksum: 10/ec45bd50e86bc9856f80fe9af4bc1ae5c98fb85f57023d11dff2b670da240c47a7b1b9b6c89755890314212bd167cf3adae7f1157216ddffb739a4ce589fc338 + "@webassemblyjs/ast": "npm:1.14.1" + "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" + "@webassemblyjs/ieee754": "npm:1.13.2" + "@webassemblyjs/leb128": "npm:1.13.2" + "@webassemblyjs/utf8": "npm:1.13.2" + checksum: 10/6085166b0987d3031355fe17a4f9ef0f412e08098d95454059aced2bd72a4c3df2bc099fa4d32d640551fc3eca1ac1a997b44432e46dc9d84642688e42c17ed4 languageName: node linkType: hard -"@webassemblyjs/wasm-opt@npm:1.12.1": - version: 1.12.1 - resolution: "@webassemblyjs/wasm-opt@npm:1.12.1" +"@webassemblyjs/wasm-opt@npm:1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/wasm-opt@npm:1.14.1" dependencies: - "@webassemblyjs/ast": "npm:1.12.1" - "@webassemblyjs/helper-buffer": "npm:1.12.1" - "@webassemblyjs/wasm-gen": "npm:1.12.1" - "@webassemblyjs/wasm-parser": "npm:1.12.1" - checksum: 10/21f25ae109012c49bb084e09f3b67679510429adc3e2408ad3621b2b505379d9cce337799a7919ef44db64e0d136833216914aea16b0d4856f353b9778e0cdb7 + "@webassemblyjs/ast": "npm:1.14.1" + "@webassemblyjs/helper-buffer": "npm:1.14.1" + "@webassemblyjs/wasm-gen": "npm:1.14.1" + "@webassemblyjs/wasm-parser": "npm:1.14.1" + checksum: 10/fa5d1ef8d2156e7390927f938f513b7fb4440dd6804b3d6c8622b7b1cf25a3abf1a5809f615896d4918e04b27b52bc3cbcf18faf2d563cb563ae0a9204a492db languageName: node linkType: hard -"@webassemblyjs/wasm-parser@npm:1.12.1, @webassemblyjs/wasm-parser@npm:^1.12.1": - version: 1.12.1 - resolution: "@webassemblyjs/wasm-parser@npm:1.12.1" +"@webassemblyjs/wasm-parser@npm:1.14.1, @webassemblyjs/wasm-parser@npm:^1.12.1, @webassemblyjs/wasm-parser@npm:^1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/wasm-parser@npm:1.14.1" dependencies: - "@webassemblyjs/ast": "npm:1.12.1" - "@webassemblyjs/helper-api-error": "npm:1.11.6" - "@webassemblyjs/helper-wasm-bytecode": "npm:1.11.6" - "@webassemblyjs/ieee754": "npm:1.11.6" - "@webassemblyjs/leb128": "npm:1.11.6" - "@webassemblyjs/utf8": "npm:1.11.6" - checksum: 10/f7311685b76c3e1def2abea3488be1e77f06ecd8633143a6c5c943ca289660952b73785231bb76a010055ca64645227a4bc79705c26ab7536216891b6bb36320 + "@webassemblyjs/ast": "npm:1.14.1" + "@webassemblyjs/helper-api-error": "npm:1.13.2" + "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" + "@webassemblyjs/ieee754": "npm:1.13.2" + "@webassemblyjs/leb128": "npm:1.13.2" + "@webassemblyjs/utf8": "npm:1.13.2" + checksum: 10/07d9805fda88a893c984ed93d5a772d20d671e9731358ab61c6c1af8e0e58d1c42fc230c18974dfddebc9d2dd7775d514ba4d445e70080b16478b4b16c39c7d9 languageName: node linkType: hard -"@webassemblyjs/wast-printer@npm:1.12.1": - version: 1.12.1 - resolution: "@webassemblyjs/wast-printer@npm:1.12.1" +"@webassemblyjs/wast-printer@npm:1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/wast-printer@npm:1.14.1" dependencies: - "@webassemblyjs/ast": "npm:1.12.1" + "@webassemblyjs/ast": "npm:1.14.1" "@xtuc/long": "npm:4.2.2" - checksum: 10/1a6a4b6bc4234f2b5adbab0cb11a24911b03380eb1cab6fb27a2250174a279fdc6aa2f5a9cf62dd1f6d4eb39f778f488e8ff15b9deb0670dee5c5077d46cf572 + checksum: 10/cef09aad2fcd291bfcf9efdae2ea1e961a1ba0f925d1d9dcdd8c746d32fbaf431b6d26a0241699c0e39f82139018aa720b4ceb84ac6f4c78f13072747480db69 languageName: node linkType: hard @@ -22534,6 +22538,31 @@ __metadata: languageName: node linkType: hard +"@xterm/addon-attach@npm:^0.11.0": + version: 0.11.0 + resolution: "@xterm/addon-attach@npm:0.11.0" + peerDependencies: + "@xterm/xterm": ^5.0.0 + checksum: 10/f11e7ba68b8b1064ce545ecbf2beeaf3ebc94622fa3ca77e0ca0b8e8834964f634ee3897c031376abc7242ddfd305a40b10aa7b3e8056bfa59055967251523b5 + languageName: node + linkType: hard + +"@xterm/addon-fit@npm:^0.10.0": + version: 0.10.0 + resolution: "@xterm/addon-fit@npm:0.10.0" + peerDependencies: + "@xterm/xterm": ^5.0.0 + checksum: 10/8edfad561c0d0316c5883cbe2ce56109f105a2b2bf53b71d5f8c788e656a3205c1093a659dddcf4025a459e4b7ff8e07b6c6a19815c8711deeded560de5f1893 + languageName: node + linkType: hard + +"@xterm/xterm@npm:^5.5.0": + version: 5.5.0 + resolution: "@xterm/xterm@npm:5.5.0" + checksum: 10/d4cdc402de81a83a3e0ef93f38072cb8f54abe4d65865f2e29b92cbc2593f86d052f6b993895c9e5dec97f47548f504e90bcea0aad6845917c09b03f2f3a4629 + languageName: node + linkType: hard + "@xtuc/ieee754@npm:^1.2.0": version: 1.2.0 resolution: "@xtuc/ieee754@npm:1.2.0" @@ -22649,7 +22678,7 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/core@npm:^4.2.1, @yarnpkg/core@npm:^4.4.0": +"@yarnpkg/core@npm:^4.2.1, @yarnpkg/core@npm:^4.4.0, @yarnpkg/core@npm:^4.4.1": version: 4.4.1 resolution: "@yarnpkg/core@npm:4.4.1" dependencies: @@ -23333,6 +23362,16 @@ __metadata: languageName: node linkType: hard +"accepts@npm:^2.0.0": + version: 2.0.0 + resolution: "accepts@npm:2.0.0" + dependencies: + mime-types: "npm:^3.0.0" + negotiator: "npm:^1.0.0" + checksum: 10/ea1343992b40b2bfb3a3113fa9c3c2f918ba0f9197ae565c48d3f84d44b174f6b1d5cd9989decd7655963eb03a272abc36968cc439c2907f999bd5ef8653d5a7 + languageName: node + linkType: hard + "acorn-globals@npm:^6.0.0": version: 6.0.0 resolution: "acorn-globals@npm:6.0.0" @@ -23352,6 +23391,15 @@ __metadata: languageName: node linkType: hard +"acorn-import-phases@npm:^1.0.3": + version: 1.0.4 + resolution: "acorn-import-phases@npm:1.0.4" + peerDependencies: + acorn: ^8.14.0 + checksum: 10/471050ac7d9b61909c837b426de9eeef2958997f6277ad7dea88d5894fd9b3245d8ed4a225c2ca44f814dbb20688009db7a80e525e8196fc9e98c5285b66161d + languageName: node + linkType: hard + "acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -23384,12 +23432,12 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.14.0, acorn@npm:^8.4.1, acorn@npm:^8.7.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": - version: 8.14.0 - resolution: "acorn@npm:8.14.0" +"acorn@npm:^8.14.0, acorn@npm:^8.15.0, acorn@npm:^8.4.1, acorn@npm:^8.7.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": + version: 8.15.0 + resolution: "acorn@npm:8.15.0" bin: acorn: bin/acorn - checksum: 10/6df29c35556782ca9e632db461a7f97947772c6c1d5438a81f0c873a3da3a792487e83e404d1c6c25f70513e91aa18745f6eafb1fcc3a43ecd1920b21dd173d2 + checksum: 10/77f2de5051a631cf1729c090e5759148459cdb76b5f5c70f890503d629cf5052357b0ce783c0f976dd8a93c5150f59f6d18df1def3f502396a20f81282482fa4 languageName: node linkType: hard @@ -23416,7 +23464,7 @@ __metadata: languageName: node linkType: hard -"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": version: 7.1.3 resolution: "agent-base@npm:7.1.3" checksum: 10/3db6d8d4651f2aa1a9e4af35b96ab11a7607af57a24f3bc721a387eaa3b5f674e901f0a648b0caefd48f3fd117c7761b79a3b55854e2aebaa96c3f32cf76af84 @@ -23548,7 +23596,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.12.2, ajv@npm:^6.12.4, ajv@npm:^6.12.5, ajv@npm:^6.5.5": +"ajv@npm:^6.12.2, ajv@npm:^6.12.4, ajv@npm:^6.12.5, ajv@npm:^6.12.6": version: 6.12.6 resolution: "ajv@npm:6.12.6" dependencies: @@ -23648,7 +23696,7 @@ __metadata: languageName: node linkType: hard -"ansi-html-community@npm:0.0.8, ansi-html-community@npm:^0.0.8": +"ansi-html-community@npm:^0.0.8": version: 0.0.8 resolution: "ansi-html-community@npm:0.0.8" bin: @@ -23743,6 +23791,13 @@ __metadata: languageName: node linkType: hard +"app-module-path@npm:^2.2.0": + version: 2.2.0 + resolution: "app-module-path@npm:2.2.0" + checksum: 10/9ed8c6ce6247a6b5d556039f29b4610869237bbb5b8f3d905b22bd2d314c30efcc0fb70c2626d7461ecc52ec7edec9908f660d0938d2bea5b8cfc6868a28806f + languageName: node + linkType: hard + "app-next-example-plugin@workspace:packages/app-next-example-plugin": version: 0.0.0-use.local resolution: "app-next-example-plugin@workspace:packages/app-next-example-plugin" @@ -24071,7 +24126,7 @@ __metadata: languageName: node linkType: hard -"asn1@npm:^0.2.4, asn1@npm:^0.2.6, asn1@npm:~0.2.3": +"asn1@npm:^0.2.4, asn1@npm:^0.2.6": version: 0.2.6 resolution: "asn1@npm:0.2.6" dependencies: @@ -24080,7 +24135,7 @@ __metadata: languageName: node linkType: hard -"assert-plus@npm:1.0.0, assert-plus@npm:^1.0.0": +"assert-plus@npm:^1.0.0": version: 1.0.0 resolution: "assert-plus@npm:1.0.0" checksum: 10/f4f991ae2df849cc678b1afba52d512a7cbf0d09613ba111e72255409ff9158550c775162a47b12d015d1b82b3c273e8e25df0e4783d3ddb008a293486d00a07 @@ -24107,6 +24162,13 @@ __metadata: languageName: node linkType: hard +"ast-module-types@npm:^6.0.1": + version: 6.0.1 + resolution: "ast-module-types@npm:6.0.1" + checksum: 10/d3a2e0ae998470c7c3d92e2bb12dfcf2797bca17d2548f60b57ed34638d2278f5f99140df009ab3cd73dbe15885b660fe780fc1c14c249f5c50b15a6e014740c + languageName: node + linkType: hard + "ast-types-flow@npm:^0.0.8": version: 0.0.8 resolution: "ast-types-flow@npm:0.0.8" @@ -24312,13 +24374,6 @@ __metadata: languageName: node linkType: hard -"aws-sign2@npm:~0.7.0": - version: 0.7.0 - resolution: "aws-sign2@npm:0.7.0" - checksum: 10/2ac497d739f71be3264cf096a33ab256a1fea7fe80b87dc51ec29374505bd5a661279ef1c22989d68528ea61ed634021ca63b31cf1d3c2a3682ffc106f7d0e96 - languageName: node - linkType: hard - "aws-ssl-profiles@npm:^1.1.1": version: 1.1.1 resolution: "aws-ssl-profiles@npm:1.1.1" @@ -24326,7 +24381,7 @@ __metadata: languageName: node linkType: hard -"aws4@npm:^1.11.0, aws4@npm:^1.12.0, aws4@npm:^1.8.0": +"aws4@npm:^1.11.0, aws4@npm:^1.12.0": version: 1.13.2 resolution: "aws4@npm:1.13.2" checksum: 10/290b9f84facbad013747725bfd8b4c42d0b3b04b5620d8418f0219832ef95a7dc597a4af7b1589ae7fce18bacde96f40911c3cda36199dd04d9f8e01f72fa50a @@ -24361,18 +24416,7 @@ __metadata: languageName: node linkType: hard -"axios@npm:1.7.7": - version: 1.7.7 - resolution: "axios@npm:1.7.7" - dependencies: - follow-redirects: "npm:^1.15.6" - form-data: "npm:^4.0.0" - proxy-from-env: "npm:^1.1.0" - checksum: 10/7f875ea13b9298cd7b40fd09985209f7a38d38321f1118c701520939de2f113c4ba137832fe8e3f811f99a38e12c8225481011023209a77b0c0641270e20cde1 - languageName: node - linkType: hard - -"axios@npm:^1.0.0, axios@npm:^1.6.0, axios@npm:^1.7.4, axios@npm:^1.7.7, axios@npm:^1.7.8": +"axios@npm:1.9.0": version: 1.9.0 resolution: "axios@npm:1.9.0" dependencies: @@ -24383,6 +24427,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.0.0, axios@npm:^1.11.0, axios@npm:^1.6.0, axios@npm:^1.7.4, axios@npm:^1.8.3": + version: 1.11.0 + resolution: "axios@npm:1.11.0" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.4" + proxy-from-env: "npm:^1.1.0" + checksum: 10/232df4af7a4e4e07baa84621b9cc4b0c518a757b4eacc7f635c0eb3642cb98dff347326739f24b891b3b4481b7b838c79a3a0c4819c9fbc1fc40232431b9c5dc + languageName: node + linkType: hard + "axobject-query@npm:^4.1.0": version: 4.1.0 resolution: "axobject-query@npm:4.1.0" @@ -24460,42 +24515,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs2@npm:^0.4.5": - version: 0.4.5 - resolution: "babel-plugin-polyfill-corejs2@npm:0.4.5" - dependencies: - "@babel/compat-data": "npm:^7.22.6" - "@babel/helper-define-polyfill-provider": "npm:^0.4.2" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/75552d49f7d874e2e9a082d19e3ce9cc95998abadbdc589e5af7de64f5088059863eb194989cfcfefc99623925c46e273bd49333f6aae58f6fff59696279132b - languageName: node - linkType: hard - -"babel-plugin-polyfill-corejs3@npm:^0.8.3": - version: 0.8.3 - resolution: "babel-plugin-polyfill-corejs3@npm:0.8.3" - dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.4.2" - core-js-compat: "npm:^3.31.0" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/95e57300341c52b4954b8c8d9d7dd6f9a5bd26f3ac6f67180f146398e5ea5ec5a8496a79d222e147a3e61b698ce4176677a194397ac9887bfa8072d2d7e4e29c - languageName: node - linkType: hard - -"babel-plugin-polyfill-regenerator@npm:^0.5.2": - version: 0.5.2 - resolution: "babel-plugin-polyfill-regenerator@npm:0.5.2" - dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.4.2" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/d962200f604016a9a09bc9b4aaf60a3db7af876bb65bcefaeac04d44ac9d9ec4037cf24ce117760cc141d7046b6394c7eb0320ba9665cb4a2ee64df2be187c93 - languageName: node - linkType: hard - "babel-plugin-styled-components@npm:>= 1.12.0": version: 2.0.7 resolution: "babel-plugin-styled-components@npm:2.0.7" @@ -24696,7 +24715,7 @@ __metadata: languageName: node linkType: hard -"bcrypt-pbkdf@npm:^1.0.0, bcrypt-pbkdf@npm:^1.0.2": +"bcrypt-pbkdf@npm:^1.0.2": version: 1.0.2 resolution: "bcrypt-pbkdf@npm:1.0.2" dependencies: @@ -24730,14 +24749,14 @@ __metadata: languageName: node linkType: hard -"better-sqlite3@npm:^11.0.0": - version: 11.7.2 - resolution: "better-sqlite3@npm:11.7.2" +"better-sqlite3@npm:^12.0.0": + version: 12.2.0 + resolution: "better-sqlite3@npm:12.2.0" dependencies: bindings: "npm:^1.5.0" node-gyp: "npm:latest" prebuild-install: "npm:^7.1.1" - checksum: 10/49e1d8847560058d00c87177dfa6ef6bf713cb0122795c8f43143ac3154608f2ff83d4180ab00c7c24f5c23f0bcfe96bb47ac08b3ef5081ed41f46869df594f6 + checksum: 10/2662e321237624d5813a7b97528dfc11fc5110b068d57f4096de82d7136e7e4a4b722a2a4266ddfb5095070712fd3d2542f6212ccb68e2d3eb2042498269e1cf languageName: node linkType: hard @@ -24891,6 +24910,23 @@ __metadata: languageName: node linkType: hard +"body-parser@npm:^2.2.0": + version: 2.2.0 + resolution: "body-parser@npm:2.2.0" + dependencies: + bytes: "npm:^3.1.2" + content-type: "npm:^1.0.5" + debug: "npm:^4.4.0" + http-errors: "npm:^2.0.0" + iconv-lite: "npm:^0.6.3" + on-finished: "npm:^2.4.1" + qs: "npm:^6.14.0" + raw-body: "npm:^3.0.0" + type-is: "npm:^2.0.0" + checksum: 10/e9d844b036bd15970df00a16f373c7ed28e1ef870974a0a1d4d6ef60d70e01087cc20a0dbb2081c49a88e3c08ce1d87caf1e2898c615dffa193f63e8faa8a84e + languageName: node + linkType: hard + "bonjour-service@npm:^1.2.1": version: 1.2.1 resolution: "bonjour-service@npm:1.2.1" @@ -24946,12 +24982,12 @@ __metadata: linkType: hard "brace-expansion@npm:^1.1.7": - version: 1.1.11 - resolution: "brace-expansion@npm:1.1.11" + version: 1.1.12 + resolution: "brace-expansion@npm:1.1.12" dependencies: balanced-match: "npm:^1.0.0" concat-map: "npm:0.0.1" - checksum: 10/faf34a7bb0c3fcf4b59c7808bc5d2a96a40988addf2e7e09dfbb67a2251800e0d14cd2bfc1aa79174f2f5095c54ff27f46fb1289fe2d77dac755b5eb3434cc07 + checksum: 10/12cb6d6310629e3048cadb003e1aca4d8c9bb5c67c3c321bafdd7e7a50155de081f78ea3e0ed92ecc75a9015e784f301efc8132383132f4f7904ad1ac529c562 languageName: node linkType: hard @@ -25098,7 +25134,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.0.0, browserslist@npm:^4.16.0, browserslist@npm:^4.16.6, browserslist@npm:^4.18.1, browserslist@npm:^4.21.10, browserslist@npm:^4.24.0": +"browserslist@npm:^4.0.0, browserslist@npm:^4.16.0, browserslist@npm:^4.16.6, browserslist@npm:^4.18.1, browserslist@npm:^4.24.0": version: 4.24.2 resolution: "browserslist@npm:4.24.2" dependencies: @@ -25232,7 +25268,7 @@ __metadata: languageName: node linkType: hard -"busboy@npm:^1.0.0, busboy@npm:^1.6.0": +"busboy@npm:^1.6.0": version: 1.6.0 resolution: "busboy@npm:1.6.0" dependencies: @@ -25255,7 +25291,7 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2": +"bytes@npm:3.1.2, bytes@npm:^3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" checksum: 10/a10abf2ba70c784471d6b4f58778c0beeb2b5d405148e66affa91f23a9f13d07603d0a0354667310ae1d6dc141474ffd44e2a074be0f6e2254edb8fc21445388 @@ -25519,24 +25555,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001669, caniuse-lite@npm:^1.0.30001715": - version: 1.0.30001715 - resolution: "caniuse-lite@npm:1.0.30001715" - checksum: 10/5608cdaf609eb5fe3a86ab6c1c2f3943dbdab813041725f4747f5432b05e6e19fc606faa8a9b75c329b37b772c91c47e8db483e76a6b715b59c289ce53dcba68 - languageName: node - linkType: hard - -"case-sensitive-paths-webpack-plugin@npm:^2.4.0": - version: 2.4.0 - resolution: "case-sensitive-paths-webpack-plugin@npm:2.4.0" - checksum: 10/8187f4a6d9c1342a62e76466d4f2ed53e6c0ea73fdbf7779751538f2abe49738bfd16b43592367f00f37fdd593accf92162c1043c016dd6d9ccb55180b6b5fa7 - languageName: node - linkType: hard - -"caseless@npm:~0.12.0": - version: 0.12.0 - resolution: "caseless@npm:0.12.0" - checksum: 10/ea1efdf430975fdbac3505cdd21007f7ac5aa29b6d4d1c091f965853cd1bf87e4b08ea07b31a6d688b038872b7cdf0589d9262d59c699d199585daad052aeb20 +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001669": + version: 1.0.30001717 + resolution: "caniuse-lite@npm:1.0.30001717" + checksum: 10/e47dfd8707ea305baa177f3d3d531df614f5a9ac6335363fc8f86f0be4caf79f5734f3f68b601fee4edd9d79f1e5ffc0931466bb894bf955ed6b1dd5a1c34b1d languageName: node linkType: hard @@ -25548,15 +25570,15 @@ __metadata: linkType: hard "chai@npm:^5.1.1, chai@npm:^5.2.0": - version: 5.2.0 - resolution: "chai@npm:5.2.0" + version: 5.2.1 + resolution: "chai@npm:5.2.1" dependencies: assertion-error: "npm:^2.0.1" check-error: "npm:^2.1.1" deep-eql: "npm:^5.0.1" loupe: "npm:^3.1.0" pathval: "npm:^2.0.0" - checksum: 10/2ce03671c159c6a567bf1912756daabdbb7c075f3c0078f1b59d61da8d276936367ee696dfe093b49e1479d9ba93a6074c8e55d49791dddd8061728cdcad249e + checksum: 10/2d9b14c9bbb9b791641ecee0d74a53f34d2c86f05c10b5567041ed376177f87af9dc7f5398207fd2711affbfeb9f0f1f60e11bcaf021f78ef37b7c65a6d94e7d languageName: node linkType: hard @@ -25764,10 +25786,10 @@ __metadata: languageName: node linkType: hard -"cjs-module-lexer@npm:^1.0.0, cjs-module-lexer@npm:^1.2.2, cjs-module-lexer@npm:^1.2.3": - version: 1.4.1 - resolution: "cjs-module-lexer@npm:1.4.1" - checksum: 10/6e830a1e00a34d416949bbc1924f3e8da65cef4a6a09e2b7fa35722e2d1c34bf378d3baca987b698d1cbc3eb83e44b044039b4e82755c96f30e0f03d1d227637 +"cjs-module-lexer@npm:^1.0.0, cjs-module-lexer@npm:^1.2.2": + version: 1.4.3 + resolution: "cjs-module-lexer@npm:1.4.3" + checksum: 10/d2b92f919a2dedbfd61d016964fce8da0035f827182ed6839c97cac56e8a8077cfa6a59388adfe2bc588a19cef9bbe830d683a76a6e93c51f65852062cfe2591 languageName: node linkType: hard @@ -26154,7 +26176,7 @@ __metadata: languageName: node linkType: hard -"color-name@npm:^1.0.0, color-name@npm:~1.1.4": +"color-name@npm:^1.0.0, color-name@npm:^1.1.4, color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" checksum: 10/b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 @@ -26228,7 +26250,7 @@ __metadata: languageName: node linkType: hard -"combined-stream@npm:^1.0.6, combined-stream@npm:^1.0.8, combined-stream@npm:~1.0.6": +"combined-stream@npm:^1.0.6, combined-stream@npm:^1.0.8": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" dependencies: @@ -26356,10 +26378,10 @@ __metadata: languageName: node linkType: hard -"component-emitter@npm:^1.3.0": - version: 1.3.0 - resolution: "component-emitter@npm:1.3.0" - checksum: 10/dfc1ec2e7aa2486346c068f8d764e3eefe2e1ca0b24f57506cd93b2ae3d67829a7ebd7cc16e2bf51368fac2f45f78fcff231718e40b1975647e4a86be65e1d05 +"component-emitter@npm:^1.3.1": + version: 1.3.1 + resolution: "component-emitter@npm:1.3.1" + checksum: 10/94550aa462c7bd5a61c1bc480e28554aa306066930152d1b1844a0dd3845d4e5db7e261ddec62ae184913b3e59b55a2ad84093b9d3596a8f17c341514d6c483d languageName: node linkType: hard @@ -26437,18 +26459,6 @@ __metadata: languageName: node linkType: hard -"concat-stream@npm:^1.5.2": - version: 1.6.2 - resolution: "concat-stream@npm:1.6.2" - dependencies: - buffer-from: "npm:^1.0.0" - inherits: "npm:^2.0.3" - readable-stream: "npm:^2.2.2" - typedarray: "npm:^0.0.6" - checksum: 10/71db903c84fc073ca35a274074e8d26c4330713d299f8623e993c448c1f6bf8b967806dd1d1a7b0f8add6f15ab1af7435df21fe79b4fe7efd78420c89e054e28 - languageName: node - linkType: hard - "concat-stream@npm:^2.0.0": version: 2.0.0 resolution: "concat-stream@npm:2.0.0" @@ -26556,6 +26566,13 @@ __metadata: languageName: node linkType: hard +"consola@npm:^3.2.3": + version: 3.4.2 + resolution: "consola@npm:3.4.2" + checksum: 10/32192c9f50d7cac27c5d7c4ecd3ff3679aea863e6bf5bd6a9cc2b05d1cd78addf5dae71df08c54330c142be8e7fbd46f051030129b57c6aacdd771efe409c4b2 + languageName: node + linkType: hard + "console-browserify@npm:^1.1.0": version: 1.2.0 resolution: "console-browserify@npm:1.2.0" @@ -26602,6 +26619,15 @@ __metadata: languageName: node linkType: hard +"content-disposition@npm:^1.0.0": + version: 1.0.0 + resolution: "content-disposition@npm:1.0.0" + dependencies: + safe-buffer: "npm:5.2.1" + checksum: 10/0dcc1a2d7874526b0072df3011b134857b49d97a3bc135bb464a299525d4972de6f5f464fd64da6c4d8406d26a1ffb976f62afaffef7723b1021a44498d10e08 + languageName: node + linkType: hard + "content-type@npm:^1.0.4, content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" @@ -26654,6 +26680,13 @@ __metadata: languageName: node linkType: hard +"cookie-signature@npm:^1.2.1": + version: 1.2.2 + resolution: "cookie-signature@npm:1.2.2" + checksum: 10/be44a3c9a56f3771aea3a8bd8ad8f0a8e2679bcb967478267f41a510b4eb5ec55085386ba79c706c4ac21605ca76f4251973444b90283e0eb3eeafe8a92c7708 + languageName: node + linkType: hard + "cookie@npm:0.7.1": version: 0.7.1 resolution: "cookie@npm:0.7.1" @@ -26661,7 +26694,7 @@ __metadata: languageName: node linkType: hard -"cookie@npm:0.7.2, cookie@npm:^0.7.0, cookie@npm:^0.7.2": +"cookie@npm:0.7.2, cookie@npm:^0.7.0, cookie@npm:^0.7.1, cookie@npm:^0.7.2": version: 0.7.2 resolution: "cookie@npm:0.7.2" checksum: 10/24b286c556420d4ba4e9bc09120c9d3db7d28ace2bd0f8ccee82422ce42322f73c8312441271e5eefafbead725980e5996cc02766dbb89a90ac7f5636ede608f @@ -26701,15 +26734,6 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.31.0": - version: 3.32.1 - resolution: "core-js-compat@npm:3.32.1" - dependencies: - browserslist: "npm:^4.21.10" - checksum: 10/e01f29cd369d4c2ba690a591e1613b167126afd10c44af4e260da1348394262f5b78c727cff864c342e328b2bf2522acad9afdcc783bc14ceb66bc18b0bf931d - languageName: node - linkType: hard - "core-js-pure@npm:^3.23.3, core-js-pure@npm:^3.30.2": version: 3.31.0 resolution: "core-js-pure@npm:3.31.0" @@ -26768,16 +26792,16 @@ __metadata: languageName: node linkType: hard -"cosmiconfig@npm:^7.0.0, cosmiconfig@npm:^7.0.1": - version: 7.0.1 - resolution: "cosmiconfig@npm:7.0.1" +"cosmiconfig@npm:^7.0.0": + version: 7.1.0 + resolution: "cosmiconfig@npm:7.1.0" dependencies: "@types/parse-json": "npm:^4.0.0" import-fresh: "npm:^3.2.1" parse-json: "npm:^5.0.0" path-type: "npm:^4.0.0" yaml: "npm:^1.10.0" - checksum: 10/861bf4c2c9e88e6c50f14278b25bb0509c484623de11fadf3788a3d543bc7c45178aeebeb6657293b12dc8bd1b86d926c5f25c803c4dc3821d628a1b24c3d20b + checksum: 10/03600bb3870c80ed151b7b706b99a1f6d78df8f4bdad9c95485072ea13358ef294b13dd99f9e7bf4cc0b43bcd3599d40df7e648750d21c2f6817ca2cd687e071 languageName: node linkType: hard @@ -26901,10 +26925,10 @@ __metadata: languageName: node linkType: hard -"crelt@npm:^1.0.5": - version: 1.0.5 - resolution: "crelt@npm:1.0.5" - checksum: 10/f692219f8550570b35d26f64a34945a016ec10c2714491d1306b95253e4b6bca609b3c73aee68e1c2035eab6cf38b6c853c4c16f80b26a808e688d25c6821b25 +"crelt@npm:^1.0.5, crelt@npm:^1.0.6": + version: 1.0.6 + resolution: "crelt@npm:1.0.6" + checksum: 10/5ed326ca6bd243b1dba6b943f665b21c2c04be03271824bc48f20dba324b0f8233e221f8c67312526d24af2b1243c023dc05a41bd8bd05d1a479fd2c72fb39c3 languageName: node linkType: hard @@ -26999,7 +27023,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.5": +"cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.5, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -27082,7 +27106,7 @@ __metadata: languageName: node linkType: hard -"css-loader@npm:^6.5.1, css-loader@npm:^6.7.1": +"css-loader@npm:^6.5.1": version: 6.11.0 resolution: "css-loader@npm:6.11.0" dependencies: @@ -27425,16 +27449,6 @@ __metadata: languageName: node linkType: hard -"dagre@npm:^0.8.5": - version: 0.8.5 - resolution: "dagre@npm:0.8.5" - dependencies: - graphlib: "npm:^2.1.8" - lodash: "npm:^4.17.15" - checksum: 10/f39899e29e9090581d67177ef6e2dd3ca5d7f764fbf3de81758d879bba66fee6fd8802d41d0c5d3d9a0563b334e99e1454a8d6ab4ce17e8e4f50836a3a403fdd - languageName: node - linkType: hard - "damerau-levenshtein@npm:^1.0.8": version: 1.0.8 resolution: "damerau-levenshtein@npm:1.0.8" @@ -27442,12 +27456,10 @@ __metadata: languageName: node linkType: hard -"dashdash@npm:^1.12.0": - version: 1.14.1 - resolution: "dashdash@npm:1.14.1" - dependencies: - assert-plus: "npm:^1.0.0" - checksum: 10/137b287fa021201ce100cef772c8eeeaaafdd2aa7282864022acf3b873021e54cb809e9c060fa164840bf54ff72d00d6e2d8da1ee5a86d7200eeefa1123a8f7f +"data-uri-to-buffer@npm:^4.0.0": + version: 4.0.1 + resolution: "data-uri-to-buffer@npm:4.0.1" + checksum: 10/0d0790b67ffec5302f204c2ccca4494f70b4e2d940fea3d36b09f0bb2b8539c2e86690429eb1f1dc4bcc9e4df0644193073e63d9ee48ac9fce79ec1506e4aa4c languageName: node linkType: hard @@ -27581,15 +27593,15 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.4.0": - version: 4.4.0 - resolution: "debug@npm:4.4.0" +"debug@npm:4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0": + version: 4.4.1 + resolution: "debug@npm:4.4.1" dependencies: ms: "npm:^2.1.3" peerDependenciesMeta: supports-color: optional: true - checksum: 10/1847944c2e3c2c732514b93d11886575625686056cd765336212dc15de2d2b29612b6cd80e1afba767bb8e1803b778caf9973e98169ef1a24a7a7009e1820367 + checksum: 10/8e2709b2144f03c7950f8804d01ccb3786373df01e406a0f66928e47001cf2d336cbed9ee137261d4f90d68d8679468c755e3548ed83ddacdc82b194d2468afe languageName: node linkType: hard @@ -27655,13 +27667,6 @@ __metadata: languageName: node linkType: hard -"dedent@npm:^0.7.0": - version: 0.7.0 - resolution: "dedent@npm:0.7.0" - checksum: 10/87de191050d9a40dd70cad01159a0bcf05ecb59750951242070b6abf9569088684880d00ba92a955b4058804f16eeaf91d604f283929b4f614d181cd7ae633d2 - languageName: node - linkType: hard - "dedent@npm:^1.0.0": version: 1.5.3 resolution: "dedent@npm:1.5.3" @@ -27864,6 +27869,20 @@ __metadata: languageName: node linkType: hard +"dependency-tree@npm:^11.0.0": + version: 11.2.0 + resolution: "dependency-tree@npm:11.2.0" + dependencies: + commander: "npm:^12.1.0" + filing-cabinet: "npm:^5.0.3" + precinct: "npm:^12.2.0" + typescript: "npm:^5.8.3" + bin: + dependency-tree: bin/cli.js + checksum: 10/0dac9f86f1587ce7c3b0050ce64a7f0f9b86dd5b1c318feadfa1d440a3abb1eaa9cd615fc151c41d17301509461669983fd311368cb7113124ad5df17ba850e7 + languageName: node + linkType: hard + "deprecation@npm:^2.0.0, deprecation@npm:^2.3.1": version: 2.3.1 resolution: "deprecation@npm:2.3.1" @@ -27966,6 +27985,108 @@ __metadata: languageName: node linkType: hard +"detective-amd@npm:^6.0.1": + version: 6.0.1 + resolution: "detective-amd@npm:6.0.1" + dependencies: + ast-module-types: "npm:^6.0.1" + escodegen: "npm:^2.1.0" + get-amd-module-type: "npm:^6.0.1" + node-source-walk: "npm:^7.0.1" + bin: + detective-amd: bin/cli.js + checksum: 10/e709bd512933f33e1f461045f8ac10295661f4cca7cd226d5d69e6972a6392e4dd3879f6c9411e6f22a12c3fac499205492cbe842cc2423aa116d1887d34d167 + languageName: node + linkType: hard + +"detective-cjs@npm:^6.0.1": + version: 6.0.1 + resolution: "detective-cjs@npm:6.0.1" + dependencies: + ast-module-types: "npm:^6.0.1" + node-source-walk: "npm:^7.0.1" + checksum: 10/8261af91c98178c2580d8d3bbfc394ccf3eb161c15c88e119dd7ce3a1ff24ccf37b2faee12f6da8efd860396bca89dca4e76530d39a26207d710da93131f5db7 + languageName: node + linkType: hard + +"detective-es6@npm:^5.0.1": + version: 5.0.1 + resolution: "detective-es6@npm:5.0.1" + dependencies: + node-source-walk: "npm:^7.0.1" + checksum: 10/460751524871a11cf7c39e12b4316f745fff68f9e5f5fd85193c3ff651cb34cdd51a34d187a713735eb407b53c706645643ad8fd2a2ca098d551ffffe98935b1 + languageName: node + linkType: hard + +"detective-postcss@npm:^7.0.1": + version: 7.0.1 + resolution: "detective-postcss@npm:7.0.1" + dependencies: + is-url: "npm:^1.2.4" + postcss-values-parser: "npm:^6.0.2" + peerDependencies: + postcss: ^8.4.47 + checksum: 10/5cbe1ecc224beb3aa846477de83959c349764b6b912decc92e4c0e1dc22641b5d97b6cdee54dfa2706c71bed50b363c87072314e22730a0c66f82f4be145221c + languageName: node + linkType: hard + +"detective-sass@npm:^6.0.1": + version: 6.0.1 + resolution: "detective-sass@npm:6.0.1" + dependencies: + gonzales-pe: "npm:^4.3.0" + node-source-walk: "npm:^7.0.1" + checksum: 10/bf941665aa8fbdf02399f227d8a80ca059e22d2a2f8d34e90ca06fe3e2772afd63b75dc267712ac8a7b6dc580a9be91a7f965b790803488544472b36dd904c72 + languageName: node + linkType: hard + +"detective-scss@npm:^5.0.1": + version: 5.0.1 + resolution: "detective-scss@npm:5.0.1" + dependencies: + gonzales-pe: "npm:^4.3.0" + node-source-walk: "npm:^7.0.1" + checksum: 10/f5ebb927fd789f8e069f186439c60e8b9efe1e27c45d755c8dd00fdab4a390e28ed0e896e5ecc4275d1dac0b4afa1b1cb7bedf08bdc2155c2f779b69c882705d + languageName: node + linkType: hard + +"detective-stylus@npm:^5.0.1": + version: 5.0.1 + resolution: "detective-stylus@npm:5.0.1" + checksum: 10/28c4168a82e427ba51b24a4d5ef6ad0fb55183dc57317073a45801ff3e2617a86c3288a4b6cd2c84911371128bd841681d855276215926bc1d176da4ebeb5ae2 + languageName: node + linkType: hard + +"detective-typescript@npm:^14.0.0": + version: 14.0.0 + resolution: "detective-typescript@npm:14.0.0" + dependencies: + "@typescript-eslint/typescript-estree": "npm:^8.23.0" + ast-module-types: "npm:^6.0.1" + node-source-walk: "npm:^7.0.1" + peerDependencies: + typescript: ^5.4.4 + checksum: 10/8dcf16e6bdc9faf0318553f3a2e1e198782cf21edde264d29efd33d88551a79ff1fe3d2d1ef7b2c00a8a88da0323ead4fddbc7ed92270e425da42c4608bec97b + languageName: node + linkType: hard + +"detective-vue2@npm:^2.2.0": + version: 2.2.0 + resolution: "detective-vue2@npm:2.2.0" + dependencies: + "@dependents/detective-less": "npm:^5.0.1" + "@vue/compiler-sfc": "npm:^3.5.13" + detective-es6: "npm:^5.0.1" + detective-sass: "npm:^6.0.1" + detective-scss: "npm:^5.0.1" + detective-stylus: "npm:^5.0.1" + detective-typescript: "npm:^14.0.0" + peerDependencies: + typescript: ^5.4.4 + checksum: 10/896226e5993e7b87948db0aaca4d8980b015828e797a1e46ae102fa079abcbb9713ef170df65735a69644f2e2bf7eb4ee8c2036727b22a65616e1fb5823039ab + languageName: node + linkType: hard + "dezalgo@npm:^1.0.0, dezalgo@npm:^1.0.4": version: 1.0.4 resolution: "dezalgo@npm:1.0.4" @@ -28085,8 +28206,8 @@ __metadata: linkType: hard "dockerode@npm:^4.0.0": - version: 4.0.6 - resolution: "dockerode@npm:4.0.6" + version: 4.0.7 + resolution: "dockerode@npm:4.0.7" dependencies: "@balena/dockerignore": "npm:^1.0.2" "@grpc/grpc-js": "npm:^1.11.1" @@ -28095,7 +28216,7 @@ __metadata: protobufjs: "npm:^7.3.2" tar-fs: "npm:~2.1.2" uuid: "npm:^10.0.0" - checksum: 10/75bd706f20f01742d22913b72e2a5215a4d9f79772c29079772f84fc41a2b1890a704a1aa3d1d764405367090e93c33197d6add19fde3546bac919d98eebc8e3 + checksum: 10/d7cd174cf4489f41335ec8aaaa7c98c164a624f9a793544aa5280d85254ce276e7797de896042ce47d87aca6f8d2653acc37a0d18807d4ce8ea31892faef40a8 languageName: node linkType: hard @@ -28422,16 +28543,6 @@ __metadata: languageName: node linkType: hard -"ecc-jsbn@npm:~0.1.1": - version: 0.1.2 - resolution: "ecc-jsbn@npm:0.1.2" - dependencies: - jsbn: "npm:~0.1.0" - safer-buffer: "npm:^2.1.0" - checksum: 10/d43591f2396196266e186e6d6928038cc11c76c3699a912cb9c13757060f7bbc7f17f47c4cb16168cdeacffc7965aef021142577e646fb3cb88810c15173eb57 - languageName: node - linkType: hard - "ecdsa-sig-formatter@npm:1.0.11, ecdsa-sig-formatter@npm:^1.0.11": version: 1.0.11 resolution: "ecdsa-sig-formatter@npm:1.0.11" @@ -28555,7 +28666,7 @@ __metadata: languageName: node linkType: hard -"encodeurl@npm:~2.0.0": +"encodeurl@npm:^2.0.0, encodeurl@npm:~2.0.0": version: 2.0.0 resolution: "encodeurl@npm:2.0.0" checksum: 10/abf5cd51b78082cf8af7be6785813c33b6df2068ce5191a40ca8b1afe6a86f9230af9a9ce694a5ce4665955e5c1120871826df9c128a642e09c58d592e2807fe @@ -28589,24 +28700,13 @@ __metadata: languageName: node linkType: hard -"endent@npm:^2.0.1": - version: 2.1.0 - resolution: "endent@npm:2.1.0" - dependencies: - dedent: "npm:^0.7.0" - fast-json-parse: "npm:^1.0.3" - objectorarray: "npm:^1.0.5" - checksum: 10/c352831088fce745a39ddbd5f87a17e073ea6556e7e96e9010d945a3f3020f836b9a84657123fa01e897db9216f4b080d950b5ded9bf3a8227f14a34efaaaf7c - languageName: node - linkType: hard - -"enhanced-resolve@npm:^5.17.1, enhanced-resolve@npm:^5.18.0": - version: 5.18.0 - resolution: "enhanced-resolve@npm:5.18.0" +"enhanced-resolve@npm:^5.17.1, enhanced-resolve@npm:^5.17.3, enhanced-resolve@npm:^5.18.0": + version: 5.18.3 + resolution: "enhanced-resolve@npm:5.18.3" dependencies: graceful-fs: "npm:^4.2.4" tapable: "npm:^2.2.0" - checksum: 10/e88463ef97b68d40d0da0cd0c572e23f43dba0be622d6d44eae5cafed05f0c5dac43e463a83a86c4f70186d029357f82b56d9e1e47e8fc91dce3d6602f8bd6ce + checksum: 10/a4d0a1eacba3079f617b68c8f7e17583c3cbc572055c2edca41c0fa0230a49f6e9b2c6ffd4128cc5f84e15ea6cc313ae2b01e1057fcd252fabef70220a5d9f6a languageName: node linkType: hard @@ -28819,10 +28919,10 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^1.2.1, es-module-lexer@npm:^1.3.1, es-module-lexer@npm:^1.5.0": - version: 1.5.4 - resolution: "es-module-lexer@npm:1.5.4" - checksum: 10/f29c7c97a58eb17640dcbd71bd6ef754ad4f58f95c3073894573d29dae2cad43ecd2060d97ed5b866dfb7804d5590fb7de1d2c5339a5fceae8bd60b580387fc5 +"es-module-lexer@npm:^1.2.1, es-module-lexer@npm:^1.3.1": + version: 1.7.0 + resolution: "es-module-lexer@npm:1.7.0" + checksum: 10/b6f3e576a3fed4d82b0d0ad4bbf6b3a5ad694d2e7ce8c4a069560da3db6399381eaba703616a182b16dde50ce998af64e07dcf49f2ae48153b9e07be3f107087 languageName: node linkType: hard @@ -28835,14 +28935,15 @@ __metadata: languageName: node linkType: hard -"es-set-tostringtag@npm:^2.0.3": - version: 2.0.3 - resolution: "es-set-tostringtag@npm:2.0.3" +"es-set-tostringtag@npm:^2.0.3, es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" dependencies: - get-intrinsic: "npm:^1.2.4" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" has-tostringtag: "npm:^1.0.2" - hasown: "npm:^2.0.1" - checksum: 10/7227fa48a41c0ce83e0377b11130d324ac797390688135b8da5c28994c0165be8b252e15cd1de41e1325e5a5412511586960213e88f9ab4a5e7d028895db5129 + hasown: "npm:^2.0.2" + checksum: 10/86814bf8afbcd8966653f731415888019d4bc4aca6b6c354132a7a75bb87566751e320369654a101d23a91c87a85c79b178bcf40332839bd347aff437c4fb65f languageName: node linkType: hard @@ -28866,6 +28967,18 @@ __metadata: languageName: node linkType: hard +"es-toolkit@npm:^1.22.0": + version: 1.39.9 + resolution: "es-toolkit@npm:1.39.9" + dependenciesMeta: + "@trivago/prettier-plugin-sort-imports@4.3.0": + unplugged: true + prettier-plugin-sort-re-exports@0.0.1: + unplugged: true + checksum: 10/39b2fb9173ae2782a31b7bda896809261078744f5c658e5f4a7d7121a221a9e0c0b816610c17fec02629b92556a86e4834c7d6c956de05b09f418cc42dba3936 + languageName: node + linkType: hard + "es5-ext@npm:^0.10.35, es5-ext@npm:^0.10.46, es5-ext@npm:^0.10.50, es5-ext@npm:^0.10.53, es5-ext@npm:^0.10.61, es5-ext@npm:^0.10.62, es5-ext@npm:~0.10.14, es5-ext@npm:~0.10.2, es5-ext@npm:~0.10.46": version: 0.10.63 resolution: "es5-ext@npm:0.10.63" @@ -28944,34 +29057,35 @@ __metadata: linkType: hard "esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0, esbuild@npm:^0.25.0": - version: 0.25.4 - resolution: "esbuild@npm:0.25.4" + version: 0.25.8 + resolution: "esbuild@npm:0.25.8" dependencies: - "@esbuild/aix-ppc64": "npm:0.25.4" - "@esbuild/android-arm": "npm:0.25.4" - "@esbuild/android-arm64": "npm:0.25.4" - "@esbuild/android-x64": "npm:0.25.4" - "@esbuild/darwin-arm64": "npm:0.25.4" - "@esbuild/darwin-x64": "npm:0.25.4" - "@esbuild/freebsd-arm64": "npm:0.25.4" - "@esbuild/freebsd-x64": "npm:0.25.4" - "@esbuild/linux-arm": "npm:0.25.4" - "@esbuild/linux-arm64": "npm:0.25.4" - "@esbuild/linux-ia32": "npm:0.25.4" - "@esbuild/linux-loong64": "npm:0.25.4" - "@esbuild/linux-mips64el": "npm:0.25.4" - "@esbuild/linux-ppc64": "npm:0.25.4" - "@esbuild/linux-riscv64": "npm:0.25.4" - "@esbuild/linux-s390x": "npm:0.25.4" - "@esbuild/linux-x64": "npm:0.25.4" - "@esbuild/netbsd-arm64": "npm:0.25.4" - "@esbuild/netbsd-x64": "npm:0.25.4" - "@esbuild/openbsd-arm64": "npm:0.25.4" - "@esbuild/openbsd-x64": "npm:0.25.4" - "@esbuild/sunos-x64": "npm:0.25.4" - "@esbuild/win32-arm64": "npm:0.25.4" - "@esbuild/win32-ia32": "npm:0.25.4" - "@esbuild/win32-x64": "npm:0.25.4" + "@esbuild/aix-ppc64": "npm:0.25.8" + "@esbuild/android-arm": "npm:0.25.8" + "@esbuild/android-arm64": "npm:0.25.8" + "@esbuild/android-x64": "npm:0.25.8" + "@esbuild/darwin-arm64": "npm:0.25.8" + "@esbuild/darwin-x64": "npm:0.25.8" + "@esbuild/freebsd-arm64": "npm:0.25.8" + "@esbuild/freebsd-x64": "npm:0.25.8" + "@esbuild/linux-arm": "npm:0.25.8" + "@esbuild/linux-arm64": "npm:0.25.8" + "@esbuild/linux-ia32": "npm:0.25.8" + "@esbuild/linux-loong64": "npm:0.25.8" + "@esbuild/linux-mips64el": "npm:0.25.8" + "@esbuild/linux-ppc64": "npm:0.25.8" + "@esbuild/linux-riscv64": "npm:0.25.8" + "@esbuild/linux-s390x": "npm:0.25.8" + "@esbuild/linux-x64": "npm:0.25.8" + "@esbuild/netbsd-arm64": "npm:0.25.8" + "@esbuild/netbsd-x64": "npm:0.25.8" + "@esbuild/openbsd-arm64": "npm:0.25.8" + "@esbuild/openbsd-x64": "npm:0.25.8" + "@esbuild/openharmony-arm64": "npm:0.25.8" + "@esbuild/sunos-x64": "npm:0.25.8" + "@esbuild/win32-arm64": "npm:0.25.8" + "@esbuild/win32-ia32": "npm:0.25.8" + "@esbuild/win32-x64": "npm:0.25.8" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -29015,6 +29129,8 @@ __metadata: optional: true "@esbuild/openbsd-x64": optional: true + "@esbuild/openharmony-arm64": + optional: true "@esbuild/sunos-x64": optional: true "@esbuild/win32-arm64": @@ -29025,7 +29141,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10/227ffe9b31f0b184a0b0a0210bb9d32b2b115b8c5c9b09f08db2c3928cb470fc55a22dbba3c2894365d3abcc62c2089b85638be96a20691d1234d31990ea01b2 + checksum: 10/9897411732768e652d90fa5dfadae965e8f420d24e5f23fa0604331a1441769e2c7ee4e41ca53e926f1fb51a53af52e01fc9070fdc1a4edf3e9ec9208ee41273 languageName: node linkType: hard @@ -29205,13 +29321,13 @@ __metadata: linkType: hard "eslint-config-prettier@npm:^9.0.0": - version: 9.1.0 - resolution: "eslint-config-prettier@npm:9.1.0" + version: 9.1.2 + resolution: "eslint-config-prettier@npm:9.1.2" peerDependencies: eslint: ">=7.0.0" bin: eslint-config-prettier: bin/cli.js - checksum: 10/411e3b3b1c7aa04e3e0f20d561271b3b909014956c4dba51c878bf1a23dbb8c800a3be235c46c4732c70827276e540b6eed4636d9b09b444fd0a8e07f0fcd830 + checksum: 10/e4bba2d76df9dc6e2adca2866e544bfd1ff32232fc483743c04cedd93918a90a327b56d4a7e3f9d3fa32d90bd50b034d09df987860260064b18c026b8bbd15aa languageName: node linkType: hard @@ -29424,6 +29540,21 @@ __metadata: languageName: node linkType: hard +"eslint-rspack-plugin@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-rspack-plugin@npm:4.2.1" + dependencies: + "@types/eslint": "npm:^8.56.10" + jest-worker: "npm:^29.7.0" + micromatch: "npm:^4.0.8" + normalize-path: "npm:^3.0.0" + schema-utils: "npm:^4.2.0" + peerDependencies: + eslint: ^8.0.0 || ^9.0.0 + checksum: 10/c45639e660d13af63c58f8f80103a033f607b9d635d9315d7d859408134cc35e2e3f981805f7abdd6061ec9d8207cfc7c0d5a097df9ac8c7ccd707ca5aa14583 + languageName: node + linkType: hard + "eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" @@ -29451,10 +29582,10 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^4.2.0": - version: 4.2.0 - resolution: "eslint-visitor-keys@npm:4.2.0" - checksum: 10/9651b3356b01760e586b4c631c5268c0e1a85236e3292bf754f0472f465bf9a856c0ddc261fceace155334118c0151778effafbab981413dbf9288349343fa25 +"eslint-visitor-keys@npm:^4.2.0, eslint-visitor-keys@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-visitor-keys@npm:4.2.1" + checksum: 10/3ee00fc6a7002d4b0ffd9dc99e13a6a7882c557329e6c25ab254220d71e5c9c4f89dca4695352949ea678eb1f3ba912a18ef8aac0a7fe094196fd92f441bfce2 languageName: node linkType: hard @@ -29611,7 +29742,7 @@ __metadata: languageName: node linkType: hard -"estree-walker@npm:^2.0.1, estree-walker@npm:^2.0.2": +"estree-walker@npm:^2.0.2": version: 2.0.2 resolution: "estree-walker@npm:2.0.2" checksum: 10/b02109c5d46bc2ed47de4990eef770f7457b1159a229f0999a09224d2b85ffeed2d7679cffcff90aeb4448e94b0168feb5265b209cdec29aad50a3d6e93d21e2 @@ -29634,7 +29765,7 @@ __metadata: languageName: node linkType: hard -"etag@npm:~1.8.1": +"etag@npm:^1.8.1, etag@npm:~1.8.1": version: 1.8.1 resolution: "etag@npm:1.8.1" checksum: 10/571aeb3dbe0f2bbd4e4fadbdb44f325fc75335cd5f6f6b6a091e6a06a9f25ed5392f0863c5442acb0646787446e816f13cbfc6edce5b07658541dff573cab1ff @@ -29702,12 +29833,12 @@ __metadata: languageName: node linkType: hard -"eventsource@npm:^3.0.6": - version: 3.0.6 - resolution: "eventsource@npm:3.0.6" +"eventsource@npm:^3.0.2, eventsource@npm:^3.0.6": + version: 3.0.7 + resolution: "eventsource@npm:3.0.7" dependencies: eventsource-parser: "npm:^3.0.1" - checksum: 10/ac08c7d1b21e454c7685693fe4ace53fc0b84f3cf752699a556876f2a7f33b7a12972ae33d1c407fb920d6d4aed10de52fdf0dd01902ccdf45cd5da8d55e7f88 + checksum: 10/e034915bc97068d1d38617951afd798e6776d6a3a78e36a7569c235b177c7afc2625c9fe82656f7341ab72c7eeecb3fd507b7f88e9328f2448872ff9c4742bb6 languageName: node linkType: hard @@ -29727,7 +29858,6 @@ __metadata: resolution: "example-app-next@workspace:packages/app-next" dependencies: "@backstage/app-defaults": "workspace:^" - "@backstage/canon": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -29767,6 +29897,7 @@ __metadata: "@backstage/plugin-user-settings": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" + "@backstage/ui": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" @@ -29801,7 +29932,6 @@ __metadata: resolution: "example-app@workspace:packages/app" dependencies: "@backstage/app-defaults": "workspace:^" - "@backstage/canon": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -29837,6 +29967,7 @@ __metadata: "@backstage/plugin-user-settings": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" + "@backstage/ui": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" @@ -29850,7 +29981,7 @@ __metadata: "@types/react": "npm:*" "@types/react-dom": "npm:*" "@types/zen-observable": "npm:^0.8.0" - axios: "npm:^1.7.7" + axios: "npm:^1.11.0" cross-env: "npm:^7.0.0" history: "npm:^5.0.0" msw: "npm:^1.0.0" @@ -29885,6 +30016,7 @@ __metadata: "@backstage/plugin-events-backend": "workspace:^" "@backstage/plugin-events-backend-module-google-pubsub": "workspace:^" "@backstage/plugin-kubernetes-backend": "workspace:^" + "@backstage/plugin-mcp-actions-backend": "workspace:^" "@backstage/plugin-notifications-backend": "workspace:^" "@backstage/plugin-permission-backend": "workspace:^" "@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^" @@ -29901,7 +30033,7 @@ __metadata: "@backstage/plugin-search-backend-node": "workspace:^" "@backstage/plugin-signals-backend": "workspace:^" "@backstage/plugin-techdocs-backend": "workspace:^" - "@opentelemetry/auto-instrumentations-node": "npm:^0.54.0" + "@opentelemetry/auto-instrumentations-node": "npm:^0.61.0" "@opentelemetry/exporter-prometheus": "npm:^0.54.0" "@opentelemetry/sdk-node": "npm:^0.54.0" example-app: "link:../app" @@ -30014,26 +30146,27 @@ __metadata: languageName: node linkType: hard -"express-openapi-validator@npm:^5.0.4": - version: 5.3.9 - resolution: "express-openapi-validator@npm:5.3.9" +"express-openapi-validator@npm:^5.5.8": + version: 5.5.8 + resolution: "express-openapi-validator@npm:5.5.8" dependencies: - "@apidevtools/json-schema-ref-parser": "npm:^11.7.0" + "@apidevtools/json-schema-ref-parser": "npm:^14.0.3" "@types/multer": "npm:^1.4.12" ajv: "npm:^8.17.1" ajv-draft-04: "npm:^1.0.0" - ajv-formats: "npm:^2.1.1" + ajv-formats: "npm:^3.0.1" content-type: "npm:^1.0.5" json-schema-traverse: "npm:^1.0.0" lodash.clonedeep: "npm:^4.5.0" lodash.get: "npm:^4.4.2" media-typer: "npm:^1.1.0" - multer: "npm:^1.4.5-lts.1" + multer: "npm:^2.0.2" ono: "npm:^7.1.3" - path-to-regexp: "npm:^8.1.0" + path-to-regexp: "npm:^8.2.0" + qs: "npm:^6.14.0" peerDependencies: express: "*" - checksum: 10/520e489c6ea10dbead42a6be4f56e3ba28fa9e6c1a24a70df6cde8e5378ed061a7d5b547922493185f04e418c53e40f9585ea84cd549458d10dd5f022e4d389d + checksum: 10/0396d4fd253fcd61725b632adcd805ac4ced940c34907775bedce39701d3b884e7a60357fa29c59d282407381c1fdcecceae6d0011684103e7ffe104038cb661 languageName: node linkType: hard @@ -30054,19 +30187,28 @@ __metadata: languageName: node linkType: hard +"express-rate-limit@npm:^7.5.0": + version: 7.5.1 + resolution: "express-rate-limit@npm:7.5.1" + peerDependencies: + express: ">= 4.11" + checksum: 10/357c3398450144ab7bbce2841d0bf4f93a0f3fd9d1d5ed9a0ee331b557af969cc790941dc37b47f8d9b5672964aa0e31666f770e1f48b334dc7d1e69f6433040 + languageName: node + linkType: hard + "express-session@npm:^1.17.1, express-session@npm:^1.17.3": - version: 1.18.1 - resolution: "express-session@npm:1.18.1" + version: 1.18.2 + resolution: "express-session@npm:1.18.2" dependencies: cookie: "npm:0.7.2" cookie-signature: "npm:1.0.7" debug: "npm:2.6.9" depd: "npm:~2.0.0" - on-headers: "npm:~1.0.2" + on-headers: "npm:~1.1.0" parseurl: "npm:~1.3.3" safe-buffer: "npm:5.2.1" uid-safe: "npm:~2.1.5" - checksum: 10/fec407d20e2b7c110e92c992f4a094b9603bf3e1d1040cf5a2f70fe64e23560f7f2d41e0e6dc8864dd146bbf0c07d9d5aa6848f80c403cbeac4cee4303192a53 + checksum: 10/1a89a4d3e579e1d2a729dc604c254c3e942b24a279681371c2d8042b1080f9e6de4ca787a091ec1a9178c9f2cc5c19bcb9f3929632d44db8972263e26c2cc4b8 languageName: node linkType: hard @@ -30109,6 +30251,41 @@ __metadata: languageName: node linkType: hard +"express@npm:^5.0.1": + version: 5.1.0 + resolution: "express@npm:5.1.0" + dependencies: + accepts: "npm:^2.0.0" + body-parser: "npm:^2.2.0" + content-disposition: "npm:^1.0.0" + content-type: "npm:^1.0.5" + cookie: "npm:^0.7.1" + cookie-signature: "npm:^1.2.1" + debug: "npm:^4.4.0" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + etag: "npm:^1.8.1" + finalhandler: "npm:^2.1.0" + fresh: "npm:^2.0.0" + http-errors: "npm:^2.0.0" + merge-descriptors: "npm:^2.0.0" + mime-types: "npm:^3.0.0" + on-finished: "npm:^2.4.1" + once: "npm:^1.4.0" + parseurl: "npm:^1.3.3" + proxy-addr: "npm:^2.0.7" + qs: "npm:^6.14.0" + range-parser: "npm:^1.2.1" + router: "npm:^2.2.0" + send: "npm:^1.1.0" + serve-static: "npm:^2.2.0" + statuses: "npm:^2.0.1" + type-is: "npm:^2.0.1" + vary: "npm:^1.1.2" + checksum: 10/6dba00bbdf308f43a84ed3f07a7e9870d5208f2a0b8f60f39459dda089750379747819863fad250849d3c9163833f33f94ce69d73938df31e0c5a430800d7e56 + languageName: node + linkType: hard + "ext@npm:^1.1.2": version: 1.7.0 resolution: "ext@npm:1.7.0" @@ -30118,7 +30295,7 @@ __metadata: languageName: node linkType: hard -"extend@npm:3.0.2, extend@npm:^3.0.0, extend@npm:^3.0.2, extend@npm:~3.0.2": +"extend@npm:3.0.2, extend@npm:^3.0.0, extend@npm:^3.0.2": version: 3.0.2 resolution: "extend@npm:3.0.2" checksum: 10/59e89e2dc798ec0f54b36d82f32a27d5f6472c53974f61ca098db5d4648430b725387b53449a34df38fd0392045434426b012f302b3cc049a6500ccf82877e4e @@ -30150,13 +30327,6 @@ __metadata: languageName: node linkType: hard -"extsprintf@npm:1.3.0": - version: 1.3.0 - resolution: "extsprintf@npm:1.3.0" - checksum: 10/26967d6c7ecbfb5bc5b7a6c43503dc5fafd9454802037e9fa1665e41f615da4ff5918bd6cb871a3beabed01a31eca1ccd0bdfb41231f50ad50d405a430f78377 - languageName: node - linkType: hard - "extsprintf@npm:^1.2.0": version: 1.4.0 resolution: "extsprintf@npm:1.4.0" @@ -30378,15 +30548,15 @@ __metadata: languageName: node linkType: hard -"fdir@npm:^6.4.2": - version: 6.4.2 - resolution: "fdir@npm:6.4.2" +"fdir@npm:^6.4.4, fdir@npm:^6.4.6": + version: 6.4.6 + resolution: "fdir@npm:6.4.6" peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: picomatch: optional: true - checksum: 10/5ff80d1d2034e75cc68be175401c9f64c4938a6b2c1e9a0c27f2d211ffbe491fd86d29e4576825d9da8aff9bd465f0283427c2dddc11653457906c46d3bbc448 + checksum: 10/c186ba387e7b75ccf874a098d9bc5fe0af0e9c52fc56f8eac8e80aa4edb65532684bf2bf769894ff90f53bf221d6136692052d31f07a9952807acae6cbe7ee50 languageName: node linkType: hard @@ -30397,6 +30567,23 @@ __metadata: languageName: node linkType: hard +"fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4": + version: 3.2.0 + resolution: "fetch-blob@npm:3.2.0" + dependencies: + node-domexception: "npm:^1.0.0" + web-streams-polyfill: "npm:^3.0.3" + checksum: 10/5264ecceb5fdc19eb51d1d0359921f12730941e333019e673e71eb73921146dceabcb0b8f534582be4497312d656508a439ad0f5edeec2b29ab2e10c72a1f86b + languageName: node + linkType: hard + +"fflate@npm:^0.8.2": + version: 0.8.2 + resolution: "fflate@npm:0.8.2" + checksum: 10/2bd26ba6d235d428de793c6a0cd1aaa96a06269ebd4e21b46c8fd1bd136abc631acf27e188d47c3936db090bf3e1ede11d15ce9eae9bffdc4bfe1b9dc66ca9cb + languageName: node + linkType: hard + "figures@npm:^3.0.0": version: 3.2.0 resolution: "figures@npm:3.2.0" @@ -30415,6 +30602,18 @@ __metadata: languageName: node linkType: hard +"file-type@npm:21.0.0": + version: 21.0.0 + resolution: "file-type@npm:21.0.0" + dependencies: + "@tokenizer/inflate": "npm:^0.2.7" + strtok3: "npm:^10.2.2" + token-types: "npm:^6.0.0" + uint8array-extras: "npm:^1.4.0" + checksum: 10/6980e8b0ef870a98b51ab2eac5db94a1884de8476fe49dc02d2f7e0c1d1d7d44d42b6c59e67867ae90f321ddf4edd00fcfda01821591e2fa05385d0e438a9dc1 + languageName: node + linkType: hard + "file-type@npm:3.9.0": version: 3.9.0 resolution: "file-type@npm:3.9.0" @@ -30456,6 +30655,27 @@ __metadata: languageName: node linkType: hard +"filing-cabinet@npm:^5.0.3": + version: 5.0.3 + resolution: "filing-cabinet@npm:5.0.3" + dependencies: + app-module-path: "npm:^2.2.0" + commander: "npm:^12.1.0" + enhanced-resolve: "npm:^5.18.0" + module-definition: "npm:^6.0.1" + module-lookup-amd: "npm:^9.0.3" + resolve: "npm:^1.22.10" + resolve-dependency-path: "npm:^4.0.1" + sass-lookup: "npm:^6.1.0" + stylus-lookup: "npm:^6.1.0" + tsconfig-paths: "npm:^4.2.0" + typescript: "npm:^5.7.3" + bin: + filing-cabinet: bin/cli.js + checksum: 10/0434ee0aa745c4da8f5e112563ee712a73c0582144ed3a37afad404cf42f700f7770ab7c0d00a9e062f14b5d52f9ab2b04e867f6f4b5c4bbde633242ee5020ac + languageName: node + linkType: hard + "fill-range@npm:^7.1.1": version: 7.1.1 resolution: "fill-range@npm:7.1.1" @@ -30495,6 +30715,20 @@ __metadata: languageName: node linkType: hard +"finalhandler@npm:^2.1.0": + version: 2.1.0 + resolution: "finalhandler@npm:2.1.0" + dependencies: + debug: "npm:^4.4.0" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + on-finished: "npm:^2.4.1" + parseurl: "npm:^1.3.3" + statuses: "npm:^2.0.1" + checksum: 10/b2bd68c310e2c463df0ab747ab05f8defbc540b8c3f2442f86e7d084ac8acbc31f8cae079931b7f5a406521501941e3395e963de848a0aaf45dd414adeb5ff4e + languageName: node + linkType: hard + "find-cache-dir@npm:^2.0.0": version: 2.1.0 resolution: "find-cache-dir@npm:2.1.0" @@ -30506,17 +30740,6 @@ __metadata: languageName: node linkType: hard -"find-cache-dir@npm:^3.3.1": - version: 3.3.2 - resolution: "find-cache-dir@npm:3.3.2" - dependencies: - commondir: "npm:^1.0.1" - make-dir: "npm:^3.0.2" - pkg-dir: "npm:^4.1.0" - checksum: 10/3907c2e0b15132704ed67083686cd3e68ab7d9ecc22e50ae9da20678245d488b01fa22c0e34c0544dc6edc4354c766f016c8c186a787be7c17f7cde8c5281e85 - languageName: node - linkType: hard - "find-file-up@npm:^2.0.1": version: 2.0.1 resolution: "find-file-up@npm:2.0.1" @@ -30559,15 +30782,15 @@ __metadata: linkType: hard "find-process@npm:^1.4.5": - version: 1.4.10 - resolution: "find-process@npm:1.4.10" + version: 1.4.11 + resolution: "find-process@npm:1.4.11" dependencies: chalk: "npm:~4.1.2" commander: "npm:^12.1.0" loglevel: "npm:^1.9.2" bin: find-process: bin/find-process.js - checksum: 10/cda45cfb3f52cbc7f643a465a8bffc19e2a3ac49ec5aa74e9b18ed2e4d6d62e370443d38c5fdd3de6b30b232fda51db1d74c2574d70d28e6c896d49df3bc3bcd + checksum: 10/95f7106877c86fc09804b29339cfac518a43228c1b5f4a4e9442e57ac5538369fbeafb746fa96390ecd7be6f5a9119b58c952b5c96a0cb9f2490ef19298dec1e languageName: node linkType: hard @@ -30693,12 +30916,12 @@ __metadata: languageName: node linkType: hard -"for-each@npm:^0.3.3": - version: 0.3.3 - resolution: "for-each@npm:0.3.3" +"for-each@npm:^0.3.3, for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" dependencies: - is-callable: "npm:^1.1.3" - checksum: 10/fdac0cde1be35610bd635ae958422e8ce0cc1313e8d32ea6d34cfda7b60850940c1fd07c36456ad76bd9c24aef6ff5e03b02beb58c83af5ef6c968a64eada676 + is-callable: "npm:^1.2.7" + checksum: 10/330cc2439f85c94f4609de3ee1d32c5693ae15cdd7fe3d112c4fd9efd4ce7143f2c64ef6c2c9e0cfdb0058437f33ef05b5bdae5b98fcc903fb2143fbaf0fea0f languageName: node linkType: hard @@ -30709,20 +30932,13 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^3.1.0": - version: 3.1.1 - resolution: "foreground-child@npm:3.1.1" +"foreground-child@npm:^3.1.0, foreground-child@npm:^3.3.1": + version: 3.3.1 + resolution: "foreground-child@npm:3.3.1" dependencies: - cross-spawn: "npm:^7.0.0" + cross-spawn: "npm:^7.0.6" signal-exit: "npm:^4.0.1" - checksum: 10/087edd44857d258c4f73ad84cb8df980826569656f2550c341b27adf5335354393eec24ea2fabd43a253233fb27cee177ebe46bd0b7ea129c77e87cb1e9936fb - languageName: node - linkType: hard - -"forever-agent@npm:~0.6.1": - version: 0.6.1 - resolution: "forever-agent@npm:0.6.1" - checksum: 10/c1e1644d5e074ac063ecbc3fb8582013ef91fff0e3fa41e76db23d2f62bc6d9677aac86db950917deed4fe1fdd772df780cfaa352075f23deec9c015313afb97 + checksum: 10/427b33f997a98073c0424e5c07169264a62cda806d8d2ded159b5b903fdfc8f0a1457e06b5fc35506497acb3f1e353f025edee796300209ac6231e80edece835 languageName: node linkType: hard @@ -30757,29 +30973,6 @@ __metadata: languageName: node linkType: hard -"fork-ts-checker-webpack-plugin@npm:^8.0.0": - version: 8.0.0 - resolution: "fork-ts-checker-webpack-plugin@npm:8.0.0" - dependencies: - "@babel/code-frame": "npm:^7.16.7" - chalk: "npm:^4.1.2" - chokidar: "npm:^3.5.3" - cosmiconfig: "npm:^7.0.1" - deepmerge: "npm:^4.2.2" - fs-extra: "npm:^10.0.0" - memfs: "npm:^3.4.1" - minimatch: "npm:^3.0.4" - node-abort-controller: "npm:^3.0.1" - schema-utils: "npm:^3.1.1" - semver: "npm:^7.3.5" - tapable: "npm:^2.2.1" - peerDependencies: - typescript: ">3.6.0" - webpack: ^5.11.0 - checksum: 10/49be81a780271b0e1f8c230c32ddb54d7e7ca562565b29811a94d5e679607b947850b133c3bb63057a71e67ac717c6c86c6d0390d1ecc885b506ae9158cf9bb5 - languageName: node - linkType: hard - "fork-ts-checker-webpack-plugin@npm:^9.0.0": version: 9.0.2 resolution: "fork-ts-checker-webpack-plugin@npm:9.0.2" @@ -30821,25 +31014,16 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0": - version: 4.0.0 - resolution: "form-data@npm:4.0.0" +"form-data@npm:^4.0.0, form-data@npm:^4.0.4": + version: 4.0.4 + resolution: "form-data@npm:4.0.4" dependencies: asynckit: "npm:^0.4.0" combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + hasown: "npm:^2.0.2" mime-types: "npm:^2.1.12" - checksum: 10/7264aa760a8cf09482816d8300f1b6e2423de1b02bba612a136857413fdc96d7178298ced106817655facc6b89036c6e12ae31c9eb5bdc16aabf502ae8a5d805 - languageName: node - linkType: hard - -"form-data@npm:~2.3.2": - version: 2.3.3 - resolution: "form-data@npm:2.3.3" - dependencies: - asynckit: "npm:^0.4.0" - combined-stream: "npm:^1.0.6" - mime-types: "npm:^2.1.12" - checksum: 10/1b6f3ccbf4540e535887b42218a2431a3f6cfdea320119c2affa2a7a374ad8fdd1e60166fc865181f45d49b1684c3e90e7b2190d3fe016692957afb9cf0d0d02 + checksum: 10/a4b62e21932f48702bc468cc26fb276d186e6b07b557e3dd7cc455872bdbb82db7db066844a64ad3cf40eaf3a753c830538183570462d3649fdfd705601cbcfb languageName: node linkType: hard @@ -30860,7 +31044,16 @@ __metadata: languageName: node linkType: hard -"formidable@npm:^3.5.1": +"formdata-polyfill@npm:^4.0.10": + version: 4.0.10 + resolution: "formdata-polyfill@npm:4.0.10" + dependencies: + fetch-blob: "npm:^3.1.2" + checksum: 10/9b5001d2edef3c9449ac3f48bd4f8cc92e7d0f2e7c1a5c8ba555ad4e77535cc5cf621fabe49e97f304067037282dd9093b9160a3cb533e46420b446c4e6bc06f + languageName: node + linkType: hard + +"formidable@npm:^3.5.4": version: 3.5.4 resolution: "formidable@npm:3.5.4" dependencies: @@ -30922,6 +31115,13 @@ __metadata: languageName: node linkType: hard +"fresh@npm:^2.0.0": + version: 2.0.0 + resolution: "fresh@npm:2.0.0" + checksum: 10/44e1468488363074641991c1340d2a10c5a6f6d7c353d89fd161c49d120c58ebf9890720f7584f509058385836e3ce50ddb60e9f017315a4ba8c6c3461813bfc + languageName: node + linkType: hard + "from2@npm:^2.3.0": version: 2.3.0 resolution: "from2@npm:2.3.0" @@ -30946,14 +31146,14 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:10.1.0, fs-extra@npm:^10.0.0": - version: 10.1.0 - resolution: "fs-extra@npm:10.1.0" +"fs-extra@npm:11.3.0": + version: 11.3.0 + resolution: "fs-extra@npm:11.3.0" dependencies: graceful-fs: "npm:^4.2.0" jsonfile: "npm:^6.0.1" universalify: "npm:^2.0.0" - checksum: 10/05ce2c3b59049bcb7b52001acd000e44b3c4af4ec1f8839f383ef41ec0048e3cfa7fd8a637b1bddfefad319145db89be91f4b7c1db2908205d38bf91e7d1d3b7 + checksum: 10/c9fe7b23dded1efe7bbae528d685c3206477e20cc60e9aaceb3f024f9b9ff2ee1f62413c161cb88546cc564009ab516dec99e9781ba782d869bb37e4fe04a97f languageName: node linkType: hard @@ -30969,14 +31169,25 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^11.0.0, fs-extra@npm:^11.1.0, fs-extra@npm:^11.2.0": - version: 11.2.0 - resolution: "fs-extra@npm:11.2.0" +"fs-extra@npm:^10.0.0": + version: 10.1.0 + resolution: "fs-extra@npm:10.1.0" dependencies: graceful-fs: "npm:^4.2.0" jsonfile: "npm:^6.0.1" universalify: "npm:^2.0.0" - checksum: 10/0579bf6726a4cd054d4aa308f10b483f52478bb16284f32cf60b4ce0542063d551fca1a08a2af365e35db21a3fa5a06cf2a6ed614004b4368982bc754cb816b3 + checksum: 10/05ce2c3b59049bcb7b52001acd000e44b3c4af4ec1f8839f383ef41ec0048e3cfa7fd8a637b1bddfefad319145db89be91f4b7c1db2908205d38bf91e7d1d3b7 + languageName: node + linkType: hard + +"fs-extra@npm:^11.0.0, fs-extra@npm:^11.1.0, fs-extra@npm:^11.2.0": + version: 11.3.1 + resolution: "fs-extra@npm:11.3.1" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10/2b893213411b1da11f9b061ccb0bcff4d6dd66fe90aa8f5b1616219a5e7ca659da869f454ebd8e94aa21c58342730fb43a2e5c98b5c6c5124f0c54a4633f64b0 languageName: node linkType: hard @@ -31044,7 +31255,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": +"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -31063,7 +31274,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin<compat/fsevents>, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin<compat/fsevents>": +"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin<compat/fsevents>, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin<compat/fsevents>, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin<compat/fsevents>": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin<compat/fsevents>::version=2.3.3&hash=df0bf1" dependencies: @@ -31141,7 +31352,7 @@ __metadata: languageName: node linkType: hard -"gaxios@npm:^6.0.0, gaxios@npm:^6.0.2, gaxios@npm:^6.0.3, gaxios@npm:^6.1.1": +"gaxios@npm:^6.0.0, gaxios@npm:^6.0.2, gaxios@npm:^6.1.1": version: 6.7.1 resolution: "gaxios@npm:6.7.1" dependencies: @@ -31154,6 +31365,17 @@ __metadata: languageName: node linkType: hard +"gaxios@npm:^7.0.0, gaxios@npm:^7.0.0-rc.4": + version: 7.1.1 + resolution: "gaxios@npm:7.1.1" + dependencies: + extend: "npm:^3.0.2" + https-proxy-agent: "npm:^7.0.1" + node-fetch: "npm:^3.3.2" + checksum: 10/9e5fa8b458c318a95d4dff0f6ac187a1b8933fb1de5b376b7098b27dfc5bf6025b62c87ed20bdae0496ae73a279834bc6b974c28849a674deed0089f2ba57b98 + languageName: node + linkType: hard + "gcp-metadata@npm:^6.0.0, gcp-metadata@npm:^6.1.0": version: 6.1.0 resolution: "gcp-metadata@npm:6.1.0" @@ -31164,6 +31386,17 @@ __metadata: languageName: node linkType: hard +"gcp-metadata@npm:^7.0.0": + version: 7.0.1 + resolution: "gcp-metadata@npm:7.0.1" + dependencies: + gaxios: "npm:^7.0.0" + google-logging-utils: "npm:^1.0.0" + json-bigint: "npm:^1.0.0" + checksum: 10/c82f20a4ce22278998fe033e668a66bff04d2b3e95e19f968adeac829e12274e07b453fcfcf34573a6d702b3570c5556cba6eb6b59d1c03757c866e3271972c1 + languageName: node + linkType: hard + "generate-function@npm:^2.3.1": version: 2.3.1 resolution: "generate-function@npm:2.3.1" @@ -31196,6 +31429,16 @@ __metadata: languageName: node linkType: hard +"get-amd-module-type@npm:^6.0.1": + version: 6.0.1 + resolution: "get-amd-module-type@npm:6.0.1" + dependencies: + ast-module-types: "npm:^6.0.1" + node-source-walk: "npm:^7.0.1" + checksum: 10/775e60d53f3c0aa4747c98ae2055957b688ffcd47944bc6d8af8ab7c1ab0b337e3d4a5624f29d815fe91e3da1830b8f9bad07d0da7025368e6e144205712b536 + languageName: node + linkType: hard + "get-caller-file@npm:^2.0.5": version: 2.0.5 resolution: "get-caller-file@npm:2.0.5" @@ -31235,6 +31478,13 @@ __metadata: languageName: node linkType: hard +"get-own-enumerable-property-symbols@npm:^3.0.0": + version: 3.0.2 + resolution: "get-own-enumerable-property-symbols@npm:3.0.2" + checksum: 10/8f0331f14159f939830884799f937343c8c0a2c330506094bc12cbee3665d88337fe97a4ea35c002cc2bdba0f5d9975ad7ec3abb925015cdf2a93e76d4759ede + languageName: node + linkType: hard + "get-package-type@npm:^0.1.0": version: 0.1.0 resolution: "get-package-type@npm:0.1.0" @@ -31347,15 +31597,6 @@ __metadata: languageName: node linkType: hard -"getpass@npm:^0.1.1": - version: 0.1.7 - resolution: "getpass@npm:0.1.7" - dependencies: - assert-plus: "npm:^1.0.0" - checksum: 10/ab18d55661db264e3eac6012c2d3daeafaab7a501c035ae0ccb193c3c23e9849c6e29b6ac762b9c2adae460266f925d55a3a2a3a3c8b94be2f222df94d70c046 - languageName: node - linkType: hard - "git-hooks-list@npm:^3.0.0": version: 3.1.0 resolution: "git-hooks-list@npm:3.1.0" @@ -31432,15 +31673,19 @@ __metadata: languageName: node linkType: hard -"glob@npm:9.3.5": - version: 9.3.5 - resolution: "glob@npm:9.3.5" +"glob@npm:11.0.3, glob@npm:^11.0.1": + version: 11.0.3 + resolution: "glob@npm:11.0.3" dependencies: - fs.realpath: "npm:^1.0.0" - minimatch: "npm:^8.0.2" - minipass: "npm:^4.2.4" - path-scurry: "npm:^1.6.1" - checksum: 10/e5fa8a58adf53525bca42d82a1fad9e6800032b7e4d372209b80cfdca524dd9a7dbe7d01a92d7ed20d89c572457f12c250092bc8817cb4f1c63efefdf9b658c0 + foreground-child: "npm:^3.3.1" + jackspeak: "npm:^4.1.1" + minimatch: "npm:^10.0.3" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^2.0.0" + bin: + glob: dist/esm/bin.mjs + checksum: 10/2ae536c1360c0266b523b2bfa6aadc10144a8b7e08869b088e37ac3c27cd30774f82e4bfb291cde796776e878f9e13200c7ff44010eb7054e00f46f649397893 languageName: node linkType: hard @@ -31460,23 +31705,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^11.0.1": - version: 11.0.2 - resolution: "glob@npm:11.0.2" - dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^4.0.1" - minimatch: "npm:^10.0.0" - minipass: "npm:^7.1.2" - package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^2.0.0" - bin: - glob: dist/esm/bin.mjs - checksum: 10/53501530240150fdceb9ace47ab856acd1e0d598f8101b0760b665fc11dae2160d366563b89232ae4f5df7ddba8f7c92294719268fe932bd3a32d16cc58c3d02 - languageName: node - linkType: hard - -"glob@npm:^7.0.0, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.1.7": +"glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.1.7, glob@npm:^7.2.3": version: 7.2.3 resolution: "glob@npm:7.2.3" dependencies: @@ -31570,13 +31799,6 @@ __metadata: languageName: node linkType: hard -"globals@npm:^11.1.0": - version: 11.12.0 - resolution: "globals@npm:11.12.0" - checksum: 10/9f054fa38ff8de8fa356502eb9d2dae0c928217b8b5c8de1f09f5c9b6c8a96d8b9bd3afc49acbcd384a98a81fea713c859e1b09e214c60509517bb8fc2bc13c2 - languageName: node - linkType: hard - "globals@npm:^13.19.0": version: 13.24.0 resolution: "globals@npm:13.24.0" @@ -31645,6 +31867,17 @@ __metadata: languageName: node linkType: hard +"gonzales-pe@npm:^4.3.0": + version: 4.3.0 + resolution: "gonzales-pe@npm:4.3.0" + dependencies: + minimist: "npm:^1.2.5" + bin: + gonzales: bin/gonzales.js + checksum: 10/d1676546bcaa4cb1c6c1fc5de5d62e85960665a13a4c489b02baeb58a10c53a249beef05ceaf21ea801813a559ff17d7b61158aa417211c135bcb8bdcb1701ca + languageName: node + linkType: hard + "goober@npm:^2.0.33": version: 2.1.14 resolution: "goober@npm:2.1.14" @@ -31654,7 +31887,22 @@ __metadata: languageName: node linkType: hard -"google-auth-library@npm:^9.0.0, google-auth-library@npm:^9.2.0, google-auth-library@npm:^9.3.0, google-auth-library@npm:^9.6.3, google-auth-library@npm:^9.7.0": +"google-auth-library@npm:^10.0.0, google-auth-library@npm:^10.0.0-rc.1": + version: 10.1.0 + resolution: "google-auth-library@npm:10.1.0" + dependencies: + base64-js: "npm:^1.3.0" + ecdsa-sig-formatter: "npm:^1.0.11" + gaxios: "npm:^7.0.0" + gcp-metadata: "npm:^7.0.0" + google-logging-utils: "npm:^1.0.0" + gtoken: "npm:^8.0.0" + jws: "npm:^4.0.0" + checksum: 10/f3e2130d38fe12045dd20480b4ea5f78a6a058d918e56d4332858c287f9b010b6aca312058b9c032843396da0fbaded6e5475866895eed461c7a06e2dd646b61 + languageName: node + linkType: hard + +"google-auth-library@npm:^9.0.0, google-auth-library@npm:^9.3.0, google-auth-library@npm:^9.6.3": version: 9.15.1 resolution: "google-auth-library@npm:9.15.1" dependencies: @@ -31688,6 +31936,13 @@ __metadata: languageName: node linkType: hard +"google-logging-utils@npm:^1.0.0": + version: 1.1.1 + resolution: "google-logging-utils@npm:1.1.1" + checksum: 10/51f915d97a812af9edeccf24d43bbec5f4d98dab4f874a08083e847d495fc94185d309f21ae7f590c9f7951bc25bc3095fa4ae73113aba47e86482f69860a806 + languageName: node + linkType: hard + "google-protobuf@npm:^3.15.8, google-protobuf@npm:^3.21.2": version: 3.21.2 resolution: "google-protobuf@npm:3.21.2" @@ -31695,17 +31950,16 @@ __metadata: languageName: node linkType: hard -"googleapis-common@npm:^7.0.0": - version: 7.2.0 - resolution: "googleapis-common@npm:7.2.0" +"googleapis-common@npm:^8.0.2-rc.0": + version: 8.0.2-rc.0 + resolution: "googleapis-common@npm:8.0.2-rc.0" dependencies: extend: "npm:^3.0.2" - gaxios: "npm:^6.0.3" - google-auth-library: "npm:^9.7.0" + gaxios: "npm:^7.0.0-rc.4" + google-auth-library: "npm:^10.0.0-rc.1" qs: "npm:^6.7.0" url-template: "npm:^2.0.8" - uuid: "npm:^9.0.0" - checksum: 10/4b914be6681f2a5a02bd0954a4a5cee1725d8623cb9d0a7c2fd7132de110e8d5707566cba39784e58147be39e74bc5513ad30fdcdaa6edcbb47ecf687003cb6c + checksum: 10/f630e8f37b2baa4980e3a66fb7736caa2a47f1f915bc6082ef844aa24be776ad2fbff39631346dcccc01c99dd5707fca02c786c6ba23ae459bcb23b125b4fb63 languageName: node linkType: hard @@ -31791,15 +32045,6 @@ __metadata: languageName: node linkType: hard -"graphlib@npm:^2.1.8": - version: 2.1.8 - resolution: "graphlib@npm:2.1.8" - dependencies: - lodash: "npm:^4.17.15" - checksum: 10/37cbd851d3c1fb99f3174750ccaa22305d23d11746e5df81a38ba3bf25c0ba29cd9658ba69a0159ea81d56c28e8e875033eeaaa7167d838419fae08d9cd2c62c - languageName: node - linkType: hard - "graphql-config@npm:^5.0.2": version: 5.1.5 resolution: "graphql-config@npm:5.1.5" @@ -31935,6 +32180,16 @@ __metadata: languageName: node linkType: hard +"gtoken@npm:^8.0.0": + version: 8.0.0 + resolution: "gtoken@npm:8.0.0" + dependencies: + gaxios: "npm:^7.0.0" + jws: "npm:^4.0.0" + checksum: 10/b921430395dcd06ee63c3fc5a5e339ca4d6dcb38b6d618beb0f260bae1088d53d130f86029a9d578f1601c64685f49a65dba57bbd617c4b14039180b67b6c5ce + languageName: node + linkType: hard + "gzip-size@npm:^6.0.0": version: 6.0.0 resolution: "gzip-size@npm:6.0.0" @@ -31976,16 +32231,6 @@ __metadata: languageName: node linkType: hard -"har-validator@npm:~5.1.3": - version: 5.1.3 - resolution: "har-validator@npm:5.1.3" - dependencies: - ajv: "npm:^6.5.5" - har-schema: "npm:^2.0.0" - checksum: 10/5903ddf55f4403bb102a86dc2da073593716c7aa422863c244cb406b69e006551553c904e30ed5d123788675ae827f977b3b366211dc730b33a2b619f926199f - languageName: node - linkType: hard - "harmony-reflect@npm:^1.4.6": version: 1.6.1 resolution: "harmony-reflect@npm:1.6.1" @@ -32082,7 +32327,7 @@ __metadata: languageName: node linkType: hard -"hasown@npm:^2.0.0, hasown@npm:^2.0.1, hasown@npm:^2.0.2": +"hasown@npm:^2.0.0, hasown@npm:^2.0.2": version: 2.0.2 resolution: "hasown@npm:2.0.2" dependencies: @@ -32305,7 +32550,7 @@ __metadata: languageName: node linkType: hard -"html-webpack-plugin@npm:^5.5.0, html-webpack-plugin@npm:^5.6.3": +"html-webpack-plugin@npm:^5.6.3": version: 5.6.3 resolution: "html-webpack-plugin@npm:5.6.3" dependencies: @@ -32475,7 +32720,7 @@ __metadata: languageName: node linkType: hard -"http-proxy-middleware@npm:^2.0.0, http-proxy-middleware@npm:^2.0.6, http-proxy-middleware@npm:^2.0.7": +"http-proxy-middleware@npm:^2.0.0, http-proxy-middleware@npm:^2.0.6, http-proxy-middleware@npm:^2.0.9": version: 2.0.9 resolution: "http-proxy-middleware@npm:2.0.9" dependencies: @@ -32511,17 +32756,6 @@ __metadata: languageName: node linkType: hard -"http-signature@npm:~1.2.0": - version: 1.2.0 - resolution: "http-signature@npm:1.2.0" - dependencies: - assert-plus: "npm:^1.0.0" - jsprim: "npm:^1.2.2" - sshpk: "npm:^1.7.0" - checksum: 10/2ff7112e6b0d8f08b382dfe705078c655501f2ddd76cf589d108445a9dd388a0a9be928c37108261519a7f53e6bbd1651048d74057b804807cce1ec49e87a95b - languageName: node - linkType: hard - "http2-wrapper@npm:^1.0.0-beta.5.2": version: 1.0.0-beta.5.2 resolution: "http2-wrapper@npm:1.0.0-beta.5.2" @@ -32559,13 +32793,13 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^7.0.0, https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.3, https-proxy-agent@npm:^7.0.5": - version: 7.0.5 - resolution: "https-proxy-agent@npm:7.0.5" +"https-proxy-agent@npm:^7.0.0, https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.5, https-proxy-agent@npm:^7.0.6": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" dependencies: - agent-base: "npm:^7.0.2" + agent-base: "npm:^7.1.2" debug: "npm:4" - checksum: 10/6679d46159ab3f9a5509ee80c3a3fc83fba3a920a5e18d32176c3327852c3c00ad640c0c4210a8fd70ea3c4a6d3a1b375bf01942516e7df80e2646bdc77658ab + checksum: 10/784b628cbd55b25542a9d85033bdfd03d4eda630fb8b3c9477959367f3be95dc476ed2ecbb9836c359c7c698027fc7b45723a302324433590f45d6c1706e8c13 languageName: node linkType: hard @@ -32593,9 +32827,9 @@ __metadata: linkType: hard "humanize-duration@npm:^3.25.1": - version: 3.32.1 - resolution: "humanize-duration@npm:3.32.1" - checksum: 10/5909107485c33d0c025e5d15a45b2700f91c9efc1e88510867926b3d1ef24d2d0c8bf31f52abef92da53b29e69410c5acb3a4d6d72429bd8b61d82ac25739ce4 + version: 3.32.2 + resolution: "humanize-duration@npm:3.32.2" + checksum: 10/2dfe847085c7586d2cc83613b8643a4cca70966a16522e3786be07f0283087502e5340e98a79867c7b019ea22ae85c3fa9d1ca1be587eb19ccf03710c986d41d languageName: node linkType: hard @@ -32617,6 +32851,13 @@ __metadata: languageName: node linkType: hard +"hyperdyperid@npm:^1.2.0": + version: 1.2.0 + resolution: "hyperdyperid@npm:1.2.0" + checksum: 10/64abb5568ff17aa08ac0175ae55e46e22831c5552be98acdd1692081db0209f36fff58b31432017b4e1772c178962676a2cc3c54e4d5d7f020d7710cec7ad7a6 + languageName: node + linkType: hard + "hyphenate-style-name@npm:^1.0.3": version: 1.0.3 resolution: "hyphenate-style-name@npm:1.0.3" @@ -33015,26 +33256,9 @@ __metadata: languageName: node linkType: hard -"ioredis@npm:^5.4.1": - version: 5.4.1 - resolution: "ioredis@npm:5.4.1" - dependencies: - "@ioredis/commands": "npm:^1.1.1" - cluster-key-slot: "npm:^1.1.0" - debug: "npm:^4.3.4" - denque: "npm:^2.1.0" - lodash.defaults: "npm:^4.2.0" - lodash.isarguments: "npm:^3.1.0" - redis-errors: "npm:^1.2.0" - redis-parser: "npm:^3.0.0" - standard-as-callback: "npm:^2.1.0" - checksum: 10/9043b812ac58065e80c759d130602cc64490fcaeaacf93723453fda04c7ba61dab0e2f50380eacb045592378ededf44f270c0d43e13e3e8b8d7c5a8d7fecb823 - languageName: node - linkType: hard - -"iovalkey@npm:^0.3.1": - version: 0.3.1 - resolution: "iovalkey@npm:0.3.1" +"iovalkey@npm:^0.3.3": + version: 0.3.3 + resolution: "iovalkey@npm:0.3.3" dependencies: "@iovalkey/commands": "npm:^0.1.0" cluster-key-slot: "npm:^1.1.0" @@ -33045,7 +33269,7 @@ __metadata: redis-errors: "npm:^1.2.0" redis-parser: "npm:^3.0.0" standard-as-callback: "npm:^2.1.0" - checksum: 10/afe5e0218810d902263dca2b22dd4501fb74698111f1850804d0948bd6a97793a7f5006757f9b6e8c8131bac6bd532d07ad971e7776bed7f6dc1f6e471706c53 + checksum: 10/39368842e6a9dbc85e92dfcd4c1c917a76240bac5c4210e19a0bd3bc7984af9b567dce5de7c560c4517d5d4cb03af229f38582c1dd055c6fd452060774318e8c languageName: node linkType: hard @@ -33209,7 +33433,7 @@ __metadata: languageName: node linkType: hard -"is-callable@npm:^1.1.3, is-callable@npm:^1.1.5, is-callable@npm:^1.2.7": +"is-callable@npm:^1.1.5, is-callable@npm:^1.2.7": version: 1.2.7 resolution: "is-callable@npm:1.2.7" checksum: 10/48a9297fb92c99e9df48706241a189da362bff3003354aea4048bd5f7b2eb0d823cd16d0a383cece3d76166ba16d85d9659165ac6fcce1ac12e6c649d66dbdb9 @@ -33497,6 +33721,13 @@ __metadata: languageName: node linkType: hard +"is-obj@npm:^1.0.1": + version: 1.0.1 + resolution: "is-obj@npm:1.0.1" + checksum: 10/3ccf0efdea12951e0b9c784e2b00e77e87b2f8bd30b42a498548a8afcc11b3287342a2030c308e473e93a7a19c9ea7854c99a8832a476591c727df2a9c79796c + languageName: node + linkType: hard + "is-obj@npm:^2.0.0": version: 2.0.0 resolution: "is-obj@npm:2.0.0" @@ -33618,6 +33849,13 @@ __metadata: languageName: node linkType: hard +"is-regexp@npm:^1.0.0": + version: 1.0.0 + resolution: "is-regexp@npm:1.0.0" + checksum: 10/be692828e24cba479ec33644326fa98959ec68ba77965e0291088c1a741feaea4919d79f8031708f85fd25e39de002b4520622b55460660b9c369e6f7187faef + languageName: node + linkType: hard + "is-resolvable@npm:^1.1.0": version: 1.1.0 resolution: "is-resolvable@npm:1.1.0" @@ -33740,7 +33978,7 @@ __metadata: languageName: node linkType: hard -"is-typedarray@npm:^1.0.0, is-typedarray@npm:~1.0.0": +"is-typedarray@npm:^1.0.0": version: 1.0.0 resolution: "is-typedarray@npm:1.0.0" checksum: 10/4b433bfb0f9026f079f4eb3fbaa4ed2de17c9995c3a0b5c800bec40799b4b2a8b4e051b1ada77749deb9ded4ae52fe2096973f3a93ff83df1a5a7184a669478c @@ -33754,6 +33992,13 @@ __metadata: languageName: node linkType: hard +"is-url-superb@npm:^4.0.0": + version: 4.0.0 + resolution: "is-url-superb@npm:4.0.0" + checksum: 10/fd55e91c96349acb0d688f95fcb1ac67450e5db934976e3a8ff13ef446841e779a6f4d18b15f02331f05a3429c8fdaba2382ac1ab444059e86e9ffcde1ec8db0 + languageName: node + linkType: hard + "is-url@npm:^1.2.4": version: 1.2.4 resolution: "is-url@npm:1.2.4" @@ -33968,13 +34213,6 @@ __metadata: languageName: node linkType: hard -"isstream@npm:~0.1.2": - version: 0.1.2 - resolution: "isstream@npm:0.1.2" - checksum: 10/22d9c181015226d4534a227539256897bbbcb7edd1066ca4fc4d3a06dbd976325dfdd16b3983c7d236a89f256805c1a685a772e0364e98873d3819b064ad35a1 - languageName: node - linkType: hard - "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.0 resolution: "istanbul-lib-coverage@npm:3.2.0" @@ -34098,12 +34336,12 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^4.0.1": - version: 4.0.2 - resolution: "jackspeak@npm:4.0.2" +"jackspeak@npm:^4.1.1": + version: 4.1.1 + resolution: "jackspeak@npm:4.1.1" dependencies: "@isaacs/cliui": "npm:^8.0.2" - checksum: 10/d9722f0e55f6c322c57aedf094c405f4201b834204629817187953988075521cfddb23df83e2a7b845723ca7eb0555068c5ce1556732e9c275d32a531881efa8 + checksum: 10/ffceb270ec286841f48413bfb4a50b188662dfd599378ce142b6540f3f0a66821dc9dcb1e9ebc55c6c3b24dc2226c96e5819ba9bd7a241bd29031b61911718c7 languageName: node linkType: hard @@ -34771,13 +35009,6 @@ __metadata: languageName: node linkType: hard -"jsbn@npm:~0.1.0": - version: 0.1.1 - resolution: "jsbn@npm:0.1.1" - checksum: 10/5450133242845100e694f0ef9175f44c012691a9b770b2571e677314e6f70600abb10777cdfc9a0c6a9f2ac6d134577403633de73e2fcd0f97875a67744e2d14 - languageName: node - linkType: hard - "jscodeshift-add-imports@npm:^1.0.10": version: 1.0.11 resolution: "jscodeshift-add-imports@npm:1.0.11" @@ -34930,15 +35161,6 @@ __metadata: languageName: node linkType: hard -"jsesc@npm:~0.5.0": - version: 0.5.0 - resolution: "jsesc@npm:0.5.0" - bin: - jsesc: bin/jsesc - checksum: 10/fab949f585c71e169c5cbe00f049f20de74f067081bbd64a55443bad1c71e1b5a5b448f2359bf2fe06f5ed7c07e2e4a9101843b01c823c30b6afc11f5bfaf724 - languageName: node - linkType: hard - "json-bigint@npm:^1.0.0": version: 1.0.0 resolution: "json-bigint@npm:1.0.0" @@ -34955,7 +35177,7 @@ __metadata: languageName: node linkType: hard -"json-buffer@npm:3.0.1, json-buffer@npm:^3.0.1": +"json-buffer@npm:3.0.1": version: 3.0.1 resolution: "json-buffer@npm:3.0.1" checksum: 10/82876154521b7b68ba71c4f969b91572d1beabadd87bd3a6b236f85fbc7dc4695089191ed60bb59f9340993c51b33d479f45b6ba9f3548beb519705281c32c3c @@ -35057,7 +35279,7 @@ __metadata: languageName: node linkType: hard -"json-schema@npm:0.4.0, json-schema@npm:^0.4.0": +"json-schema@npm:^0.4.0": version: 0.4.0 resolution: "json-schema@npm:0.4.0" checksum: 10/8b3b64eff4a807dc2a3045b104ed1b9335cd8d57aa74c58718f07f0f48b8baa3293b00af4dcfbdc9144c3aafea1e97982cc27cc8e150fc5d93c540649507a458 @@ -35090,7 +35312,7 @@ __metadata: languageName: node linkType: hard -"json-stringify-safe@npm:^5.0.1, json-stringify-safe@npm:~5.0.1": +"json-stringify-safe@npm:^5.0.1": version: 5.0.1 resolution: "json-stringify-safe@npm:5.0.1" checksum: 10/59169a081e4eeb6f9559ae1f938f656191c000e0512aa6df9f3c8b2437a4ab1823819c6b9fd1818a4e39593ccfd72e9a051fdd3e2d1e340ed913679e888ded8c @@ -35193,13 +35415,6 @@ __metadata: languageName: node linkType: hard -"jsonpath-plus@npm:^7.2.0": - version: 7.2.0 - resolution: "jsonpath-plus@npm:7.2.0" - checksum: 10/f602445b1aa2d55abc2875859fd948f942980ef6400ca2a0362c7a6aa6f912467865262f4d092e04a16889fa74f0dbf6fd67b9dc9583485a5059be6e0a62c6c2 - languageName: node - linkType: hard - "jsonpath@npm:^1.1.1": version: 1.1.1 resolution: "jsonpath@npm:1.1.1" @@ -35237,18 +35452,6 @@ __metadata: languageName: node linkType: hard -"jsprim@npm:^1.2.2": - version: 1.4.2 - resolution: "jsprim@npm:1.4.2" - dependencies: - assert-plus: "npm:1.0.0" - extsprintf: "npm:1.3.0" - json-schema: "npm:0.4.0" - verror: "npm:1.10.0" - checksum: 10/df2bf234eab1b5078d01bcbff3553d50a243f7b5c10a169745efeda6344d62798bd1d85bcca6a8446f3b5d0495e989db45f9de8dae219f0f9796e70e0c776089 - languageName: node - linkType: hard - "jss-plugin-camel-case@npm:^10.10.0, jss-plugin-camel-case@npm:^10.5.1": version: 10.10.0 resolution: "jss-plugin-camel-case@npm:10.10.0" @@ -35436,6 +35639,13 @@ __metadata: languageName: node linkType: hard +"kafkajs@npm:^2.2.4": + version: 2.2.4 + resolution: "kafkajs@npm:2.2.4" + checksum: 10/75eb0d221397085f90e51f8a2d752495c9fa9c1b3a1a6db610cd7074fa8c52777f295832fd0a7c49cded5e574337a09fafa8c3f7cf1caa38f4dc9aa20fcfb7df + languageName: node + linkType: hard + "keygrip@npm:~1.1.0": version: 1.1.0 resolution: "keygrip@npm:1.1.0" @@ -35446,11 +35656,11 @@ __metadata: linkType: hard "keyv@npm:*, keyv@npm:^5.2.1": - version: 5.2.3 - resolution: "keyv@npm:5.2.3" + version: 5.3.4 + resolution: "keyv@npm:5.3.4" dependencies: - "@keyv/serialize": "npm:^1.0.2" - checksum: 10/47b4e9deb33e6a80e5ea79f3022ed3a14bc9fe553b7527ffff0a70b10c7a6c1a5d7e49b9bcfdbd8e8b9fb4632d68baa19d09e82628bcf853103e750e56d49a9e + "@keyv/serialize": "npm:^1.0.3" + checksum: 10/3e294eb1168af78ad3430d0cc47b6839fd3e70593238d35226a0a1e4094abe397ea378b7bce35cfcb314e55b5d1b4fcdae3c19bee5610e78d283b3cb5b279c8b languageName: node linkType: hard @@ -35463,7 +35673,7 @@ __metadata: languageName: node linkType: hard -"keyv@npm:^4.0.0, keyv@npm:^4.5.2": +"keyv@npm:^4.0.0": version: 4.5.4 resolution: "keyv@npm:4.5.4" dependencies: @@ -35776,91 +35986,91 @@ __metadata: languageName: node linkType: hard -"lightningcss-darwin-arm64@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-darwin-arm64@npm:1.29.3" +"lightningcss-darwin-arm64@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-darwin-arm64@npm:1.30.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"lightningcss-darwin-x64@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-darwin-x64@npm:1.29.3" +"lightningcss-darwin-x64@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-darwin-x64@npm:1.30.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"lightningcss-freebsd-x64@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-freebsd-x64@npm:1.29.3" +"lightningcss-freebsd-x64@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-freebsd-x64@npm:1.30.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"lightningcss-linux-arm-gnueabihf@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-linux-arm-gnueabihf@npm:1.29.3" +"lightningcss-linux-arm-gnueabihf@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.30.1" conditions: os=linux & cpu=arm languageName: node linkType: hard -"lightningcss-linux-arm64-gnu@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-linux-arm64-gnu@npm:1.29.3" +"lightningcss-linux-arm64-gnu@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-linux-arm64-gnu@npm:1.30.1" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"lightningcss-linux-arm64-musl@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-linux-arm64-musl@npm:1.29.3" +"lightningcss-linux-arm64-musl@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-linux-arm64-musl@npm:1.30.1" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"lightningcss-linux-x64-gnu@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-linux-x64-gnu@npm:1.29.3" +"lightningcss-linux-x64-gnu@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-linux-x64-gnu@npm:1.30.1" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"lightningcss-linux-x64-musl@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-linux-x64-musl@npm:1.29.3" +"lightningcss-linux-x64-musl@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-linux-x64-musl@npm:1.30.1" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"lightningcss-win32-arm64-msvc@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-win32-arm64-msvc@npm:1.29.3" +"lightningcss-win32-arm64-msvc@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-win32-arm64-msvc@npm:1.30.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"lightningcss-win32-x64-msvc@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-win32-x64-msvc@npm:1.29.3" +"lightningcss-win32-x64-msvc@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-win32-x64-msvc@npm:1.30.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "lightningcss@npm:^1.29.1": - version: 1.29.3 - resolution: "lightningcss@npm:1.29.3" + version: 1.30.1 + resolution: "lightningcss@npm:1.30.1" dependencies: detect-libc: "npm:^2.0.3" - lightningcss-darwin-arm64: "npm:1.29.3" - lightningcss-darwin-x64: "npm:1.29.3" - lightningcss-freebsd-x64: "npm:1.29.3" - lightningcss-linux-arm-gnueabihf: "npm:1.29.3" - lightningcss-linux-arm64-gnu: "npm:1.29.3" - lightningcss-linux-arm64-musl: "npm:1.29.3" - lightningcss-linux-x64-gnu: "npm:1.29.3" - lightningcss-linux-x64-musl: "npm:1.29.3" - lightningcss-win32-arm64-msvc: "npm:1.29.3" - lightningcss-win32-x64-msvc: "npm:1.29.3" + lightningcss-darwin-arm64: "npm:1.30.1" + lightningcss-darwin-x64: "npm:1.30.1" + lightningcss-freebsd-x64: "npm:1.30.1" + lightningcss-linux-arm-gnueabihf: "npm:1.30.1" + lightningcss-linux-arm64-gnu: "npm:1.30.1" + lightningcss-linux-arm64-musl: "npm:1.30.1" + lightningcss-linux-x64-gnu: "npm:1.30.1" + lightningcss-linux-x64-musl: "npm:1.30.1" + lightningcss-win32-arm64-msvc: "npm:1.30.1" + lightningcss-win32-x64-msvc: "npm:1.30.1" dependenciesMeta: lightningcss-darwin-arm64: optional: true @@ -35882,7 +36092,7 @@ __metadata: optional: true lightningcss-win32-x64-msvc: optional: true - checksum: 10/90933a666975f8e4f2b1bed1ec698a0072f96d2cd118e358932456a46105d62b72b701e4a5da5bd66f0b6e3beac2c65d0be2306acdf3f30603d86143502cce88 + checksum: 10/6c921135216cc498dfcb87e35dffe8432e99306cbd58009c598b1daf20c81cc14535abbd4c1066e5d1faf4080ed44a2995e8ecc343633db4897a2d041b76fb05 languageName: node linkType: hard @@ -35925,20 +36135,20 @@ __metadata: languageName: node linkType: hard -"linkify-react@npm:4.1.3": - version: 4.1.3 - resolution: "linkify-react@npm:4.1.3" +"linkify-react@npm:4.3.2": + version: 4.3.2 + resolution: "linkify-react@npm:4.3.2" peerDependencies: linkifyjs: ^4.0.0 react: ">= 15.0.0" - checksum: 10/e21dec51798783288c08979a74d0b8b6ee87315205d9a6ca36d8dc062c967577184791ab6456bb70f3635c2d156930040de9c278a3ca6dc940a45ea3b5d184c4 + checksum: 10/cdf3942af0d291db6dc67adccbce381bbe7ce2eb5c2a06b9ee92a19323f540284e1768ef1df369b7aabc5d6894310addddafb8e53226f50d376b36cd4ddbd598 languageName: node linkType: hard -"linkifyjs@npm:4.1.3": - version: 4.1.3 - resolution: "linkifyjs@npm:4.1.3" - checksum: 10/7c17dac6a66fdea30e56b17d49d882a333833ec093993723738842b224c8cbd87bcddc6f51f2deac9529c868f162358d7acb0c44753b92832027ae761eceac1a +"linkifyjs@npm:4.3.2": + version: 4.3.2 + resolution: "linkifyjs@npm:4.3.2" + checksum: 10/b03477486658d1e5531bf65ee1fdc0f79423594e689184c67b8a63c75d9f35d1cd0344edd97d5799502cde4f3163d620e2cbd9e72ad718c6a95084177c004386 languageName: node linkType: hard @@ -35983,6 +36193,13 @@ __metadata: languageName: node linkType: hard +"load-esm@npm:1.0.2": + version: 1.0.2 + resolution: "load-esm@npm:1.0.2" + checksum: 10/1b4adb40c28c6fdbd4ca8c97942c04debddb3c93ae91413540ff5a21ca3511a651988c835cb80cad7288d1ecb869c4794b8a787ab02e09cc07ec951ad1eefcf9 + languageName: node + linkType: hard + "load-yaml-file@npm:^0.2.0": version: 0.2.0 resolution: "load-yaml-file@npm:0.2.0" @@ -36124,7 +36341,7 @@ __metadata: languageName: node linkType: hard -"lodash.debounce@npm:^4, lodash.debounce@npm:^4.0.8": +"lodash.debounce@npm:^4": version: 4.0.8 resolution: "lodash.debounce@npm:4.0.8" checksum: 10/cd0b2819786e6e80cb9f5cda26b1a8fc073daaf04e48d4cb462fa4663ec9adb3a5387aa22d7129e48eed1afa05b482e2a6b79bfc99b86886364449500cbb00fd @@ -36512,10 +36729,10 @@ __metadata: languageName: node linkType: hard -"loupe@npm:^3.1.0, loupe@npm:^3.1.1, loupe@npm:^3.1.2, loupe@npm:^3.1.3": - version: 3.1.3 - resolution: "loupe@npm:3.1.3" - checksum: 10/9e98c34daf0eba48ccc603595e51f2ae002110982d84879cf78c51de2c632f0c571dfe82ce4210af60c32203d06b443465c269bda925076fe6d9b612cc65c321 +"loupe@npm:^3.1.0, loupe@npm:^3.1.1, loupe@npm:^3.1.2, loupe@npm:^3.1.4": + version: 3.2.0 + resolution: "loupe@npm:3.2.0" + checksum: 10/80d48e35b014c2ba5886e25a02ee4cc9c1f659b0eca9c4fa8f07051cdc689e0507a763fbe05a63abbd3b7d4640774a722c905d4b1681b4b92c3ba8f87d96fea2 languageName: node linkType: hard @@ -36644,6 +36861,33 @@ __metadata: languageName: node linkType: hard +"madge@npm:^8.0.0": + version: 8.0.0 + resolution: "madge@npm:8.0.0" + dependencies: + chalk: "npm:^4.1.2" + commander: "npm:^7.2.0" + commondir: "npm:^1.0.1" + debug: "npm:^4.3.4" + dependency-tree: "npm:^11.0.0" + ora: "npm:^5.4.1" + pluralize: "npm:^8.0.0" + pretty-ms: "npm:^7.0.1" + rc: "npm:^1.2.8" + stream-to-array: "npm:^2.3.0" + ts-graphviz: "npm:^2.1.2" + walkdir: "npm:^0.4.1" + peerDependencies: + typescript: ^5.4.4 + peerDependenciesMeta: + typescript: + optional: true + bin: + madge: bin/cli.js + checksum: 10/9adee480224aadb1d24d36f583396053c3cea84359316b601f5bc423c67c87e81ba842425acaa6c764ccc8c00241e2e78700b16f9af546111d1106b70c336174 + languageName: node + linkType: hard + "magic-string@npm:^0.25.7": version: 0.25.7 resolution: "magic-string@npm:0.25.7" @@ -36653,12 +36897,21 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.10, magic-string@npm:^0.30.3, magic-string@npm:^0.30.5": - version: 0.30.14 - resolution: "magic-string@npm:0.30.14" +"magic-string@npm:^0.27.0": + version: 0.27.0 + resolution: "magic-string@npm:0.27.0" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.4.13" + checksum: 10/10a18a48d22fb14467d6cb4204aba58d6790ae7ba023835dc7a65e310cf216f042a17fab1155ba43e47117310a9b7c3fd3bb79f40be40f5124d6b1af9e96399b + languageName: node + linkType: hard + +"magic-string@npm:^0.30.0, magic-string@npm:^0.30.10, magic-string@npm:^0.30.17, magic-string@npm:^0.30.3": + version: 0.30.17 + resolution: "magic-string@npm:0.30.17" dependencies: "@jridgewell/sourcemap-codec": "npm:^1.5.0" - checksum: 10/8ca0f8937c2824e48ebc70e7e065a193c467713639cc6e5972aaba0fa5417b375a6f62c383410a19a66e618c386bb7253fbd3ccbfb0144bb310f0ba772121f12 + checksum: 10/2f71af2b0afd78c2e9012a29b066d2c8ba45a9cd0c8070f7fd72de982fb1c403b4e3afdb1dae00691d56885ede66b772ef6bedf765e02e3a7066208fe2fec4aa languageName: node linkType: hard @@ -36672,7 +36925,7 @@ __metadata: languageName: node linkType: hard -"make-dir@npm:^3.0.0, make-dir@npm:^3.0.2": +"make-dir@npm:^3.0.0": version: 3.1.0 resolution: "make-dir@npm:3.1.0" dependencies: @@ -37143,7 +37396,7 @@ __metadata: languageName: node linkType: hard -"memfs@npm:^3.1.2, memfs@npm:^3.4.1, memfs@npm:^3.4.12": +"memfs@npm:^3.1.2, memfs@npm:^3.4.1": version: 3.5.3 resolution: "memfs@npm:3.5.3" dependencies: @@ -37152,16 +37405,19 @@ __metadata: languageName: node linkType: hard -"memfs@npm:^4.6.0": - version: 4.7.7 - resolution: "memfs@npm:4.7.7" +"memfs@npm:^4.28.0, memfs@npm:^4.6.0": + version: 4.36.0 + resolution: "memfs@npm:4.36.0" dependencies: + "@jsonjoy.com/json-pack": "npm:^1.0.3" + "@jsonjoy.com/util": "npm:^1.3.0" + tree-dump: "npm:^1.0.1" tslib: "npm:^2.0.0" - checksum: 10/311633e5857c91f41021b43f00eda8d540fed2c2d9e02c780fe78de720cfb55d15ab2d5b5ce9f2576637589b82e84488f1b9ff503563e817ed65200ad24617fb + checksum: 10/4898187c3278bd127340ec3d6c51d1fa77faa50aad71a8a1e4b7c02abbccd8485dccc7f565291b386b0cc96e5ac06902e4645108fad44e458fe8c1dfee3caf42 languageName: node linkType: hard -"memjs@npm:^1.3.1, memjs@npm:^1.3.2": +"memjs@npm:^1.3.2": version: 1.3.2 resolution: "memjs@npm:1.3.2" checksum: 10/705bb9be88180685fa3bc71375dc8b3a188356ab8f5c6c5ea1761e5e0912a78520cb964d2afc24e65686d53b45d3a7d8b5e770b2f3cf071a6eaea7f8d0d6bbfd @@ -37207,6 +37463,13 @@ __metadata: languageName: node linkType: hard +"merge-descriptors@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-descriptors@npm:2.0.0" + checksum: 10/e383332e700a94682d0125a36c8be761142a1320fc9feeb18e6e36647c9edf064271645f5669b2c21cf352116e561914fd8aa831b651f34db15ef4038c86696a + languageName: node + linkType: hard + "merge-stream@npm:^2.0.0": version: 2.0.0 resolution: "merge-stream@npm:2.0.0" @@ -37590,13 +37853,20 @@ __metadata: languageName: node linkType: hard -"mime-db@npm:1.52.0, mime-db@npm:>= 1.43.0 < 2": +"mime-db@npm:1.52.0": version: 1.52.0 resolution: "mime-db@npm:1.52.0" checksum: 10/54bb60bf39e6f8689f6622784e668a3d7f8bed6b0d886f5c3c446cb3284be28b30bf707ed05d0fe44a036f8469976b2629bbea182684977b084de9da274694d7 languageName: node linkType: hard +"mime-db@npm:>= 1.43.0 < 2, mime-db@npm:^1.54.0": + version: 1.54.0 + resolution: "mime-db@npm:1.54.0" + checksum: 10/9e7834be3d66ae7f10eaa69215732c6d389692b194f876198dca79b2b90cbf96688d9d5d05ef7987b20f749b769b11c01766564264ea5f919c88b32a29011311 + languageName: node + linkType: hard + "mime-db@npm:~1.33.0": version: 1.33.0 resolution: "mime-db@npm:1.33.0" @@ -37622,7 +37892,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:2.1.35, mime-types@npm:^2.1.12, mime-types@npm:^2.1.18, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:^2.1.35, mime-types@npm:~2.1.17, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": +"mime-types@npm:2.1.35, mime-types@npm:^2.1.12, mime-types@npm:^2.1.18, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -37631,6 +37901,15 @@ __metadata: languageName: node linkType: hard +"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1": + version: 3.0.1 + resolution: "mime-types@npm:3.0.1" + dependencies: + mime-db: "npm:^1.54.0" + checksum: 10/fa1d3a928363723a8046c346d87bf85d35014dae4285ad70a3ff92bd35957992b3094f8417973cfe677330916c6ef30885109624f1fb3b1e61a78af509dba120 + languageName: node + linkType: hard + "mime@npm:1.6.0, mime@npm:^1.3.4": version: 1.6.0 resolution: "mime@npm:1.6.0" @@ -37715,14 +37994,14 @@ __metadata: linkType: hard "mini-css-extract-plugin@npm:^2.4.2, mini-css-extract-plugin@npm:^2.9.2": - version: 2.9.2 - resolution: "mini-css-extract-plugin@npm:2.9.2" + version: 2.9.3 + resolution: "mini-css-extract-plugin@npm:2.9.3" dependencies: schema-utils: "npm:^4.0.0" tapable: "npm:^2.2.1" peerDependencies: webpack: ^5.0.0 - checksum: 10/db6ddb8ba56affa1a295b57857d66bad435d36e48e1f95c75d16fadd6c70e3ba33e8c4141c3fb0e22b4d875315b41c4f58550c6ac73b50bdbe429f768297e3ff + checksum: 10/55bcc341af9e472c3b8010aa1a8a0212aef78eb8a76d07e3f9d5646b357af03ab4cae0dabfb03be8e0476064f36ab415be2fa65682b6acdb420805c67cdee4c6 languageName: node linkType: hard @@ -37767,12 +38046,12 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^10.0.0": - version: 10.0.1 - resolution: "minimatch@npm:10.0.1" +"minimatch@npm:^10.0.3": + version: 10.0.3 + resolution: "minimatch@npm:10.0.3" dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10/082e7ccbc090d5f8c4e4e029255d5a1d1e3af37bda837da2b8b0085b1503a1210c91ac90d9ebfe741d8a5f286ece820a1abb4f61dc1f82ce602a055d461d93f3 + "@isaacs/brace-expansion": "npm:^5.0.0" + checksum: 10/d5b8b2538b367f2cfd4aeef27539fddeee58d1efb692102b848e4a968a09780a302c530eb5aacfa8c57f7299155fb4b4e85219ad82664dcef5c66f657111d9b8 languageName: node linkType: hard @@ -37794,15 +38073,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^8.0.2": - version: 8.0.4 - resolution: "minimatch@npm:8.0.4" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10/aef05598ee565e1013bc8a10f53410ac681561f901c1a084b8ecfd016c9ed919f58f4bbd5b63e05643189dfb26e8106a84f0e1ff12e4a263aa37e1cae7ce9828 - languageName: node - linkType: hard - "minimatch@npm:^9.0.0, minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": version: 9.0.5 resolution: "minimatch@npm:9.0.5" @@ -37961,7 +38231,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^4.0.0, minipass@npm:^4.2.4": +"minipass@npm:^4.0.0": version: 4.2.8 resolution: "minipass@npm:4.2.8" checksum: 10/e148eb6dcb85c980234cad889139ef8ddf9d5bdac534f4f0268446c8792dd4c74f4502479be48de3c1cce2f6450f6da4d0d4a86405a8a12be04c1c36b339569a @@ -38020,7 +38290,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.4, mkdirp@npm:^0.5.5": +"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.5, mkdirp@npm:^0.5.6": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" dependencies: @@ -38157,6 +38427,18 @@ __metadata: languageName: node linkType: hard +"module-definition@npm:^6.0.1": + version: 6.0.1 + resolution: "module-definition@npm:6.0.1" + dependencies: + ast-module-types: "npm:^6.0.1" + node-source-walk: "npm:^7.0.1" + bin: + module-definition: bin/cli.js + checksum: 10/a61280434289b9c2209a0075e3754731c797dce1816f164995e8a94ba57d3b0cffd6ca5d719b26cdef31c5fb41176676d54f2dc83cc62f7e61576369a05bef7c + languageName: node + linkType: hard + "module-details-from-path@npm:^1.0.3": version: 1.0.3 resolution: "module-details-from-path@npm:1.0.3" @@ -38164,6 +38446,20 @@ __metadata: languageName: node linkType: hard +"module-lookup-amd@npm:^9.0.3": + version: 9.0.5 + resolution: "module-lookup-amd@npm:9.0.5" + dependencies: + commander: "npm:^12.1.0" + glob: "npm:^7.2.3" + requirejs: "npm:^2.3.7" + requirejs-config-file: "npm:^4.0.0" + bin: + lookup-amd: bin/cli.js + checksum: 10/9d1e001a0d59a9169437eb595d26ccf6c0d2905ab46d26bf819495afdea3c796252c0417a4af62b42ce5ae519cc6d66968419a3c7b70b0671bc158a4bc57375a + languageName: node + linkType: hard + "moo@npm:^0.5.0": version: 0.5.2 resolution: "moo@npm:0.5.2" @@ -38172,15 +38468,15 @@ __metadata: linkType: hard "morgan@npm:^1.10.0": - version: 1.10.0 - resolution: "morgan@npm:1.10.0" + version: 1.10.1 + resolution: "morgan@npm:1.10.1" dependencies: basic-auth: "npm:~2.0.1" debug: "npm:2.6.9" depd: "npm:~2.0.0" on-finished: "npm:~2.3.0" - on-headers: "npm:~1.0.2" - checksum: 10/4497ace00dac65318658595528c1924942c900aae88b7adc5e69e18dd78fb5d1fcccdc2048404ce7d88b5344dc088c492e3aa7cf8023f1e601c6b0f4ff806b93 + on-headers: "npm:~1.1.0" + checksum: 10/f6a611bdcb9bebe8283381c49efedee81f50b75f6cbc52430cda1743ec35443c92d5e5d4384ce38b102d8c102162c92da563471def3cf840b4980160f278f8ba languageName: node linkType: hard @@ -38254,14 +38550,14 @@ __metadata: linkType: hard "msw@npm:^2.0.0, msw@npm:^2.0.8": - version: 2.7.6 - resolution: "msw@npm:2.7.6" + version: 2.10.4 + resolution: "msw@npm:2.10.4" dependencies: "@bundled-es-modules/cookie": "npm:^2.0.1" "@bundled-es-modules/statuses": "npm:^1.0.1" "@bundled-es-modules/tough-cookie": "npm:^0.1.6" "@inquirer/confirm": "npm:^5.0.0" - "@mswjs/interceptors": "npm:^0.37.0" + "@mswjs/interceptors": "npm:^0.39.1" "@open-draft/deferred-promise": "npm:^2.2.0" "@open-draft/until": "npm:^2.1.0" "@types/cookie": "npm:^0.6.0" @@ -38282,22 +38578,22 @@ __metadata: optional: true bin: msw: cli/index.js - checksum: 10/23f3907b487102b395e2405ab8ae69c8cc74413485805a1039e1a3ab0b54b56947aa77b5ef5a15befad02a69aaa21a204ecfafd2e81d4fb1fd284cb99dfe2c0b + checksum: 10/e2f25dda1aba66c7444c29c41d3157cb15c0332055ab7ebfb74ef4b506e7b90098cf37c577768edb5b2b2dbf0d6ed6a7a3ca8ee6da3d72df5a25823d82f33316 languageName: node linkType: hard -"multer@npm:^1.4.5-lts.1": - version: 1.4.5-lts.1 - resolution: "multer@npm:1.4.5-lts.1" +"multer@npm:^2.0.2": + version: 2.0.2 + resolution: "multer@npm:2.0.2" dependencies: append-field: "npm:^1.0.0" - busboy: "npm:^1.0.0" - concat-stream: "npm:^1.5.2" - mkdirp: "npm:^0.5.4" + busboy: "npm:^1.6.0" + concat-stream: "npm:^2.0.0" + mkdirp: "npm:^0.5.6" object-assign: "npm:^4.1.1" - type-is: "npm:^1.6.4" - xtend: "npm:^4.0.0" - checksum: 10/957c09956f3b7f79d8586cac5e2a50e9a5c3011eb841667b5e4590c5f31d9464f5b46aecd399c83e183a15b88b019cccf0e4fa5620db40bf16b9e3af7fab3ac6 + type-is: "npm:^1.6.18" + xtend: "npm:^4.0.2" + checksum: 10/4bdcb07138cf72f93adc08a0dc27c058faab9f6721067a58f394fa546d73d11b7f100cdd66e733d649d184f9d1b402065f6888b31ec427409f056ee92c4367a6 languageName: node linkType: hard @@ -38405,12 +38701,12 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.7": - version: 3.3.8 - resolution: "nanoid@npm:3.3.8" +"nanoid@npm:^3.3.11, nanoid@npm:^3.3.7": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" bin: nanoid: bin/nanoid.cjs - checksum: 10/2d1766606cf0d6f47b6f0fdab91761bb81609b2e3d367027aff45e6ee7006f660fb7e7781f4a34799fe6734f1268eeed2e37a5fdee809ade0c2d4eb11b0f9c40 + checksum: 10/73b5afe5975a307aaa3c95dfe3334c52cdf9ae71518176895229b8d65ab0d1c0417dd081426134eb7571c055720428ea5d57c645138161e7d10df80815527c48 languageName: node linkType: hard @@ -38653,6 +38949,17 @@ __metadata: languageName: node linkType: hard +"node-fetch@npm:^3.3.2": + version: 3.3.2 + resolution: "node-fetch@npm:3.3.2" + dependencies: + data-uri-to-buffer: "npm:^4.0.0" + fetch-blob: "npm:^3.1.4" + formdata-polyfill: "npm:^4.0.10" + checksum: 10/24207ca8c81231c7c59151840e3fded461d67a31cf3e3b3968e12201a42f89ce4a0b5fb7079b1fa0a4655957b1ca9257553200f03a9f668b45ebad265ca5593d + languageName: node + linkType: hard + "node-forge@npm:^1, node-forge@npm:^1.2.1, node-forge@npm:^1.3.1": version: 1.3.1 resolution: "node-forge@npm:1.3.1" @@ -38765,8 +39072,8 @@ __metadata: linkType: hard "node-mocks-http@npm:^1.0.0": - version: 1.17.0 - resolution: "node-mocks-http@npm:1.17.0" + version: 1.17.2 + resolution: "node-mocks-http@npm:1.17.2" dependencies: accepts: "npm:^1.3.7" content-disposition: "npm:^0.5.3" @@ -38786,7 +39093,7 @@ __metadata: optional: true "@types/node": optional: true - checksum: 10/7b8e0db30df8dacc957b352e4553635ed9c212a0e307ed486b5cb93cb7653c82a8b5bbe6a99ba016e1f390b3a2f5ef9388831b6cf1a4e43f014c0070c3292df3 + checksum: 10/3ab97b5a7b2dba0032fc5090983e4ca4be0597f1c645b34dff99d465feb17ef1ee7ea4829a9b4303495399c1ac0f12f8964b878e0dda43cda798bb97254ad571 languageName: node linkType: hard @@ -38818,6 +39125,15 @@ __metadata: languageName: node linkType: hard +"node-source-walk@npm:^7.0.1": + version: 7.0.1 + resolution: "node-source-walk@npm:7.0.1" + dependencies: + "@babel/parser": "npm:^7.26.7" + checksum: 10/cd71a136b095aa8055d2a670e7820d467eaad3c62d343dacb706d3ac651991ae73032ce5f82a9ac0373ec8186d98c3c7e9b4311f1cd0887d1ec035c734b5f5ed + languageName: node + linkType: hard + "node-stdlib-browser@npm:^1.3.1": version: 1.3.1 resolution: "node-stdlib-browser@npm:1.3.1" @@ -39189,13 +39505,6 @@ __metadata: languageName: node linkType: hard -"oauth-sign@npm:~0.9.0": - version: 0.9.0 - resolution: "oauth-sign@npm:0.9.0" - checksum: 10/1809a366d258f41fdf4ab5310cff3d1e15f96b187503bc7333cef4351de7bd0f52cb269bc95800f1fae5fb04dd886287df1471985fd67e8484729fdbcf857119 - languageName: node - linkType: hard - "oauth4webapi@npm:^3.4.1": version: 3.5.0 resolution: "oauth4webapi@npm:3.5.0" @@ -39323,14 +39632,7 @@ __metadata: languageName: node linkType: hard -"objectorarray@npm:^1.0.5": - version: 1.0.5 - resolution: "objectorarray@npm:1.0.5" - checksum: 10/8fd776aa495d113e217837f4adc1d53e63f656498237094d25f84c3e2c038b34b71d6fd85c4b60c7ae5f558790e5042426a400fae3eac35f297e11be12643a78 - languageName: node - linkType: hard - -"obuf@npm:^1.0.0, obuf@npm:^1.1.2, obuf@npm:~1.1.2": +"obuf@npm:^1.0.0, obuf@npm:^1.1.2": version: 1.1.2 resolution: "obuf@npm:1.1.2" checksum: 10/53ff4ab3a13cc33ba6c856cf281f2965c0aec9720967af450e8fd06cfd50aceeefc791986a16bcefa14e7898b3ca9acdfcf15b9d9a1b9c7e1366581a8ad6e65e @@ -39347,20 +39649,21 @@ __metadata: linkType: hard "octokit@npm:^3.0.0": - version: 3.2.1 - resolution: "octokit@npm:3.2.1" + version: 3.2.2 + resolution: "octokit@npm:3.2.2" dependencies: "@octokit/app": "npm:^14.0.2" "@octokit/core": "npm:^5.0.0" "@octokit/oauth-app": "npm:^6.0.0" "@octokit/plugin-paginate-graphql": "npm:^4.0.0" - "@octokit/plugin-paginate-rest": "npm:11.3.1" - "@octokit/plugin-rest-endpoint-methods": "npm:13.2.2" + "@octokit/plugin-paginate-rest": "npm:11.4.4-cjs.2" + "@octokit/plugin-rest-endpoint-methods": "npm:13.3.2-cjs.1" "@octokit/plugin-retry": "npm:^6.0.0" "@octokit/plugin-throttling": "npm:^8.0.0" "@octokit/request-error": "npm:^5.0.0" "@octokit/types": "npm:^13.0.0" - checksum: 10/a3539831c9c0828b1e37dc94a3668f5fb9aa873fc396c0464275d152e4065b6b20300cfba766871cb52ed2e6d537febda7d4be872894c4c62024393a3578fde0 + "@octokit/webhooks": "npm:^12.3.1" + checksum: 10/a258cc62767552fcf9d9a2dfb2aac44b326a2a235fb6444c1f37bc018bda4ac4bf0203704d8b7b3955d3caee6f9256c200287f7e99843c8be01febe4d9fe86d8 languageName: node linkType: hard @@ -39396,6 +39699,13 @@ __metadata: languageName: node linkType: hard +"on-headers@npm:~1.1.0": + version: 1.1.0 + resolution: "on-headers@npm:1.1.0" + checksum: 10/98aa64629f986fb8cc4517dd8bede73c980e31208cba97f4442c330959f60ced3dc6214b83420491f5111fc7c4f4343abe2ea62c85f505cf041d67850f238776 + languageName: node + linkType: hard + "once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -39566,7 +39876,7 @@ __metadata: languageName: node linkType: hard -"openid-client@npm:^5.3.0, openid-client@npm:^5.4.3, openid-client@npm:^5.5.0": +"openid-client@npm:^5.4.3, openid-client@npm:^5.5.0": version: 5.7.1 resolution: "openid-client@npm:5.7.1" dependencies: @@ -39860,19 +40170,19 @@ __metadata: languageName: node linkType: hard -"pac-proxy-agent@npm:^7.0.0, pac-proxy-agent@npm:^7.0.1": - version: 7.0.2 - resolution: "pac-proxy-agent@npm:7.0.2" +"pac-proxy-agent@npm:^7.0.0, pac-proxy-agent@npm:^7.1.0": + version: 7.2.0 + resolution: "pac-proxy-agent@npm:7.2.0" dependencies: "@tootallnate/quickjs-emscripten": "npm:^0.23.0" - agent-base: "npm:^7.0.2" + agent-base: "npm:^7.1.2" debug: "npm:^4.3.4" get-uri: "npm:^6.0.1" http-proxy-agent: "npm:^7.0.0" - https-proxy-agent: "npm:^7.0.5" + https-proxy-agent: "npm:^7.0.6" pac-resolver: "npm:^7.0.1" - socks-proxy-agent: "npm:^8.0.4" - checksum: 10/bb9b53b32ba98f085fd98ad0ea5e4201498585bf8d9390b3365c057b692b8562997be166d44224878ac216a81f1016c1f55f4e1dec52a6d92e5aa659eba9124c + socks-proxy-agent: "npm:^8.0.5" + checksum: 10/187656be62d5a6b983d90a86d64106a38b1a9ee78f591fabb27b3cf0d51e5d528456a9faaaf981c93dd54dc9c9ee8d33e35a51072b73a19ec1a8e0d0c36a2b99 languageName: node linkType: hard @@ -40039,6 +40349,13 @@ __metadata: languageName: node linkType: hard +"parse-ms@npm:^2.1.0": + version: 2.1.0 + resolution: "parse-ms@npm:2.1.0" + checksum: 10/517eab80cdb9df6ae22a8fad944bfb4289482699bcde5211a1c127091dfea33c3dcb217246b188865fc32e998bcee815bfa4a863f41e3b2d0bcc69f34ef1a543 + languageName: node + linkType: hard + "parse-ms@npm:^4.0.0": version: 4.0.0 resolution: "parse-ms@npm:4.0.0" @@ -40310,7 +40627,7 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.11.1, path-scurry@npm:^1.6.1": +"path-scurry@npm:^1.11.1": version: 1.11.1 resolution: "path-scurry@npm:1.11.1" dependencies: @@ -40344,6 +40661,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:8.2.0, path-to-regexp@npm:^8.0.0, path-to-regexp@npm:^8.1.0, path-to-regexp@npm:^8.2.0": + version: 8.2.0 + resolution: "path-to-regexp@npm:8.2.0" + checksum: 10/23378276a172b8ba5f5fb824475d1818ca5ccee7bbdb4674701616470f23a14e536c1db11da9c9e6d82b82c556a817bbf4eee6e41b9ed20090ef9427cbb38e13 + languageName: node + linkType: hard + "path-to-regexp@npm:^6.3.0": version: 6.3.0 resolution: "path-to-regexp@npm:6.3.0" @@ -40351,13 +40675,6 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:^8.0.0, path-to-regexp@npm:^8.1.0": - version: 8.2.0 - resolution: "path-to-regexp@npm:8.2.0" - checksum: 10/23378276a172b8ba5f5fb824475d1818ca5ccee7bbdb4674701616470f23a14e536c1db11da9c9e6d82b82c556a817bbf4eee6e41b9ed20090ef9427cbb38e13 - languageName: node - linkType: hard - "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -40462,13 +40779,6 @@ __metadata: languageName: node linkType: hard -"pg-numeric@npm:1.0.2": - version: 1.0.2 - resolution: "pg-numeric@npm:1.0.2" - checksum: 10/8899f8200caa1744439a8778a9eb3ceefb599d893e40a09eef84ee0d4c151319fd416634a6c0fc7b7db4ac268710042da5be700b80ef0de716fe089b8652c84f - languageName: node - linkType: hard - "pg-pool@npm:^3.8.0": version: 3.8.0 resolution: "pg-pool@npm:3.8.0" @@ -40498,21 +40808,6 @@ __metadata: languageName: node linkType: hard -"pg-types@npm:^4.0.1": - version: 4.0.1 - resolution: "pg-types@npm:4.0.1" - dependencies: - pg-int8: "npm:1.0.1" - pg-numeric: "npm:1.0.2" - postgres-array: "npm:~3.0.1" - postgres-bytea: "npm:~3.0.0" - postgres-date: "npm:~2.0.1" - postgres-interval: "npm:^3.0.0" - postgres-range: "npm:^1.1.1" - checksum: 10/2c686ef361856ff9695a40cf34d7bb59bcf2218bd1f121d7fc8b77a75cc4106c09519e9520a82aac8fce4540dec81f695458b8e43bfc420ba74eb9b5005b5619 - languageName: node - linkType: hard - "pg@npm:^8.11.3, pg@npm:^8.9.0": version: 8.14.1 resolution: "pg@npm:8.14.1" @@ -40565,7 +40860,7 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.0.0, picocolors@npm:^1.1.0, picocolors@npm:^1.1.1": +"picocolors@npm:1.1.1, picocolors@npm:^1.0.0, picocolors@npm:^1.1.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 @@ -40579,17 +40874,17 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.2, picomatch@npm:^2.2.3, picomatch@npm:^2.3.0, picomatch@npm:^2.3.1": +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.3, picomatch@npm:^2.3.0, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" checksum: 10/60c2595003b05e4535394d1da94850f5372c9427ca4413b71210f437f7b2ca091dbd611c45e8b37d10036fa8eade25c1b8951654f9d3973bfa66a2ff4d3b08bc languageName: node linkType: hard -"picomatch@npm:^4.0.1, picomatch@npm:^4.0.2": - version: 4.0.2 - resolution: "picomatch@npm:4.0.2" - checksum: 10/ce617b8da36797d09c0baacb96ca8a44460452c89362d7cb8f70ca46b4158ba8bc3606912de7c818eb4a939f7f9015cef3c766ec8a0c6bfc725fdc078e39c717 +"picomatch@npm:^4.0.1, picomatch@npm:^4.0.2, picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10/57b99055f40b16798f2802916d9c17e9744e620a0db136554af01d19598b96e45e2f00014c91d1b8b13874b80caa8c295b3d589a3f72373ec4aaf54baa5962d5 languageName: node linkType: hard @@ -40679,6 +40974,13 @@ __metadata: languageName: node linkType: hard +"pkce-challenge@npm:^5.0.0": + version: 5.0.0 + resolution: "pkce-challenge@npm:5.0.0" + checksum: 10/e60c06a0e0481cb82f80072053d5c479a7490758541c4226460450285dd5d72a995c44b3c553731ca7c2f64cc34b35f1d2e5f9de08d276b59899298f9efe1ddf + languageName: node + linkType: hard + "pkg-dir@npm:^3.0.0": version: 3.0.0 resolution: "pkg-dir@npm:3.0.0" @@ -40688,7 +40990,7 @@ __metadata: languageName: node linkType: hard -"pkg-dir@npm:^4.1.0, pkg-dir@npm:^4.2.0": +"pkg-dir@npm:^4.2.0": version: 4.2.0 resolution: "pkg-dir@npm:4.2.0" dependencies: @@ -40729,27 +41031,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.52.0": - version: 1.52.0 - resolution: "playwright-core@npm:1.52.0" +"playwright-core@npm:1.54.2": + version: 1.54.2 + resolution: "playwright-core@npm:1.54.2" bin: playwright-core: cli.js - checksum: 10/42e13f5f98dc25ebc95525fb338a215b9097b2ba39d41e99972a190bf75d79979f163f5bc07b1ca06847ee07acb2c9b487d070fab67e9cd55e33310fc05aca3c + checksum: 10/8eb8b37d7b02c2394f85567c5703464ee7c85929e1de7118946548a0277cacd41a6f247b8f7eb50c921433346f785356eff8132a9d3c6c2dcf3402c6a83e4f18 languageName: node linkType: hard -"playwright@npm:1.52.0": - version: 1.52.0 - resolution: "playwright@npm:1.52.0" +"playwright@npm:1.54.2": + version: 1.54.2 + resolution: "playwright@npm:1.54.2" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.52.0" + playwright-core: "npm:1.54.2" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10/214175446089000c2ac997b925063b95f7d86d129c5d7c74caa5ddcb05bcad598dfd569d2133a10dc82d288bf67e7858877dcd099274b0b928b9c63db7d6ecec + checksum: 10/3f4fedc1e2290b6da6960eedf0d78b9097251848b499b0627186ca85e94178fe6be1590c4926246ff4dad5729fc80ab63fee6554fe736d29c0a1808ec483467f languageName: node linkType: hard @@ -41231,14 +41533,27 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.1.0, postcss@npm:^8.4.33": - version: 8.4.49 - resolution: "postcss@npm:8.4.49" +"postcss-values-parser@npm:^6.0.2": + version: 6.0.2 + resolution: "postcss-values-parser@npm:6.0.2" dependencies: - nanoid: "npm:^3.3.7" + color-name: "npm:^1.1.4" + is-url-superb: "npm:^4.0.0" + quote-unquote: "npm:^1.0.0" + peerDependencies: + postcss: ^8.2.9 + checksum: 10/ff2fa096896f1c33f7531e814b8d01e785bd99d672c1597d5c5d8c2409b30b8146be6565f6269c952d1f03d626f00ae3f1afb8308cc772c08b323abee23c9a42 + languageName: node + linkType: hard + +"postcss@npm:^8.1.0, postcss@npm:^8.4.33, postcss@npm:^8.5.1, postcss@npm:^8.5.6": + version: 8.5.6 + resolution: "postcss@npm:8.5.6" + dependencies: + nanoid: "npm:^3.3.11" picocolors: "npm:^1.1.1" source-map-js: "npm:^1.2.1" - checksum: 10/28fe1005b1339870e0a5006375ba5ac1213fd69800f79e7db09c398e074421ba6e162898e94f64942fed554037fd292db3811d87835d25ab5ef7f3c9daacb6ca + checksum: 10/9e4fbe97574091e9736d0e82a591e29aa100a0bf60276a926308f8c57249698935f35c5d2f4e80de778d0cbb8dcffab4f383d85fd50c5649aca421c3df729b86 languageName: node linkType: hard @@ -41249,13 +41564,6 @@ __metadata: languageName: node linkType: hard -"postgres-array@npm:~3.0.1": - version: 3.0.2 - resolution: "postgres-array@npm:3.0.2" - checksum: 10/0159517e4e5f263bf9e324f0c4d3c10244a294021f2b5980abc8c23afdb965370a7fc0c82012fce4d28e83186ad089b6476b05fcef6c88f8e43e37a3a2fa0ad5 - languageName: node - linkType: hard - "postgres-bytea@npm:~1.0.0": version: 1.0.0 resolution: "postgres-bytea@npm:1.0.0" @@ -41263,15 +41571,6 @@ __metadata: languageName: node linkType: hard -"postgres-bytea@npm:~3.0.0": - version: 3.0.0 - resolution: "postgres-bytea@npm:3.0.0" - dependencies: - obuf: "npm:~1.1.2" - checksum: 10/f5c01758fd2fa807afbd34e1ba2146f683818ebc2d23f4a62f0fd627c0b1126fc543cab1b63925f97ce6c7d8f5f316043218619c447445210ea82f10411efb1b - languageName: node - linkType: hard - "postgres-date@npm:~1.0.4": version: 1.0.5 resolution: "postgres-date@npm:1.0.5" @@ -41279,13 +41578,6 @@ __metadata: languageName: node linkType: hard -"postgres-date@npm:~2.0.1": - version: 2.0.1 - resolution: "postgres-date@npm:2.0.1" - checksum: 10/908eacec35faf9b6fa4248074e7c8db0fc4ac6a2e0a00eacef9fb9cf44a25fc6f100af7cda09f5d89d076c87923b59e940a309560d86aded4753985c97255be1 - languageName: node - linkType: hard - "postgres-interval@npm:^1.1.0": version: 1.2.0 resolution: "postgres-interval@npm:1.2.0" @@ -41295,20 +41587,6 @@ __metadata: languageName: node linkType: hard -"postgres-interval@npm:^3.0.0": - version: 3.0.0 - resolution: "postgres-interval@npm:3.0.0" - checksum: 10/c7a1cf006de97de663b6b8c4d2b167aa9909a238c4866a94b15d303762f5ac884ff4796cd6e2111b7f0a91302b83c570453aa8506fd005b5a5d5dfa87441bebc - languageName: node - linkType: hard - -"postgres-range@npm:^1.1.1": - version: 1.1.3 - resolution: "postgres-range@npm:1.1.3" - checksum: 10/356a46d97eab301cca0dbf7a65be6f68c4a7e33d304909e5ca840038b1210390e1d33fcc3b566c42020f7b97e9de189bc5286d1ec8d7ba2445ed3377c576ce9e - languageName: node - linkType: hard - "postman-collection@npm:^4.1.7": version: 4.2.1 resolution: "postman-collection@npm:4.2.1" @@ -41359,6 +41637,31 @@ __metadata: languageName: node linkType: hard +"precinct@npm:^12.2.0": + version: 12.2.0 + resolution: "precinct@npm:12.2.0" + dependencies: + "@dependents/detective-less": "npm:^5.0.1" + commander: "npm:^12.1.0" + detective-amd: "npm:^6.0.1" + detective-cjs: "npm:^6.0.1" + detective-es6: "npm:^5.0.1" + detective-postcss: "npm:^7.0.1" + detective-sass: "npm:^6.0.1" + detective-scss: "npm:^5.0.1" + detective-stylus: "npm:^5.0.1" + detective-typescript: "npm:^14.0.0" + detective-vue2: "npm:^2.2.0" + module-definition: "npm:^6.0.1" + node-source-walk: "npm:^7.0.1" + postcss: "npm:^8.5.1" + typescript: "npm:^5.7.3" + bin: + precinct: bin/cli.js + checksum: 10/fb0cd96b234aa70ede2326971dff039ceee99c4bab68ee8c3f5c3cd09ca67f7a7ae7537121ac49698ef0f9d90f3454e9d3bf2b22bf5ceb745fe4049ec31c9ef0 + languageName: node + linkType: hard + "precond@npm:0.2": version: 0.2.3 resolution: "precond@npm:0.2.3" @@ -41408,6 +41711,15 @@ __metadata: languageName: node linkType: hard +"prettier@npm:^3.1.1": + version: 3.6.2 + resolution: "prettier@npm:3.6.2" + bin: + prettier: bin/prettier.cjs + checksum: 10/1213691706bcef1371d16ef72773c8111106c3533b660b1cc8ec158bd109cdf1462804125f87f981f23c4a3dba053b6efafda30ab0114cc5b4a725606bb9ff26 + languageName: node + linkType: hard + "pretty-bytes@npm:^5.1.0, pretty-bytes@npm:^5.3.0": version: 5.6.0 resolution: "pretty-bytes@npm:5.6.0" @@ -41447,6 +41759,15 @@ __metadata: languageName: node linkType: hard +"pretty-ms@npm:^7.0.1": + version: 7.0.1 + resolution: "pretty-ms@npm:7.0.1" + dependencies: + parse-ms: "npm:^2.1.0" + checksum: 10/a39aac23cc7dae7a94c70518ab8b6c6db0894a7b84c81ee7abc8778c5ec8bae2d1e71ba991ff641732b38433724bfbdbb37bd3a00418637f797c072e06fe8b4c + languageName: node + linkType: hard + "pretty-ms@npm:^9.0.0": version: 9.2.0 resolution: "pretty-ms@npm:9.2.0" @@ -41616,7 +41937,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:15.x, prop-types@npm:^15.0.0, prop-types@npm:^15.5.10, prop-types@npm:^15.5.7, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": +"prop-types@npm:15.x, prop-types@npm:^15.0.0, prop-types@npm:^15.5.10, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -41699,7 +42020,7 @@ __metadata: languageName: node linkType: hard -"proxy-addr@npm:~2.0.7": +"proxy-addr@npm:^2.0.7, proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" dependencies: @@ -41709,19 +42030,19 @@ __metadata: languageName: node linkType: hard -"proxy-agent@npm:6.4.0": - version: 6.4.0 - resolution: "proxy-agent@npm:6.4.0" +"proxy-agent@npm:6.5.0": + version: 6.5.0 + resolution: "proxy-agent@npm:6.5.0" dependencies: - agent-base: "npm:^7.0.2" + agent-base: "npm:^7.1.2" debug: "npm:^4.3.4" http-proxy-agent: "npm:^7.0.1" - https-proxy-agent: "npm:^7.0.3" + https-proxy-agent: "npm:^7.0.6" lru-cache: "npm:^7.14.1" - pac-proxy-agent: "npm:^7.0.1" + pac-proxy-agent: "npm:^7.1.0" proxy-from-env: "npm:^1.1.0" - socks-proxy-agent: "npm:^8.0.2" - checksum: 10/a22f202b74cc52f093efd9bfe52de8db08eda8bbc16b9d3d73acda2acc1b40223966e5521b1706788b06adf9265f093ed554d989b354e81b2d6ad482e5bd4d23 + socks-proxy-agent: "npm:^8.0.5" + checksum: 10/56d5a494d96dafad94868870af776939e7b9aaca172465a5c251d2523496a8353b029c32d2a72a012bd62622cdc9a43ba3df59b5738ab7b740bc6b362e9f9477 languageName: node linkType: hard @@ -41732,7 +42053,7 @@ __metadata: languageName: node linkType: hard -"psl@npm:^1.1.28, psl@npm:^1.1.33": +"psl@npm:^1.1.33": version: 1.8.0 resolution: "psl@npm:1.8.0" checksum: 10/5f62a8eca06cb4a017983d15b92b0d38dc8699d637eabc8cb482c59b4106c9760f59cc8afabcb8bb7b98f0322907680d8f0f59226386fffab5248d180bc04578 @@ -41823,7 +42144,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.10.1, qs@npm:^6.10.3, qs@npm:^6.11.0, qs@npm:^6.12.2, qs@npm:^6.12.3, qs@npm:^6.7.0, qs@npm:^6.9.4": +"qs@npm:^6.10.1, qs@npm:^6.10.3, qs@npm:^6.11.2, qs@npm:^6.12.2, qs@npm:^6.12.3, qs@npm:^6.14.0, qs@npm:^6.7.0, qs@npm:^6.9.4": version: 6.14.0 resolution: "qs@npm:6.14.0" dependencies: @@ -41832,13 +42153,6 @@ __metadata: languageName: node linkType: hard -"qs@npm:~6.5.2": - version: 6.5.3 - resolution: "qs@npm:6.5.3" - checksum: 10/485c990fba7ad17671e16c92715fb064c1600337738f5d140024eb33a49fbc1ed31890d3db850117c760caeb9c9cc9f4ba22a15c20dd119968e41e3d3fe60b28 - languageName: node - linkType: hard - "querystring-es3@npm:^0.2.1": version: 0.2.1 resolution: "querystring-es3@npm:0.2.1" @@ -41894,6 +42208,13 @@ __metadata: languageName: node linkType: hard +"quote-unquote@npm:^1.0.0": + version: 1.0.0 + resolution: "quote-unquote@npm:1.0.0" + checksum: 10/955a2ead534f5b6a3f8d4dc5a4b95ac6468213d3fb11f8c1592a0a56345c45a3d14d5ca04d3de2bc9891493fcac38c03dfa91c48a6159aef50124e9c5afcea49 + languageName: node + linkType: hard + "raf-schd@npm:^4.0.2": version: 4.0.2 resolution: "raf-schd@npm:4.0.2" @@ -41991,6 +42312,15 @@ __metadata: languageName: node linkType: hard +"rate-limit-redis@npm:^4.2.0": + version: 4.2.1 + resolution: "rate-limit-redis@npm:4.2.1" + peerDependencies: + express-rate-limit: ">= 6" + checksum: 10/c36c50cfca992cbd14c97c08bb01c7d1d340d27b5fa1a2c1154275fec0b59c187eb6fb207da3d035c7c87e7629c92324cd53074f854dbb16548faf94f5a41351 + languageName: node + linkType: hard + "rate-limiter-flexible@npm:^4.0.1": version: 4.0.1 resolution: "rate-limiter-flexible@npm:4.0.1" @@ -42010,6 +42340,18 @@ __metadata: languageName: node linkType: hard +"raw-body@npm:^3.0.0": + version: 3.0.0 + resolution: "raw-body@npm:3.0.0" + dependencies: + bytes: "npm:3.1.2" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.6.3" + unpipe: "npm:1.0.0" + checksum: 10/2443429bbb2f9ae5c50d3d2a6c342533dfbde6b3173740b70fa0302b30914ff400c6d31a46b3ceacbe7d0925dc07d4413928278b494b04a65736fc17ca33e30c + languageName: node + linkType: hard + "raw-loader@npm:^4.0.2": version: 4.0.2 resolution: "raw-loader@npm:4.0.2" @@ -42071,6 +42413,98 @@ __metadata: languageName: node linkType: hard +"react-aria-components@npm:^1.10.1": + version: 1.10.1 + resolution: "react-aria-components@npm:1.10.1" + dependencies: + "@internationalized/date": "npm:^3.8.2" + "@internationalized/string": "npm:^3.2.7" + "@react-aria/autocomplete": "npm:3.0.0-beta.5" + "@react-aria/collections": "npm:3.0.0-rc.3" + "@react-aria/dnd": "npm:^3.10.1" + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/live-announcer": "npm:^3.4.3" + "@react-aria/overlays": "npm:^3.27.3" + "@react-aria/ssr": "npm:^3.9.9" + "@react-aria/toolbar": "npm:3.0.0-beta.18" + "@react-aria/utils": "npm:^3.29.1" + "@react-aria/virtualizer": "npm:^4.1.7" + "@react-stately/autocomplete": "npm:3.0.0-beta.2" + "@react-stately/layout": "npm:^4.3.1" + "@react-stately/selection": "npm:^3.20.3" + "@react-stately/table": "npm:^3.14.3" + "@react-stately/utils": "npm:^3.10.7" + "@react-stately/virtualizer": "npm:^4.4.1" + "@react-types/form": "npm:^3.7.13" + "@react-types/grid": "npm:^3.3.3" + "@react-types/shared": "npm:^3.30.0" + "@react-types/table": "npm:^3.13.1" + "@swc/helpers": "npm:^0.5.0" + client-only: "npm:^0.0.1" + react-aria: "npm:^3.41.1" + react-stately: "npm:^3.39.0" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/6fcc35404bdd8718bbfb05263bd0c67157d2ba665e0446c47f42f78262ef92231bb0512fc7734c2114bb20c28fff8eb13694c9719c741d2c7a59181fd8fdf115 + languageName: node + linkType: hard + +"react-aria@npm:^3.41.1": + version: 3.41.1 + resolution: "react-aria@npm:3.41.1" + dependencies: + "@internationalized/string": "npm:^3.2.7" + "@react-aria/breadcrumbs": "npm:^3.5.26" + "@react-aria/button": "npm:^3.13.3" + "@react-aria/calendar": "npm:^3.8.3" + "@react-aria/checkbox": "npm:^3.15.7" + "@react-aria/color": "npm:^3.0.9" + "@react-aria/combobox": "npm:^3.12.5" + "@react-aria/datepicker": "npm:^3.14.5" + "@react-aria/dialog": "npm:^3.5.27" + "@react-aria/disclosure": "npm:^3.0.6" + "@react-aria/dnd": "npm:^3.10.1" + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/gridlist": "npm:^3.13.2" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/landmark": "npm:^3.0.4" + "@react-aria/link": "npm:^3.8.3" + "@react-aria/listbox": "npm:^3.14.6" + "@react-aria/menu": "npm:^3.18.5" + "@react-aria/meter": "npm:^3.4.24" + "@react-aria/numberfield": "npm:^3.11.16" + "@react-aria/overlays": "npm:^3.27.3" + "@react-aria/progress": "npm:^3.4.24" + "@react-aria/radio": "npm:^3.11.5" + "@react-aria/searchfield": "npm:^3.8.6" + "@react-aria/select": "npm:^3.15.7" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/separator": "npm:^3.4.10" + "@react-aria/slider": "npm:^3.7.21" + "@react-aria/ssr": "npm:^3.9.9" + "@react-aria/switch": "npm:^3.7.5" + "@react-aria/table": "npm:^3.17.5" + "@react-aria/tabs": "npm:^3.10.5" + "@react-aria/tag": "npm:^3.6.2" + "@react-aria/textfield": "npm:^3.17.5" + "@react-aria/toast": "npm:^3.0.5" + "@react-aria/tooltip": "npm:^3.8.5" + "@react-aria/tree": "npm:^3.1.1" + "@react-aria/utils": "npm:^3.29.1" + "@react-aria/visually-hidden": "npm:^3.8.25" + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/6ec619bc9b26800516e22054a428ea29ebc7bcfabf762918893a5a1a73d1576dd4b1c99d85aae02ae69eb16a49d1a765ab34cb840a500134a76e145a4e995f42 + languageName: node + linkType: hard + "react-base16-styling@npm:^0.6.0": version: 0.6.0 resolution: "react-base16-styling@npm:0.6.0" @@ -42168,17 +42602,17 @@ __metadata: linkType: hard "react-docgen-typescript@npm:^2.2.2": - version: 2.2.2 - resolution: "react-docgen-typescript@npm:2.2.2" + version: 2.4.0 + resolution: "react-docgen-typescript@npm:2.4.0" peerDependencies: typescript: ">= 4.3.x" - checksum: 10/081fc3a876f53b9eeffcff357e5b6c190db799d50edcf11b187857d8cb8cce28000ed777ed16dd52a1c955f332612ef6b1f02cf8adcbcb084b8da9ff1ae5fd13 + checksum: 10/81e45bc012150dee50a9919a44597a436d45168f7a83febbbfef134c07e71c1a2f09fb6e1fc040f18bd6747f37b46d463a2b4a30177f6137e4ff49570bcaf253 languageName: node linkType: hard "react-docgen@npm:^7.0.0": - version: 7.1.0 - resolution: "react-docgen@npm:7.1.0" + version: 7.1.1 + resolution: "react-docgen@npm:7.1.1" dependencies: "@babel/core": "npm:^7.18.9" "@babel/traverse": "npm:^7.18.9" @@ -42190,7 +42624,7 @@ __metadata: doctrine: "npm:^3.0.0" resolve: "npm:^1.22.1" strip-indent: "npm:^4.0.0" - checksum: 10/ce1f6e4394375307ecf268ef0b3e41fd75a6b050e70053c6e64b72f108d7606a4b8de6c0f1dab2759bc80864db01be38a9245e2d71024d2ed3d3e0179bd02140 + checksum: 10/501e5fa0d00e32ee27559f44462a34e9531018ccb46c51efbe60b98a4c077f43dbe8999da5bb91d2ab45a83a34099436a3b725fdabd3f218dbb4493c0b1c9f95 languageName: node linkType: hard @@ -42209,13 +42643,13 @@ __metadata: linkType: hard "react-dom@npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0": - version: 19.0.0 - resolution: "react-dom@npm:19.0.0" + version: 19.1.1 + resolution: "react-dom@npm:19.1.1" dependencies: - scheduler: "npm:^0.25.0" + scheduler: "npm:^0.26.0" peerDependencies: - react: ^19.0.0 - checksum: 10/aa64a2f1991042f516260e8b0eca0ae777b6c8f1aa2b5ae096e80bbb6ac9b005aef2bca697969841d34f7e1819556263476bdfea36c35092e8d9aefde3de2d9a + react: ^19.1.1 + checksum: 10/9005415d2175b1f1eb4a544ad04afb29691bb7b6dd43bbdaa09932146b310b73bd4552bc772ad78fa481f409eada1560cf887606c83c1a53a922c1e30f1b3a34 languageName: node linkType: hard @@ -42328,16 +42762,6 @@ __metadata: languageName: node linkType: hard -"react-idle-timer@npm:5.6.2": - version: 5.6.2 - resolution: "react-idle-timer@npm:5.6.2" - peerDependencies: - react: ">=16" - react-dom: ">=16" - checksum: 10/beca4ea53db58a041b562ada6b2eb3de965e9abd1e00f230fbd098114addf668afed907b2e5e31d645be0c02a0648e4837d5bb1644f2cf66428d92f8ef233db4 - languageName: node - linkType: hard - "react-idle-timer@npm:5.7.2": version: 5.7.2 resolution: "react-idle-timer@npm:5.7.2" @@ -42559,6 +42983,16 @@ __metadata: languageName: node linkType: hard +"react-resizable-panels@npm:^3.0.4": + version: 3.0.4 + resolution: "react-resizable-panels@npm:3.0.4" + peerDependencies: + react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + checksum: 10/92b8ac224d3c60c7c8ea0d80f712d9cd719afad76d4f1a5b8c8cc7c886b77f1d5058fa622ea057f28f5f7fc1a2c86e38e14d7b0f33722c30bff02e37f9fba705 + languageName: node + linkType: hard + "react-resizable@npm:^3.0.4, react-resizable@npm:^3.0.5": version: 3.0.5 resolution: "react-resizable@npm:3.0.5" @@ -42666,6 +43100,42 @@ __metadata: languageName: node linkType: hard +"react-stately@npm:^3.39.0": + version: 3.39.0 + resolution: "react-stately@npm:3.39.0" + dependencies: + "@react-stately/calendar": "npm:^3.8.2" + "@react-stately/checkbox": "npm:^3.6.15" + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/color": "npm:^3.8.6" + "@react-stately/combobox": "npm:^3.10.6" + "@react-stately/data": "npm:^3.13.1" + "@react-stately/datepicker": "npm:^3.14.2" + "@react-stately/disclosure": "npm:^3.0.5" + "@react-stately/dnd": "npm:^3.6.0" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/list": "npm:^3.12.3" + "@react-stately/menu": "npm:^3.9.5" + "@react-stately/numberfield": "npm:^3.9.13" + "@react-stately/overlays": "npm:^3.6.17" + "@react-stately/radio": "npm:^3.10.14" + "@react-stately/searchfield": "npm:^3.5.13" + "@react-stately/select": "npm:^3.6.14" + "@react-stately/selection": "npm:^3.20.3" + "@react-stately/slider": "npm:^3.6.5" + "@react-stately/table": "npm:^3.14.3" + "@react-stately/tabs": "npm:^3.8.3" + "@react-stately/toast": "npm:^3.1.1" + "@react-stately/toggle": "npm:^3.8.5" + "@react-stately/tooltip": "npm:^3.5.5" + "@react-stately/tree": "npm:^3.9.0" + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/f59f86aca0797e389698fc20a7e88a26748e00e56e011dd6d9f7a4fa1ba7fda32083bc8e5f2af000957d20ad9f4c74dd151cb667b958dcac8f3959d860905fd4 + languageName: node + linkType: hard + "react-style-singleton@npm:^2.2.1": version: 2.2.1 resolution: "react-style-singleton@npm:2.2.1" @@ -42713,18 +43183,6 @@ __metadata: languageName: node linkType: hard -"react-text-truncate@npm:^0.19.0": - version: 0.19.0 - resolution: "react-text-truncate@npm:0.19.0" - dependencies: - prop-types: "npm:^15.5.7" - peerDependencies: - react: ^15.4.1 || ^16.0.0 || ^17.0.0 || || ^18.0.0 - react-dom: ^15.4.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 - checksum: 10/e68e6b924b248b0179e98df5fb654dd6903379df77b9867ee127779b3290c74ead27c75b14b96d759639dc5d07597b4f5f2954bbe543e9cfeff3cff3813e6a72 - languageName: node - linkType: hard - "react-textarea-autosize@npm:^8.3.2": version: 8.4.1 resolution: "react-textarea-autosize@npm:8.4.1" @@ -42812,9 +43270,9 @@ __metadata: linkType: hard "react@npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0": - version: 19.0.0 - resolution: "react@npm:19.0.0" - checksum: 10/2490969c503f644703c88990d20e4011fa6119ddeca451e9de48f6d7ab058d670d2852a5fcd3aa3cd90a923ab2815d532637bd4a814add402ae5c0d4f129ee71 + version: 19.1.1 + resolution: "react@npm:19.1.1" + checksum: 10/9801530fdc939e1a7a499422e930515b2400809cb39c2872984e99f832d233f61659a693871183dac3155c2f9b2c9dcf4440a56bd18983277ae92860e38c3a61 languageName: node linkType: hard @@ -42886,7 +43344,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.2.2, readable-stream@npm:^2.3.3, readable-stream@npm:^2.3.5, readable-stream@npm:^2.3.8, readable-stream@npm:~2.3.6": +"readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.3.3, readable-stream@npm:^2.3.5, readable-stream@npm:^2.3.8, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -43038,20 +43496,6 @@ __metadata: languageName: node linkType: hard -"redis@npm:^4.7.0": - version: 4.7.0 - resolution: "redis@npm:4.7.0" - dependencies: - "@redis/bloom": "npm:1.2.0" - "@redis/client": "npm:1.6.0" - "@redis/graph": "npm:1.1.1" - "@redis/json": "npm:1.0.7" - "@redis/search": "npm:1.2.0" - "@redis/time-series": "npm:1.1.0" - checksum: 10/d927a0b1516e2845b7eab67b1466b6f2d0d0695be7a3d4a0a1ffa2f2c60dace98fb9ad01ec0db07519fb9d4d078b99d95b6809508c2a72afe6f814fc8f693188 - languageName: node - linkType: hard - "redux-immutable@npm:^4.0.0": version: 4.0.0 resolution: "redux-immutable@npm:4.0.0" @@ -43077,10 +43521,10 @@ __metadata: languageName: node linkType: hard -"reflect-metadata@npm:0.1.13": - version: 0.1.13 - resolution: "reflect-metadata@npm:0.1.13" - checksum: 10/732570da35d2d96f8fdd5aac60fb263aa92f6512eaded5962b052bd9e90f22a9dec5aaf0d7ff4bfe97646c9530e8444e8435c2d80b24d0bdf938b5d47f6f5b83 +"reflect-metadata@npm:0.2.2": + version: 0.2.2 + resolution: "reflect-metadata@npm:0.2.2" + checksum: 10/1c93f9ac790fea1c852fde80c91b2760420069f4862f28e6fae0c00c6937a56508716b0ed2419ab02869dd488d123c4ab92d062ae84e8739ea7417fae10c4745 languageName: node linkType: hard @@ -43111,22 +43555,6 @@ __metadata: languageName: node linkType: hard -"regenerate-unicode-properties@npm:^10.1.0": - version: 10.1.0 - resolution: "regenerate-unicode-properties@npm:10.1.0" - dependencies: - regenerate: "npm:^1.4.2" - checksum: 10/25b268659898955ad105267b4efba20e361e27b233670694b683728a2800314bec3053918d3bf71b0604376fd76fe9bc9c6f80379cfb6d1e209a58de44101aac - languageName: node - linkType: hard - -"regenerate@npm:^1.4.2": - version: 1.4.2 - resolution: "regenerate@npm:1.4.2" - checksum: 10/dc6c95ae4b3ba6adbd7687cafac260eee4640318c7a95239d5ce847d9b9263979758389e862fe9c93d633b5792ea4ada5708df75885dc5aa05a309fa18140a87 - languageName: node - linkType: hard - "regenerator-runtime@npm:^0.10.5": version: 0.10.5 resolution: "regenerator-runtime@npm:0.10.5" @@ -43148,38 +43576,17 @@ __metadata: languageName: node linkType: hard -"regenerator-transform@npm:^0.15.2": - version: 0.15.2 - resolution: "regenerator-transform@npm:0.15.2" - dependencies: - "@babel/runtime": "npm:^7.8.4" - checksum: 10/c4fdcb46d11bbe32605b4b9ed76b21b8d3f241a45153e9dc6f5542fed4c7744fed459f42701f650d5d5956786bf7de57547329d1c05a9df2ed9e367b9d903302 - languageName: node - linkType: hard - "regexp.prototype.flags@npm:^1.5.3": - version: 1.5.3 - resolution: "regexp.prototype.flags@npm:1.5.3" + version: 1.5.4 + resolution: "regexp.prototype.flags@npm:1.5.4" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" define-properties: "npm:^1.2.1" es-errors: "npm:^1.3.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" set-function-name: "npm:^2.0.2" - checksum: 10/fe17bc4eebbc72945aaf9dd059eb7784a5ca453a67cc4b5b3e399ab08452c9a05befd92063e2c52e7b24d9238c60031656af32dd57c555d1ba6330dbf8c23b43 - languageName: node - linkType: hard - -"regexpu-core@npm:^5.3.1": - version: 5.3.2 - resolution: "regexpu-core@npm:5.3.2" - dependencies: - "@babel/regjsgen": "npm:^0.8.0" - regenerate: "npm:^1.4.2" - regenerate-unicode-properties: "npm:^10.1.0" - regjsparser: "npm:^0.9.1" - unicode-match-property-ecmascript: "npm:^2.0.0" - unicode-match-property-value-ecmascript: "npm:^2.1.0" - checksum: 10/ed0d7c66d84c633fbe8db4939d084c780190eca11f6920807dfb8ebac59e2676952cd8f2008d9c86ae8cf0463ea5fd12c5cff09ef2ce7d51ee6b420a5eb4d177 + checksum: 10/8ab897ca445968e0b96f6237641510f3243e59c180ee2ee8d83889c52ff735dd1bf3657fcd36db053e35e1d823dd53f2565d0b8021ea282c9fe62401c6c3bd6d languageName: node linkType: hard @@ -43201,17 +43608,6 @@ __metadata: languageName: node linkType: hard -"regjsparser@npm:^0.9.1": - version: 0.9.1 - resolution: "regjsparser@npm:0.9.1" - dependencies: - jsesc: "npm:~0.5.0" - bin: - regjsparser: bin/parser - checksum: 10/be7757ef76e1db10bf6996001d1021048b5fb12f5cb470a99b8cf7f3ff943f0f0e2291c0dcdbb418b458ddc4ac10e48680a822b69ef487a0284c8b6b77beddc3 - languageName: node - linkType: hard - "relateurl@npm:^0.2.7": version: 0.2.7 resolution: "relateurl@npm:0.2.7" @@ -43313,34 +43709,6 @@ __metadata: languageName: node linkType: hard -"request@npm:^2.88.0": - version: 2.88.2 - resolution: "request@npm:2.88.2" - dependencies: - aws-sign2: "npm:~0.7.0" - aws4: "npm:^1.8.0" - caseless: "npm:~0.12.0" - combined-stream: "npm:~1.0.6" - extend: "npm:~3.0.2" - forever-agent: "npm:~0.6.1" - form-data: "npm:~2.3.2" - har-validator: "npm:~5.1.3" - http-signature: "npm:~1.2.0" - is-typedarray: "npm:~1.0.0" - isstream: "npm:~0.1.2" - json-stringify-safe: "npm:~5.0.1" - mime-types: "npm:~2.1.19" - oauth-sign: "npm:~0.9.0" - performance-now: "npm:^2.1.0" - qs: "npm:~6.5.2" - safe-buffer: "npm:^5.1.2" - tough-cookie: "npm:~2.5.0" - tunnel-agent: "npm:^0.6.0" - uuid: "npm:^3.3.2" - checksum: 10/005b8b237b56f1571cfd4ecc09772adaa2e82dcb884fc14ea2bb25e23dbf7c2009f9929e0b6d3fd5802e33ed8ee705a3b594c8f9467c1458cd973872bf89db8e - languageName: node - linkType: hard - "require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" @@ -43366,6 +43734,26 @@ __metadata: languageName: node linkType: hard +"requirejs-config-file@npm:^4.0.0": + version: 4.0.0 + resolution: "requirejs-config-file@npm:4.0.0" + dependencies: + esprima: "npm:^4.0.0" + stringify-object: "npm:^3.2.1" + checksum: 10/9fc55813ca8627cc4d3704622702b6b119a222c97825fc61459727d5f9ef9435e4fd34ee03335dc119c66e834d7963fdfe43d9a4df99448d43219587c624ba4e + languageName: node + linkType: hard + +"requirejs@npm:^2.3.7": + version: 2.3.7 + resolution: "requirejs@npm:2.3.7" + bin: + r.js: bin/r.js + r_js: bin/r.js + checksum: 10/7f42af10d8af210a2769501004b34f9dad4c39716f008f91faeef36c33b60a5f372ca1025c8f40b262f2522fd1ea0f36fd11e9587cba14311fac0df51a8571b0 + languageName: node + linkType: hard + "requires-port@npm:^1.0.0": version: 1.0.0 resolution: "requires-port@npm:1.0.0" @@ -43403,6 +43791,13 @@ __metadata: languageName: node linkType: hard +"resolve-dependency-path@npm:^4.0.1": + version: 4.0.1 + resolution: "resolve-dependency-path@npm:4.0.1" + checksum: 10/db52fc3b970c6784fa5a3695dfeccce0a9dd26d1ea7a4e83a91c30c7be11aed21d27c27c59311c5362fc1c6d2f7f5c40913304a544301c524bc18dbd98837622 + languageName: node + linkType: hard + "resolve-dir@npm:^1.0.0, resolve-dir@npm:^1.0.1": version: 1.0.1 resolution: "resolve-dir@npm:1.0.1" @@ -43454,7 +43849,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.1.6, resolve@npm:^1.14.2, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.4, resolve@npm:^1.22.8, resolve@npm:~1.22.1, resolve@npm:~1.22.2": +"resolve@npm:^1.1.6, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.10, resolve@npm:^1.22.4, resolve@npm:^1.22.8, resolve@npm:~1.22.1, resolve@npm:~1.22.2": version: 1.22.10 resolution: "resolve@npm:1.22.10" dependencies: @@ -43493,7 +43888,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.17.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A~1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin<compat/resolve>": +"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.17.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.10#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A~1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin<compat/resolve>": version: 1.22.10 resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin<compat/resolve>::version=1.22.10&hash=c3c19d" dependencies: @@ -43804,29 +44199,31 @@ __metadata: languageName: node linkType: hard -"rollup@npm:^4.27.3": - version: 4.27.4 - resolution: "rollup@npm:4.27.4" +"rollup@npm:^4.27.3, rollup@npm:^4.43.0": + version: 4.46.2 + resolution: "rollup@npm:4.46.2" dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.27.4" - "@rollup/rollup-android-arm64": "npm:4.27.4" - "@rollup/rollup-darwin-arm64": "npm:4.27.4" - "@rollup/rollup-darwin-x64": "npm:4.27.4" - "@rollup/rollup-freebsd-arm64": "npm:4.27.4" - "@rollup/rollup-freebsd-x64": "npm:4.27.4" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.27.4" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.27.4" - "@rollup/rollup-linux-arm64-gnu": "npm:4.27.4" - "@rollup/rollup-linux-arm64-musl": "npm:4.27.4" - "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.27.4" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.27.4" - "@rollup/rollup-linux-s390x-gnu": "npm:4.27.4" - "@rollup/rollup-linux-x64-gnu": "npm:4.27.4" - "@rollup/rollup-linux-x64-musl": "npm:4.27.4" - "@rollup/rollup-win32-arm64-msvc": "npm:4.27.4" - "@rollup/rollup-win32-ia32-msvc": "npm:4.27.4" - "@rollup/rollup-win32-x64-msvc": "npm:4.27.4" - "@types/estree": "npm:1.0.6" + "@rollup/rollup-android-arm-eabi": "npm:4.46.2" + "@rollup/rollup-android-arm64": "npm:4.46.2" + "@rollup/rollup-darwin-arm64": "npm:4.46.2" + "@rollup/rollup-darwin-x64": "npm:4.46.2" + "@rollup/rollup-freebsd-arm64": "npm:4.46.2" + "@rollup/rollup-freebsd-x64": "npm:4.46.2" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.46.2" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.46.2" + "@rollup/rollup-linux-arm64-gnu": "npm:4.46.2" + "@rollup/rollup-linux-arm64-musl": "npm:4.46.2" + "@rollup/rollup-linux-loongarch64-gnu": "npm:4.46.2" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.46.2" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.46.2" + "@rollup/rollup-linux-riscv64-musl": "npm:4.46.2" + "@rollup/rollup-linux-s390x-gnu": "npm:4.46.2" + "@rollup/rollup-linux-x64-gnu": "npm:4.46.2" + "@rollup/rollup-linux-x64-musl": "npm:4.46.2" + "@rollup/rollup-win32-arm64-msvc": "npm:4.46.2" + "@rollup/rollup-win32-ia32-msvc": "npm:4.46.2" + "@rollup/rollup-win32-x64-msvc": "npm:4.46.2" + "@types/estree": "npm:1.0.8" fsevents: "npm:~2.3.2" dependenciesMeta: "@rollup/rollup-android-arm-eabi": @@ -43849,10 +44246,14 @@ __metadata: optional: true "@rollup/rollup-linux-arm64-musl": optional: true - "@rollup/rollup-linux-powerpc64le-gnu": + "@rollup/rollup-linux-loongarch64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-gnu": optional: true "@rollup/rollup-linux-riscv64-gnu": optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true "@rollup/rollup-linux-s390x-gnu": optional: true "@rollup/rollup-linux-x64-gnu": @@ -43869,7 +44270,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10/ff7dcb877fcb6240b5135292dcb5c6aa66d06071e6570bb8aa2ce0863ae1f879e5dd04aff7ed3a77d29633da40393361553549bf819c02cc199d7be94801da66 + checksum: 10/3acc425a9828e8ba75eaec9cb506a680d3deaa09a753635140cc4a3c98445ba6e3f631b32ca1c98965b3f60672d2815cd560ea844bba497d69e65c58c02327cf languageName: node linkType: hard @@ -43882,12 +44283,20 @@ __metadata: "@backstage/create-app": "workspace:*" "@backstage/e2e-test-utils": "workspace:*" "@backstage/errors": "workspace:^" + "@backstage/eslint-plugin": "workspace:*" "@backstage/repo-tools": "workspace:*" "@changesets/cli": "npm:^2.14.0" "@manypkg/get-packages": "npm:^1.1.3" "@octokit/rest": "npm:^19.0.3" "@playwright/test": "npm:^1.32.3" "@spotify/eslint-plugin": "npm:^15.0.0" + "@storybook/addon-essentials": "npm:^8.6.12" + "@storybook/addon-interactions": "npm:^8.6.12" + "@storybook/addon-links": "npm:^8.6.12" + "@storybook/addon-storysource": "npm:^8.6.12" + "@storybook/addon-themes": "npm:^8.6.12" + "@storybook/react": "npm:^8.6.12" + "@storybook/react-vite": "npm:^8.6.12" "@techdocs/cli": "workspace:*" "@types/cacheable-request": "npm:^8.3.6" "@types/global-agent": "npm:^2.1.3" @@ -43906,19 +44315,35 @@ __metadata: fs-extra: "npm:^11.2.0" husky: "npm:^9.0.0" lint-staged: "npm:^15.0.0" + madge: "npm:^8.0.0" minimist: "npm:^1.2.5" node-gyp: "npm:^10.0.0" prettier: "npm:^2.2.1" semver: "npm:^7.5.3" - shx: "npm:^0.3.2" + shx: "npm:^0.4.0" sloc: "npm:^0.3.1" sort-package-json: "npm:^2.8.0" - typedoc: "npm:^0.27.6" - typescript: "npm:~5.6.0" + storybook: "npm:^8.6.12" + typedoc: "npm:^0.28.0" + typescript: "npm:~5.7.0" + vite: "npm:^7.1.2" yaml: "npm:^2.7.0" languageName: unknown linkType: soft +"router@npm:^2.2.0": + version: 2.2.0 + resolution: "router@npm:2.2.0" + dependencies: + debug: "npm:^4.4.0" + depd: "npm:^2.0.0" + is-promise: "npm:^4.0.0" + parseurl: "npm:^1.3.3" + path-to-regexp: "npm:^8.0.0" + checksum: 10/8949bd1d3da5403cc024e2989fee58d7fda0f3ffe9f2dc5b8a192f295f400b3cde307b0b554f7d44851077640f36962ca469a766b3d57410d7d96245a7ba6c91 + languageName: node + linkType: hard + "rrweb-cssom@npm:^0.7.1": version: 0.7.1 resolution: "rrweb-cssom@npm:0.7.1" @@ -43960,12 +44385,12 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:7.8.1": - version: 7.8.1 - resolution: "rxjs@npm:7.8.1" +"rxjs@npm:7.8.2, rxjs@npm:^7.2.0, rxjs@npm:^7.5.5": + version: 7.8.2 + resolution: "rxjs@npm:7.8.2" dependencies: tslib: "npm:^2.1.0" - checksum: 10/b10cac1a5258f885e9dd1b70d23c34daeb21b61222ee735d2ec40a8685bdca40429000703a44f0e638c27a684ac139e1c37e835d2a0dc16f6fc061a138ae3abb + checksum: 10/03dff09191356b2b87d94fbc1e97c4e9eb3c09d4452399dddd451b09c2f1ba8d56925a40af114282d7bc0c6fe7514a2236ca09f903cf70e4bbf156650dddb49d languageName: node linkType: hard @@ -43978,15 +44403,6 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:^7.2.0, rxjs@npm:^7.5.5": - version: 7.8.2 - resolution: "rxjs@npm:7.8.2" - dependencies: - tslib: "npm:^2.1.0" - checksum: 10/03dff09191356b2b87d94fbc1e97c4e9eb3c09d4452399dddd451b09c2f1ba8d56925a40af114282d7bc0c6fe7514a2236ca09f903cf70e4bbf156650dddb49d - languageName: node - linkType: hard - "sade@npm:^1.7.3": version: 1.8.1 resolution: "sade@npm:1.8.1" @@ -44080,13 +44496,25 @@ __metadata: languageName: node linkType: hard -"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.0.2, safer-buffer@npm:^2.1.0, safer-buffer@npm:~2.1.0": +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.0.2, safer-buffer@npm:~2.1.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" checksum: 10/7eaf7a0cf37cc27b42fb3ef6a9b1df6e93a1c6d98c6c6702b02fe262d5fcbd89db63320793b99b21cb5348097d0a53de81bd5f4e8b86e20cc9412e3f1cfb4e83 languageName: node linkType: hard +"sass-lookup@npm:^6.1.0": + version: 6.1.0 + resolution: "sass-lookup@npm:6.1.0" + dependencies: + commander: "npm:^12.1.0" + enhanced-resolve: "npm:^5.18.0" + bin: + sass-lookup: bin/cli.js + checksum: 10/90a06d5b0545615919f619608019a731eee81939cd0f1f88ed006060222f57de797ad20fc0eb103e13e6a9ff3e620af773e9f3e2ddf69a99fb2ccf4513f47fa4 + languageName: node + linkType: hard + "saxes@npm:^6.0.0": version: 6.0.0 resolution: "saxes@npm:6.0.0" @@ -44125,10 +44553,10 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:^0.25.0": - version: 0.25.0 - resolution: "scheduler@npm:0.25.0" - checksum: 10/e661e38503ab29a153429a99203fefa764f28b35c079719eb5efdd2c1c1086522f6653d8ffce388209682c23891a6d1d32fa6badf53c35fb5b9cd0c55ace42de +"scheduler@npm:^0.26.0": + version: 0.26.0 + resolution: "scheduler@npm:0.26.0" + checksum: 10/1ecf2e5d7de1a7a132796834afe14a2d589ba7e437615bd8c06f3e0786a3ac3434655e67aac8755d9b14e05754c177e49c064261de2673aaa3c926bc98caa002 languageName: node linkType: hard @@ -44154,15 +44582,15 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:^4.0.0, schema-utils@npm:^4.2.0, schema-utils@npm:^4.3.0": - version: 4.3.0 - resolution: "schema-utils@npm:4.3.0" +"schema-utils@npm:^4.0.0, schema-utils@npm:^4.2.0, schema-utils@npm:^4.3.0, schema-utils@npm:^4.3.2": + version: 4.3.2 + resolution: "schema-utils@npm:4.3.2" dependencies: "@types/json-schema": "npm:^7.0.9" ajv: "npm:^8.9.0" ajv-formats: "npm:^2.1.1" ajv-keywords: "npm:^5.1.0" - checksum: 10/86c5a7c72a275c56f140bc3cdd832d56efb11428c88ad588127db12cb9b2c83ccaa9540e115d7baa9c6175b5e360094457e29c44e6fb76787c9498c2eb6df5d6 + checksum: 10/02c32c34aae762d48468f98465a96a167fede637772871c7c7d8923671ddb9f20b2cc6f6e8448ae6bef5363e3597493c655212c8b06a4ee73aa099d9452fbd8b languageName: node linkType: hard @@ -44282,11 +44710,11 @@ __metadata: linkType: hard "semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.2, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2": - version: 7.7.1 - resolution: "semver@npm:7.7.1" + version: 7.7.2 + resolution: "semver@npm:7.7.2" bin: semver: bin/semver.js - checksum: 10/4cfa1eb91ef3751e20fc52e47a935a0118d56d6f15a837ab814da0c150778ba2ca4f1a4d9068b33070ea4273629e615066664c2cfcd7c272caf7a8a0f6518b2c + checksum: 10/7a24cffcaa13f53c09ce55e05efe25cd41328730b2308678624f8b9f5fc3093fc4d189f47950f0b811ff8f3c3039c24a2c36717ba7961615c682045bf03e1dda languageName: node linkType: hard @@ -44311,6 +44739,25 @@ __metadata: languageName: node linkType: hard +"send@npm:^1.1.0, send@npm:^1.2.0": + version: 1.2.0 + resolution: "send@npm:1.2.0" + dependencies: + debug: "npm:^4.3.5" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + etag: "npm:^1.8.1" + fresh: "npm:^2.0.0" + http-errors: "npm:^2.0.0" + mime-types: "npm:^3.0.1" + ms: "npm:^2.1.3" + on-finished: "npm:^2.4.1" + range-parser: "npm:^1.2.1" + statuses: "npm:^2.0.1" + checksum: 10/9fa3b1a3b9a06b7b4ab00c25e8228326d9665a9745753a34d1ffab8ac63c7c206727331d1dc5be73647f1b658d259a1aa8e275b0e0eee51349370af02e9da506 + languageName: node + linkType: hard + "seq-queue@npm:^0.0.5": version: 0.0.5 resolution: "seq-queue@npm:0.0.5" @@ -44387,6 +44834,18 @@ __metadata: languageName: node linkType: hard +"serve-static@npm:^2.2.0": + version: 2.2.0 + resolution: "serve-static@npm:2.2.0" + dependencies: + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + parseurl: "npm:^1.3.3" + send: "npm:^1.2.0" + checksum: 10/9f1a900738c5bb02258275ce3bd1273379c4c3072b622e15d44e8f47d89a1ba2d639ec2d63b11c263ca936096b40758acb7a0d989cd6989018a65a12f9433ada + languageName: node + linkType: hard + "set-blocking@npm:^2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -44395,9 +44854,9 @@ __metadata: linkType: hard "set-cookie-parser@npm:^2.4.6": - version: 2.6.0 - resolution: "set-cookie-parser@npm:2.6.0" - checksum: 10/8d451ebadb760989f93b634942c79de3c925ca7a986d133d08a80c40b5ae713ce12e354f0d5245c49f288c52daa7bd6554d5dc52f8a4eecaaf5e192881cf2b1f + version: 2.7.1 + resolution: "set-cookie-parser@npm:2.7.1" + checksum: 10/c92b1130032693342bca13ea1b1bc93967ab37deec4387fcd8c2a843c0ef2fd9a9f3df25aea5bb3976cd05a91c2cf4632dd6164d6e1814208fb7d7e14edd42b4 languageName: node linkType: hard @@ -44532,16 +44991,17 @@ __metadata: languageName: node linkType: hard -"shelljs@npm:^0.8.5": - version: 0.8.5 - resolution: "shelljs@npm:0.8.5" +"shelljs@npm:^0.9.2": + version: 0.9.2 + resolution: "shelljs@npm:0.9.2" dependencies: - glob: "npm:^7.0.0" + execa: "npm:^1.0.0" + fast-glob: "npm:^3.3.2" interpret: "npm:^1.0.0" rechoir: "npm:^0.6.2" bin: shjs: bin/shjs - checksum: 10/f2178274b97b44332bbe9ddb78161137054f55ecf701c7a99db9552cb5478fe279ad5f5131d8a7c2f0730e01ccf0c629d01094143f0541962ce1a3d0243d23f7 + checksum: 10/d264239d4b5f2cb075270a8de5f6c8f8ace1c8320efc8fa05b9a0c94bd9cb8aabb296f4942772b2c225d32431ead0b45a40861b2be5ace9f0d09b50d6a5261cd languageName: node linkType: hard @@ -44562,15 +45022,15 @@ __metadata: languageName: node linkType: hard -"shx@npm:^0.3.2": - version: 0.3.4 - resolution: "shx@npm:0.3.4" +"shx@npm:^0.4.0": + version: 0.4.0 + resolution: "shx@npm:0.4.0" dependencies: - minimist: "npm:^1.2.3" - shelljs: "npm:^0.8.5" + minimist: "npm:^1.2.8" + shelljs: "npm:^0.9.2" bin: shx: lib/cli.js - checksum: 10/5271b60f7e322540799102ad6935121f10c857a995a1321357a7bffd1628674a4758a602153dc5cc126d8dc94d3489586587d3dee54dc3cac0222ca08a78e33a + checksum: 10/8df78b1f2e099de11d4ed44846878cb811cee02b40a08232302d59a8cb130e7be8ef71698e70e0984c345202bab39c35d5e0202cc0f0d0ea7b0d0501fb81ac00 languageName: node linkType: hard @@ -44882,7 +45342,7 @@ __metadata: languageName: node linkType: hard -"socks-proxy-agent@npm:^8.0.1, socks-proxy-agent@npm:^8.0.2, socks-proxy-agent@npm:^8.0.3, socks-proxy-agent@npm:^8.0.4": +"socks-proxy-agent@npm:^8.0.1, socks-proxy-agent@npm:^8.0.3, socks-proxy-agent@npm:^8.0.4, socks-proxy-agent@npm:^8.0.5": version: 8.0.5 resolution: "socks-proxy-agent@npm:8.0.5" dependencies: @@ -45154,27 +45614,6 @@ __metadata: languageName: node linkType: hard -"sshpk@npm:^1.7.0": - version: 1.16.1 - resolution: "sshpk@npm:1.16.1" - dependencies: - asn1: "npm:~0.2.3" - assert-plus: "npm:^1.0.0" - bcrypt-pbkdf: "npm:^1.0.0" - dashdash: "npm:^1.12.0" - ecc-jsbn: "npm:~0.1.1" - getpass: "npm:^0.1.1" - jsbn: "npm:~0.1.0" - safer-buffer: "npm:^2.0.2" - tweetnacl: "npm:~0.14.0" - bin: - sshpk-conv: bin/sshpk-conv - sshpk-sign: bin/sshpk-sign - sshpk-verify: bin/sshpk-verify - checksum: 10/b437fd3fd2777b29b5425b57d3d352b5771d5c61ddb0e000ddc6ff4b8a5b69c2d1d6b188787a0577df2b125045a492d2c9d03452067e87c49b013bd273e1c70a - languageName: node - linkType: hard - "ssri@npm:^10.0.0": version: 10.0.5 resolution: "ssri@npm:10.0.5" @@ -45319,10 +45758,10 @@ __metadata: linkType: hard "storybook@npm:^8.6.12": - version: 8.6.12 - resolution: "storybook@npm:8.6.12" + version: 8.6.14 + resolution: "storybook@npm:8.6.14" dependencies: - "@storybook/core": "npm:8.6.12" + "@storybook/core": "npm:8.6.14" peerDependencies: prettier: ^2 || ^3 peerDependenciesMeta: @@ -45332,7 +45771,7 @@ __metadata: getstorybook: ./bin/index.cjs sb: ./bin/index.cjs storybook: ./bin/index.cjs - checksum: 10/babd1d086eb02ba25ee659e02e619f7797a6b91028ad74d2da0ab77e72021cd5c2ac4f239668f15156aabf00bd97066a774370dceadf178b1e649bf971160a26 + checksum: 10/104932fe29ebf49bef24c90285741cec964d1c36b3f0b38da1dace31ac664be457f2a510e41ba69fe8aa0d90c20e3446fec27d1ced41e5eefb3a9ca713d99e79 languageName: node linkType: hard @@ -45397,7 +45836,7 @@ __metadata: languageName: node linkType: hard -"stream-to-array@npm:~2.3.0": +"stream-to-array@npm:^2.3.0, stream-to-array@npm:~2.3.0": version: 2.3.0 resolution: "stream-to-array@npm:2.3.0" dependencies: @@ -45634,6 +46073,17 @@ __metadata: languageName: node linkType: hard +"stringify-object@npm:^3.2.1": + version: 3.3.0 + resolution: "stringify-object@npm:3.3.0" + dependencies: + get-own-enumerable-property-symbols: "npm:^3.0.0" + is-obj: "npm:^1.0.1" + is-regexp: "npm:^1.0.0" + checksum: 10/973782f09a3df3f39a2cf07dbf43fb9ba6cb32976f3616cd0f6c10e0a5c5415dd72b7b700e72920e8da2bf57c3001b8e37b5af7174bab9a748ce0416989e19b1 + languageName: node + linkType: hard + "strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:6.0, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -45770,6 +46220,15 @@ __metadata: languageName: node linkType: hard +"strtok3@npm:^10.2.2": + version: 10.3.1 + resolution: "strtok3@npm:10.3.1" + dependencies: + "@tokenizer/token": "npm:^0.3.0" + checksum: 10/bb7950cc9ce98ec742a5db360630f0b004f16197959ae28d8c8dad4f8f0e405d71cfdc992483038ba29a0b4cbd7227618ad2492005b510d84a3fc5903df0c13f + languageName: node + linkType: hard + "strtok3@npm:^6.2.4": version: 6.2.4 resolution: "strtok3@npm:6.2.4" @@ -45877,6 +46336,17 @@ __metadata: languageName: node linkType: hard +"stylus-lookup@npm:^6.1.0": + version: 6.1.0 + resolution: "stylus-lookup@npm:6.1.0" + dependencies: + commander: "npm:^12.1.0" + bin: + stylus-lookup: bin/cli.js + checksum: 10/0d998a648025b5e4ad07b65c9f0ee3a0b67cfdbecc352d56e6b2806f9f0ce966ffa287922d3dde7e7e245510a10d6435169dbaa015bdda44cccc60907a2bbce7 + languageName: node + linkType: hard + "sucrase@npm:^3.20.2": version: 3.35.0 resolution: "sucrase@npm:3.35.0" @@ -45902,30 +46372,30 @@ __metadata: languageName: node linkType: hard -"superagent@npm:^9.0.1": - version: 9.0.2 - resolution: "superagent@npm:9.0.2" +"superagent@npm:^10.2.3": + version: 10.2.3 + resolution: "superagent@npm:10.2.3" dependencies: - component-emitter: "npm:^1.3.0" + component-emitter: "npm:^1.3.1" cookiejar: "npm:^2.1.4" - debug: "npm:^4.3.4" + debug: "npm:^4.3.7" fast-safe-stringify: "npm:^2.1.1" - form-data: "npm:^4.0.0" - formidable: "npm:^3.5.1" + form-data: "npm:^4.0.4" + formidable: "npm:^3.5.4" methods: "npm:^1.1.2" mime: "npm:2.6.0" - qs: "npm:^6.11.0" - checksum: 10/d3c0c9051ceec84d5b431eaa410ad81bcd53255cea57af1fc66d683a24c34f3ba4761b411072a9bf489a70e3d5b586a78a0e6f2eac6a561067e7d196ddab0907 + qs: "npm:^6.11.2" + checksum: 10/377bf938e68927dd772169c5285be27872bf6e84fac01c52bcd9396bc5b348c9ded8f8be54649510ec09a67bc5096055847b37cb01b3bca0eb06ff1856170e35 languageName: node linkType: hard "supertest@npm:^7.0.0": - version: 7.1.0 - resolution: "supertest@npm:7.1.0" + version: 7.1.4 + resolution: "supertest@npm:7.1.4" dependencies: methods: "npm:^1.1.2" - superagent: "npm:^9.0.1" - checksum: 10/20069f739a44821dfa4f7f397b9086ef31a358366331138f97945eedb2e231796e7c55b032125d3bd12f9839f089fbb809893dbc0f98edc57e12333b9f42b726 + superagent: "npm:^10.2.3" + checksum: 10/ecb5d41f2b62b257dbdcabac245c32b8e8fb264fe2636dd85c2c883569d23dc14adc0a471abb84187cbdb49bc36ad870ad355b4a0b85973f510fd57fc229e6cc languageName: node linkType: hard @@ -45972,14 +46442,7 @@ __metadata: languageName: node linkType: hard -"svg-parser@npm:^2.0.4": - version: 2.0.4 - resolution: "svg-parser@npm:2.0.4" - checksum: 10/ec196da6ea21481868ab26911970e35488361c39ead1c6cdd977ba16c885c21a91ddcbfd113bfb01f79a822e2a751ef85b2f7f95e2cb9245558ebce12c34af1f - languageName: node - linkType: hard - -"svgo@npm:^2.7.0, svgo@npm:^2.8.0": +"svgo@npm:^2.7.0": version: 2.8.0 resolution: "svgo@npm:2.8.0" dependencies: @@ -46067,7 +46530,7 @@ __metadata: languageName: node linkType: hard -"swc-loader@npm:^0.2.3, swc-loader@npm:^0.2.6": +"swc-loader@npm:^0.2.3": version: 0.2.6 resolution: "swc-loader@npm:0.2.6" dependencies: @@ -46315,7 +46778,7 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:*, terser-webpack-plugin@npm:^5.1.3, terser-webpack-plugin@npm:^5.3.1, terser-webpack-plugin@npm:^5.3.10": +"terser-webpack-plugin@npm:*, terser-webpack-plugin@npm:^5.1.3, terser-webpack-plugin@npm:^5.3.10, terser-webpack-plugin@npm:^5.3.11": version: 5.3.14 resolution: "terser-webpack-plugin@npm:5.3.14" dependencies: @@ -46431,6 +46894,15 @@ __metadata: languageName: node linkType: hard +"thingies@npm:^2.5.0": + version: 2.5.0 + resolution: "thingies@npm:2.5.0" + peerDependencies: + tslib: ^2 + checksum: 10/b8b028a474aab798ff12ad5a5648306059976d3e23870327ae4ef640012953314b1d7f208dd5a8e9ebaeee6dd1cdb05503bb829699ee8f36514c37f771f2f035 + languageName: node + linkType: hard + "throttle-debounce@npm:^3.0.1": version: 3.0.1 resolution: "throttle-debounce@npm:3.0.1" @@ -46518,13 +46990,13 @@ __metadata: languageName: node linkType: hard -"tinyglobby@npm:^0.2.9": - version: 0.2.10 - resolution: "tinyglobby@npm:0.2.10" +"tinyglobby@npm:^0.2.14, tinyglobby@npm:^0.2.9": + version: 0.2.14 + resolution: "tinyglobby@npm:0.2.14" dependencies: - fdir: "npm:^6.4.2" + fdir: "npm:^6.4.4" picomatch: "npm:^4.0.2" - checksum: 10/10c976866d849702edc47fc3fef27d63f074c40f75ef17171ecc1452967900699fa1e62373681dd58e673ddff2e3f6094bcd0a2101e3e4b30f4c2b9da41397f2 + checksum: 10/3d306d319718b7cc9d79fb3f29d8655237aa6a1f280860a217f93417039d0614891aee6fc47c5db315f4fcc6ac8d55eb8e23e2de73b2c51a431b42456d9e5764 languageName: node linkType: hard @@ -46549,13 +47021,20 @@ __metadata: languageName: node linkType: hard -"tinyspy@npm:^3.0.0, tinyspy@npm:^3.0.2": +"tinyspy@npm:^3.0.0": version: 3.0.2 resolution: "tinyspy@npm:3.0.2" checksum: 10/5db671b2ff5cd309de650c8c4761ca945459d7204afb1776db9a04fb4efa28a75f08517a8620c01ee32a577748802231ad92f7d5b194dc003ee7f987a2a06337 languageName: node linkType: hard +"tinyspy@npm:^4.0.3": + version: 4.0.3 + resolution: "tinyspy@npm:4.0.3" + checksum: 10/b6a3ed40dd76a2b3c020250cf1401506b456509d1fb9dba0c7b0e644d258dac722843b85c57ccc36c8687db1e7978cb6adcc43e3b71c475910c085b96d41cb53 + languageName: node + linkType: hard + "tldts-core@npm:^6.1.51": version: 6.1.51 resolution: "tldts-core@npm:6.1.51" @@ -46646,6 +47125,16 @@ __metadata: languageName: node linkType: hard +"token-types@npm:^6.0.0": + version: 6.0.0 + resolution: "token-types@npm:6.0.0" + dependencies: + "@tokenizer/token": "npm:^0.3.0" + ieee754: "npm:^1.2.1" + checksum: 10/b541b605d602e8e6495745badb35f90ee8f997e43dc29bc51aee7e9a0bc3c6bc7372a305bd45f3e80d75223c2b6a5c7e65cb5159d8c4e49fa25cdbaae531fad4 + languageName: node + linkType: hard + "tosource@npm:^2.0.0-alpha.3": version: 2.0.0-alpha.3 resolution: "tosource@npm:2.0.0-alpha.3" @@ -46685,16 +47174,6 @@ __metadata: languageName: node linkType: hard -"tough-cookie@npm:~2.5.0": - version: 2.5.0 - resolution: "tough-cookie@npm:2.5.0" - dependencies: - psl: "npm:^1.1.28" - punycode: "npm:^2.1.1" - checksum: 10/024cb13a4d1fe9af57f4323dff765dd9b217cc2a69be77e3b8a1ca45600aa33a097b6ad949f225d885e904f4bd3ceccef104741ef202d8378e6ca78e850ff82f - languageName: node - linkType: hard - "tr46@npm:^3.0.0": version: 3.0.0 resolution: "tr46@npm:3.0.0" @@ -46720,6 +47199,15 @@ __metadata: languageName: node linkType: hard +"tree-dump@npm:^1.0.1": + version: 1.0.3 + resolution: "tree-dump@npm:1.0.3" + peerDependencies: + tslib: 2 + checksum: 10/cf382e61cfb5e3ff8f03425b5bc1923e8f0e385b3a02f43d9d0a32d09da9984477e0f2a7698628662263d1d3f1af17e33486c77ff454978f0f9f07fb5d1fe9a2 + languageName: node + linkType: hard + "tree-kill@npm:^1.2.2": version: 1.2.2 resolution: "tree-kill@npm:1.2.2" @@ -46807,12 +47295,33 @@ __metadata: languageName: node linkType: hard -"ts-api-utils@npm:^2.0.1": - version: 2.0.1 - resolution: "ts-api-utils@npm:2.0.1" +"ts-api-utils@npm:^2.0.1, ts-api-utils@npm:^2.1.0": + version: 2.1.0 + resolution: "ts-api-utils@npm:2.1.0" peerDependencies: typescript: ">=4.8.4" - checksum: 10/2e68938cd5acad6b5157744215ce10cd097f9f667fd36b5fdd5efdd4b0c51063e855459d835f94f6777bb8a0f334916b6eb5c1eedab8c325feb34baa39238898 + checksum: 10/02e55b49d9617c6eebf8aadfa08d3ca03ca0cd2f0586ad34117fdfc7aa3cd25d95051843fde9df86665ad907f99baed179e7a117b11021417f379e4d2614eacd + languageName: node + linkType: hard + +"ts-checker-rspack-plugin@npm:^1.1.5": + version: 1.1.5 + resolution: "ts-checker-rspack-plugin@npm:1.1.5" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@rspack/lite-tapable": "npm:^1.0.1" + chokidar: "npm:^3.6.0" + is-glob: "npm:^4.0.3" + memfs: "npm:^4.28.0" + minimatch: "npm:^9.0.5" + picocolors: "npm:^1.1.1" + peerDependencies: + "@rspack/core": ^1.0.0 + typescript: ">=3.8.0" + peerDependenciesMeta: + "@rspack/core": + optional: true + checksum: 10/d43f87a5860f3b8f528bf8f43c78c2d958a18fa484d791a204047fc5672134fd689665021ee25b07f9f9d8c807cd291978f58b789bf531b4704a9a4e3ad84735 languageName: node linkType: hard @@ -46830,6 +47339,18 @@ __metadata: languageName: node linkType: hard +"ts-graphviz@npm:^2.1.2": + version: 2.1.6 + resolution: "ts-graphviz@npm:2.1.6" + dependencies: + "@ts-graphviz/adapter": "npm:^2.0.6" + "@ts-graphviz/ast": "npm:^2.0.7" + "@ts-graphviz/common": "npm:^2.1.5" + "@ts-graphviz/core": "npm:^2.0.7" + checksum: 10/7490978263c2365b80b687964c13ad622a05b00e307c1ce448317a551fa1ee7fc03f37cd88d606ba23a030dd3baeb5b147d06d5af087b73133ac9276a7d65449 + languageName: node + linkType: hard + "ts-interface-checker@npm:^0.1.9": version: 0.1.13 resolution: "ts-interface-checker@npm:0.1.13" @@ -46952,13 +47473,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.7.0": - version: 2.7.0 - resolution: "tslib@npm:2.7.0" - checksum: 10/9a5b47ddac65874fa011c20ff76db69f97cf90c78cff5934799ab8894a5342db2d17b4e7613a087046bc1d133d21547ddff87ac558abeec31ffa929c88b7fce6 - languageName: node - linkType: hard - "tslib@npm:^1.11.1, tslib@npm:^1.13.0, tslib@npm:^1.14.1, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": version: 1.14.1 resolution: "tslib@npm:1.14.1" @@ -47018,7 +47532,7 @@ __metadata: languageName: node linkType: hard -"tweetnacl@npm:^0.14.3, tweetnacl@npm:~0.14.0": +"tweetnacl@npm:^0.14.3": version: 0.14.5 resolution: "tweetnacl@npm:0.14.5" checksum: 10/04ee27901cde46c1c0a64b9584e04c96c5fe45b38c0d74930710751ea991408b405747d01dfae72f80fc158137018aea94f9c38c651cb9c318f0861a310c3679 @@ -47106,7 +47620,7 @@ __metadata: languageName: node linkType: hard -"type-is@npm:^1.6.16, type-is@npm:^1.6.18, type-is@npm:^1.6.4, type-is@npm:~1.6.18": +"type-is@npm:^1.6.16, type-is@npm:^1.6.18, type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" dependencies: @@ -47116,6 +47630,17 @@ __metadata: languageName: node linkType: hard +"type-is@npm:^2.0.0, type-is@npm:^2.0.1": + version: 2.0.1 + resolution: "type-is@npm:2.0.1" + dependencies: + content-type: "npm:^1.0.5" + media-typer: "npm:^1.1.0" + mime-types: "npm:^3.0.0" + checksum: 10/bacdb23c872dacb7bd40fbd9095e6b2fca2895eedbb689160c05534d7d4810a7f4b3fd1ae87e96133c505958f6d602967a68db5ff577b85dd6be76eaa75d58af + languageName: node + linkType: hard + "type@npm:^1.0.1": version: 1.2.0 resolution: "type@npm:1.2.0" @@ -47219,20 +47744,20 @@ __metadata: languageName: node linkType: hard -"typedoc@npm:^0.27.6": - version: 0.27.9 - resolution: "typedoc@npm:0.27.9" +"typedoc@npm:^0.28.0": + version: 0.28.9 + resolution: "typedoc@npm:0.28.9" dependencies: - "@gerrit0/mini-shiki": "npm:^1.24.0" + "@gerrit0/mini-shiki": "npm:^3.9.0" lunr: "npm:^2.3.9" markdown-it: "npm:^14.1.0" minimatch: "npm:^9.0.5" - yaml: "npm:^2.6.1" + yaml: "npm:^2.8.0" peerDependencies: - typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x + typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x bin: typedoc: bin/typedoc - checksum: 10/fb1e4b54849cad1628543fb24863358320b737aabba83194562360cfbcaac8684b2b18824fd623bb27a9e8dbbc0e01c0b88fedd9a642d78e7a3c108387e44d25 + checksum: 10/b803651a2c1295c293c68b97bc8aa722ba89fe253b23104aab0f751f30921b69a813352173bf09fa65efc0c6dfbce11305f365218ee7298f955a0993907da36d languageName: node linkType: hard @@ -47273,6 +47798,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:^5.7.3, typescript@npm:^5.8.3": + version: 5.8.3 + resolution: "typescript@npm:5.8.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10/65c40944c51b513b0172c6710ee62e951b70af6f75d5a5da745cb7fab132c09ae27ffdf7838996e3ed603bb015dadd099006658046941bd0ba30340cc563ae92 + languageName: node + linkType: hard + "typescript@npm:~5.5.0": version: 5.5.4 resolution: "typescript@npm:5.5.4" @@ -47283,13 +47818,13 @@ __metadata: languageName: node linkType: hard -"typescript@npm:~5.6.0": - version: 5.6.3 - resolution: "typescript@npm:5.6.3" +"typescript@npm:~5.7.0": + version: 5.7.3 + resolution: "typescript@npm:5.7.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/c328e418e124b500908781d9f7b9b93cf08b66bf5936d94332b463822eea2f4e62973bfb3b8a745fdc038785cb66cf59d1092bac3ec2ac6a3e5854687f7833f1 + checksum: 10/6a7e556de91db3d34dc51cd2600e8e91f4c312acd8e52792f243c7818dfadb27bae677175fad6947f9c81efb6c57eb6b2d0c736f196a6ee2f1f7d57b74fc92fa languageName: node linkType: hard @@ -47303,6 +47838,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A^5.7.3#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.8.3#optional!builtin<compat/typescript>": + version: 5.8.3 + resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin<compat/typescript>::version=5.8.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10/b9b1e73dabac5dc730c041325dbd9c99467c1b0d239f1b74ec3b90d831384af3e2ba973946232df670519147eb51a2c20f6f96163cea2b359f03de1e2091cc4f + languageName: node + linkType: hard + "typescript@patch:typescript@npm%3A~5.5.0#optional!builtin<compat/typescript>": version: 5.5.4 resolution: "typescript@patch:typescript@npm%3A5.5.4#optional!builtin<compat/typescript>::version=5.5.4&hash=379a07" @@ -47313,13 +47858,13 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A~5.6.0#optional!builtin<compat/typescript>": - version: 5.6.3 - resolution: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin<compat/typescript>::version=5.6.3&hash=8c6c40" +"typescript@patch:typescript@npm%3A~5.7.0#optional!builtin<compat/typescript>": + version: 5.7.3 + resolution: "typescript@patch:typescript@npm%3A5.7.3#optional!builtin<compat/typescript>::version=5.7.3&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/00504c01ee42d470c23495426af07512e25e6546bce7e24572e72a9ca2e6b2e9bea63de4286c3cfea644874da1467dcfca23f4f98f7caf20f8b03c0213bb6837 + checksum: 10/dc58d777eb4c01973f7fbf1fd808aad49a0efdf545528dab9b07d94fdcb65b8751742804c3057e9619a4627f2d9cc85547fdd49d9f4326992ad0181b49e61d81 languageName: node linkType: hard @@ -47385,6 +47930,13 @@ __metadata: languageName: node linkType: hard +"uint8array-extras@npm:^1.4.0": + version: 1.4.0 + resolution: "uint8array-extras@npm:1.4.0" + checksum: 10/4d2955d67c112e5ebaa4901272a75fc9ad14902c40f05a178b01e32387aa2702b6840472d931a1ca16e068ac59013c7d9ee2b4b2f141c4e73ba4bc7456490599 + languageName: node + linkType: hard + "unbox-primitive@npm:^1.1.0": version: 1.1.0 resolution: "unbox-primitive@npm:1.1.0" @@ -47440,18 +47992,18 @@ __metadata: linkType: hard "undici@npm:^5.28.4": - version: 5.28.4 - resolution: "undici@npm:5.28.4" + version: 5.29.0 + resolution: "undici@npm:5.29.0" dependencies: "@fastify/busboy": "npm:^2.0.0" - checksum: 10/a666a9f5ac4270c659fafc33d78b6b5039a0adbae3e28f934774c85dcc66ea91da907896f12b414bd6f578508b44d5dc206fa636afa0e49a4e1c9e99831ff065 + checksum: 10/0ceca8924a32acdcc0cfb8dd2d368c217840970aa3f5e314fc169608474be6341c5b8e50cad7bd257dbe3b4e432bc5d0a0d000f83644b54fa11a48735ec52b93 languageName: node linkType: hard "undici@npm:^7.2.3": - version: 7.2.3 - resolution: "undici@npm:7.2.3" - checksum: 10/92870d186682c19f4b9909e4b6229a88b4e73e6e12d7a3e151033cefb66579bdc1d36e46d97e386161ba0afcd84b718e917fca7b7f08d409d918689343034c09 + version: 7.9.0 + resolution: "undici@npm:7.9.0" + checksum: 10/fa92d3d9106612be566e79942174e00426c2e1f9a688fb86c8d1809c84a032c02fc57bd601d2051a45d94bbd312c50221644b58c8186c4f0d64f8ecc7f18b806 languageName: node linkType: hard @@ -47464,37 +48016,6 @@ __metadata: languageName: node linkType: hard -"unicode-canonical-property-names-ecmascript@npm:^2.0.0": - version: 2.0.0 - resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.0" - checksum: 10/39be078afd014c14dcd957a7a46a60061bc37c4508ba146517f85f60361acf4c7539552645ece25de840e17e293baa5556268d091ca6762747fdd0c705001a45 - languageName: node - linkType: hard - -"unicode-match-property-ecmascript@npm:^2.0.0": - version: 2.0.0 - resolution: "unicode-match-property-ecmascript@npm:2.0.0" - dependencies: - unicode-canonical-property-names-ecmascript: "npm:^2.0.0" - unicode-property-aliases-ecmascript: "npm:^2.0.0" - checksum: 10/1f34a7434a23df4885b5890ac36c5b2161a809887000be560f56ad4b11126d433c0c1c39baf1016bdabed4ec54829a6190ee37aa24919aa116dc1a5a8a62965a - languageName: node - linkType: hard - -"unicode-match-property-value-ecmascript@npm:^2.1.0": - version: 2.1.0 - resolution: "unicode-match-property-value-ecmascript@npm:2.1.0" - checksum: 10/06661bc8aba2a60c7733a7044f3e13085808939ad17924ffd4f5222a650f88009eb7c09481dc9c15cfc593d4ad99bd1cde8d54042733b335672591a81c52601c - languageName: node - linkType: hard - -"unicode-property-aliases-ecmascript@npm:^2.0.0": - version: 2.0.0 - resolution: "unicode-property-aliases-ecmascript@npm:2.0.0" - checksum: 10/dda4d39128cbbede2ac60fbb85493d979ec65913b8a486bf7cb7a375a2346fa48cbf9dc6f1ae23376e7e8e684c2b411434891e151e865a661b40a85407db51d0 - languageName: node - linkType: hard - "unicorn-magic@npm:^0.1.0": version: 0.1.0 resolution: "unicorn-magic@npm:0.1.0" @@ -47716,12 +48237,12 @@ __metadata: linkType: hard "unplugin@npm:^1.3.1": - version: 1.16.0 - resolution: "unplugin@npm:1.16.0" + version: 1.16.1 + resolution: "unplugin@npm:1.16.1" dependencies: acorn: "npm:^8.14.0" webpack-virtual-modules: "npm:^0.6.2" - checksum: 10/5cb6704e11eb39b68b1f51dbdc48b0bd4ac01e3ceefe8f722a3cb26192d5e0a30619a3d3f4cf5a479e41097736d24729f10f6fc2bf1fa62213912306ea459049 + checksum: 10/4b46d7d2a63d334a45111ba57a266b3f8993ef12a72b77d7b31ffc455e8a9bef9c0e37ea463eb409dbf7ccec0b9868aeb845dd42c690d9288e4b8ac2d90fbefd languageName: node linkType: hard @@ -47840,7 +48361,7 @@ __metadata: languageName: node linkType: hard -"url@npm:^0.11.0, url@npm:^0.11.4": +"url@npm:^0.11.4": version: 0.11.4 resolution: "url@npm:0.11.4" dependencies: @@ -48038,7 +48559,7 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^3.3.2, uuid@npm:^3.4.0": +"uuid@npm:^3.4.0": version: 3.4.0 resolution: "uuid@npm:3.4.0" bin: @@ -48234,6 +48755,61 @@ __metadata: languageName: node linkType: hard +"vite@npm:^7.1.2": + version: 7.1.2 + resolution: "vite@npm:7.1.2" + dependencies: + esbuild: "npm:^0.25.0" + fdir: "npm:^6.4.6" + fsevents: "npm:~2.3.3" + picomatch: "npm:^4.0.3" + postcss: "npm:^8.5.6" + rollup: "npm:^4.43.0" + tinyglobby: "npm:^0.2.14" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + jiti: ">=1.21.0" + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10/942188896db181bd5f95c576303eb617cd08580eb129d0223bc87a8ed9b8dc0d60d8487c077fd9a01087c55600af132c5fc677e51b4b48a4a54a376056969edb + languageName: node + linkType: hard + "vm-browserify@npm:^1.0.1": version: 1.1.2 resolution: "vm-browserify@npm:1.1.2" @@ -48296,6 +48872,13 @@ __metadata: languageName: node linkType: hard +"walkdir@npm:^0.4.1": + version: 0.4.1 + resolution: "walkdir@npm:0.4.1" + checksum: 10/54cbe7afc5fb811a55748b0bfa077a9a4aa43f568eb5857db9785af9728e1ad8b1ecf6b9ce6f14b405c6124939a92522e36aaa0397f3f52a9a7a08496f2eebe1 + languageName: node + linkType: hard + "walker@npm:^1.0.8": version: 1.0.8 resolution: "walker@npm:1.0.8" @@ -48381,24 +48964,6 @@ __metadata: languageName: node linkType: hard -"webpack-dev-middleware@npm:^6.1.2": - version: 6.1.3 - resolution: "webpack-dev-middleware@npm:6.1.3" - dependencies: - colorette: "npm:^2.0.10" - memfs: "npm:^3.4.12" - mime-types: "npm:^2.1.31" - range-parser: "npm:^1.2.1" - schema-utils: "npm:^4.0.0" - peerDependencies: - webpack: ^5.0.0 - peerDependenciesMeta: - webpack: - optional: true - checksum: 10/ee699430c33c4dfa2a016becc85e32a9b04aa0b6edbce0bb173c4dfd29c80c77d192d14fd2f2ec500dbdede4e0f1c5557993aa20a04a44190750a1e8e13f6d67 - languageName: node - linkType: hard - "webpack-dev-middleware@npm:^7.4.2": version: 7.4.2 resolution: "webpack-dev-middleware@npm:7.4.2" @@ -48418,53 +48983,9 @@ __metadata: languageName: node linkType: hard -"webpack-dev-server@npm:5.2.0": - version: 5.2.0 - resolution: "webpack-dev-server@npm:5.2.0" - dependencies: - "@types/bonjour": "npm:^3.5.13" - "@types/connect-history-api-fallback": "npm:^1.5.4" - "@types/express": "npm:^4.17.21" - "@types/serve-index": "npm:^1.9.4" - "@types/serve-static": "npm:^1.15.5" - "@types/sockjs": "npm:^0.3.36" - "@types/ws": "npm:^8.5.10" - ansi-html-community: "npm:^0.0.8" - bonjour-service: "npm:^1.2.1" - chokidar: "npm:^3.6.0" - colorette: "npm:^2.0.10" - compression: "npm:^1.7.4" - connect-history-api-fallback: "npm:^2.0.0" - express: "npm:^4.21.2" - graceful-fs: "npm:^4.2.6" - http-proxy-middleware: "npm:^2.0.7" - ipaddr.js: "npm:^2.1.0" - launch-editor: "npm:^2.6.1" - open: "npm:^10.0.3" - p-retry: "npm:^6.2.0" - schema-utils: "npm:^4.2.0" - selfsigned: "npm:^2.4.1" - serve-index: "npm:^1.9.1" - sockjs: "npm:^0.3.24" - spdy: "npm:^4.0.2" - webpack-dev-middleware: "npm:^7.4.2" - ws: "npm:^8.18.0" - peerDependencies: - webpack: ^5.0.0 - peerDependenciesMeta: - webpack: - optional: true - webpack-cli: - optional: true - bin: - webpack-dev-server: bin/webpack-dev-server.js - checksum: 10/f93ca46b037e547a9db157db72ef98ab177659ad13a6e63302d87bd77b32e524dd7133f1ad18f5a51ec68712911c59be8d4e06aa7bcbe6f56a9e9ce3774cf7f6 - languageName: node - linkType: hard - -"webpack-dev-server@npm:^5.0.0": - version: 5.2.1 - resolution: "webpack-dev-server@npm:5.2.1" +"webpack-dev-server@npm:5.2.2, webpack-dev-server@npm:^5.0.0": + version: 5.2.2 + resolution: "webpack-dev-server@npm:5.2.2" dependencies: "@types/bonjour": "npm:^3.5.13" "@types/connect-history-api-fallback": "npm:^1.5.4" @@ -48482,7 +49003,7 @@ __metadata: connect-history-api-fallback: "npm:^2.0.0" express: "npm:^4.21.2" graceful-fs: "npm:^4.2.6" - http-proxy-middleware: "npm:^2.0.7" + http-proxy-middleware: "npm:^2.0.9" ipaddr.js: "npm:^2.1.0" launch-editor: "npm:^2.6.1" open: "npm:^10.0.3" @@ -48503,18 +49024,7 @@ __metadata: optional: true bin: webpack-dev-server: bin/webpack-dev-server.js - checksum: 10/424edfe22b7bbe2301a38b8b519dfeb7643e0ca643be01af3fa48ec18512955c1952246741d7577bdb911ee09dcd6c521ade7d65e0059448ee69ab02bfac4624 - languageName: node - linkType: hard - -"webpack-hot-middleware@npm:^2.25.1": - version: 2.26.1 - resolution: "webpack-hot-middleware@npm:2.26.1" - dependencies: - ansi-html-community: "npm:0.0.8" - html-entities: "npm:^2.1.0" - strip-ansi: "npm:^6.0.0" - checksum: 10/69fa1a25284eeba386c99b0b159d61b0cf800d21379ae7b03203c52e5d58d9082d96ca9f98ebbd8436165cd105de496a0356a8191064b277abff4d3c56825843 + checksum: 10/59517409cd38c01a875a03b9658f3d20d492b5b8bead9ded4a0f3d33e6857daf2d352fe89f0181dcaea6d0fbe84b0494cb4750a87120fe81cdbb3c32b499451c languageName: node linkType: hard @@ -48528,21 +49038,59 @@ __metadata: languageName: node linkType: hard -"webpack-sources@npm:^3.2.3": - version: 3.2.3 - resolution: "webpack-sources@npm:3.2.3" - checksum: 10/a661f41795d678b7526ae8a88cd1b3d8ce71a7d19b6503da8149b2e667fc7a12f9b899041c1665d39e38245ed3a59ab68de648ea31040c3829aa695a5a45211d +"webpack-sources@npm:^3.2.3, webpack-sources@npm:^3.3.3": + version: 3.3.3 + resolution: "webpack-sources@npm:3.3.3" + checksum: 10/ec5d72607e8068467370abccbfff855c596c098baedbe9d198a557ccf198e8546a322836a6f74241492576adba06100286592993a62b63196832cdb53c8bae91 languageName: node linkType: hard -"webpack-virtual-modules@npm:^0.6.0, webpack-virtual-modules@npm:^0.6.2": +"webpack-virtual-modules@npm:^0.6.2": version: 0.6.2 resolution: "webpack-virtual-modules@npm:0.6.2" checksum: 10/d9a0d035f7ec0c7f1055aaf88bfe48b7f96458043916a1b2926d9012fd61de3810a6b768e31a8cd4b3c84a9b6d55824361a9dd20aaf9f5ccfb6f017af216a178 languageName: node linkType: hard -"webpack@npm:5, webpack@npm:^5, webpack@npm:^5.94.0": +"webpack@npm:^5": + version: 5.101.1 + resolution: "webpack@npm:5.101.1" + dependencies: + "@types/eslint-scope": "npm:^3.7.7" + "@types/estree": "npm:^1.0.8" + "@types/json-schema": "npm:^7.0.15" + "@webassemblyjs/ast": "npm:^1.14.1" + "@webassemblyjs/wasm-edit": "npm:^1.14.1" + "@webassemblyjs/wasm-parser": "npm:^1.14.1" + acorn: "npm:^8.15.0" + acorn-import-phases: "npm:^1.0.3" + browserslist: "npm:^4.24.0" + chrome-trace-event: "npm:^1.0.2" + enhanced-resolve: "npm:^5.17.3" + es-module-lexer: "npm:^1.2.1" + eslint-scope: "npm:5.1.1" + events: "npm:^3.2.0" + glob-to-regexp: "npm:^0.4.1" + graceful-fs: "npm:^4.2.11" + json-parse-even-better-errors: "npm:^2.3.1" + loader-runner: "npm:^4.2.0" + mime-types: "npm:^2.1.27" + neo-async: "npm:^2.6.2" + schema-utils: "npm:^4.3.2" + tapable: "npm:^2.1.1" + terser-webpack-plugin: "npm:^5.3.11" + watchpack: "npm:^2.4.1" + webpack-sources: "npm:^3.3.3" + peerDependenciesMeta: + webpack-cli: + optional: true + bin: + webpack: bin/webpack.js + checksum: 10/2cbb76683a959bf7f1c11a593e45bb6a2522254eff78e58106da155d6b08da49a3aca584d57948b473a7a55178adcbd728e98cd572ab535d72694b327896a595 + languageName: node + linkType: hard + +"webpack@npm:~5.96.0": version: 5.96.1 resolution: "webpack@npm:5.96.1" dependencies: @@ -48715,16 +49263,17 @@ __metadata: linkType: hard "which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18, which-typed-array@npm:^1.1.2": - version: 1.1.18 - resolution: "which-typed-array@npm:1.1.18" + version: 1.1.19 + resolution: "which-typed-array@npm:1.1.19" dependencies: available-typed-arrays: "npm:^1.0.7" call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - for-each: "npm:^0.3.3" + call-bound: "npm:^1.0.4" + for-each: "npm:^0.3.5" + get-proto: "npm:^1.0.1" gopd: "npm:^1.2.0" has-tostringtag: "npm:^1.0.2" - checksum: 10/11eed801b2bd08cdbaecb17aff381e0fb03526532f61acc06e6c7b9370e08062c33763a51f27825f13fdf34aabd0df6104007f4e8f96e6eaef7db0ce17a26d6e + checksum: 10/12be30fb88567f9863186bee1777f11bea09dd59ed8b3ce4afa7dd5cade75e2f4cc56191a2da165113cc7cf79987ba021dac1e22b5b62aa7e5c56949f2469a68 languageName: node linkType: hard @@ -48917,9 +49466,9 @@ __metadata: languageName: node linkType: hard -"ws@npm:*, ws@npm:^8.11.0, ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.17.1, ws@npm:^8.18.0, ws@npm:^8.2.3, ws@npm:^8.8.0": - version: 8.18.1 - resolution: "ws@npm:8.18.1" +"ws@npm:*, ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.17.1, ws@npm:^8.18.0, ws@npm:^8.2.3, ws@npm:^8.8.0": + version: 8.18.3 + resolution: "ws@npm:8.18.3" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -48928,7 +49477,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10/3f38e9594f2af5b6324138e86b74df7d77bbb8e310bf8188679dd80bac0d1f47e51536a1923ac3365f31f3d8b25ea0b03e4ade466aa8292a86cd5defca64b19b + checksum: 10/725964438d752f0ab0de582cd48d6eeada58d1511c3f613485b5598a83680bedac6187c765b0fe082e2d8cc4341fc57707c813ae780feee82d0c5efe6a4c61b6 languageName: node linkType: hard @@ -49020,31 +49569,6 @@ __metadata: languageName: node linkType: hard -"xterm-addon-attach@npm:^0.9.0": - version: 0.9.0 - resolution: "xterm-addon-attach@npm:0.9.0" - peerDependencies: - xterm: ^5.0.0 - checksum: 10/70e5d3ecf139c04fae13c644b79c33858ef1a6e28dfe78f91dad3e34f5a155579029b87e91d1d016575acaf17f74e6c59402bde4bcff03461595bea0870f1ec1 - languageName: node - linkType: hard - -"xterm-addon-fit@npm:^0.8.0": - version: 0.8.0 - resolution: "xterm-addon-fit@npm:0.8.0" - peerDependencies: - xterm: ^5.0.0 - checksum: 10/5af2041b442f7c804eda2e6f62e3b68b5159b0ae6bd96e2aa8d85b26441df57291cbfed653d1196d4af5d9b94bfc39993df8b409a25c35e0d36bdaf6f5cdfe5f - languageName: node - linkType: hard - -"xterm@npm:^5.2.1, xterm@npm:^5.3.0": - version: 5.3.0 - resolution: "xterm@npm:5.3.0" - checksum: 10/3690b6a6d744f1d2932279975bb8e6c786e70c675531045016ecfe0f9b7957e2fc6811b815335f3e0e84b40ffae654f6ee4afe55a5768534744497e62252dd50 - languageName: node - linkType: hard - "y18n@npm:^5.0.5": version: 5.0.8 resolution: "y18n@npm:5.0.8" @@ -49102,12 +49626,12 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.0.0, yaml@npm:^2.0.0-10, yaml@npm:^2.1.1, yaml@npm:^2.2.1, yaml@npm:^2.2.2, yaml@npm:^2.3.2, yaml@npm:^2.3.3, yaml@npm:^2.3.4, yaml@npm:^2.6.1, yaml@npm:^2.7.0": - version: 2.7.1 - resolution: "yaml@npm:2.7.1" +"yaml@npm:^2.0.0, yaml@npm:^2.0.0-10, yaml@npm:^2.1.1, yaml@npm:^2.2.1, yaml@npm:^2.2.2, yaml@npm:^2.3.2, yaml@npm:^2.3.3, yaml@npm:^2.3.4, yaml@npm:^2.7.0, yaml@npm:^2.8.0": + version: 2.8.1 + resolution: "yaml@npm:2.8.1" bin: yaml: bin.mjs - checksum: 10/af57658d37c5efae4bac7204589b742ae01878a278554d632f01012868cf7fa66cba09b39140f12e7f6ceecc693ae52bcfb737596c4827e6e233338cb3a9528e + checksum: 10/eae07b3947d405012672ec17ce27348aea7d1fa0534143355d24a43a58f5e05652157ea2182c4fe0604f0540be71f99f1173f9d61018379404507790dff17665 languageName: node linkType: hard @@ -49164,8 +49688,9 @@ __metadata: "@backstage/cli-common": "workspace:^" "@backstage/release-manifests": "workspace:^" "@yarnpkg/builder": "npm:^4.2.1" - "@yarnpkg/core": "npm:^4.4.0" + "@yarnpkg/core": "npm:^4.4.1" "@yarnpkg/fslib": "npm:^3.1.2" + "@yarnpkg/plugin-essentials": "npm:^4.4.0" "@yarnpkg/plugin-npm": "patch:@yarnpkg/plugin-npm@npm%3A3.1.0#~/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch" "@yarnpkg/plugin-pack": "npm:^4.0.1" fs-extra: "npm:^11.2.0" @@ -49312,12 +49837,12 @@ __metadata: languageName: node linkType: hard -"zod-to-json-schema@npm:^3.20.4, zod-to-json-schema@npm:^3.21.4": - version: 3.23.5 - resolution: "zod-to-json-schema@npm:3.23.5" +"zod-to-json-schema@npm:^3.20.4, zod-to-json-schema@npm:^3.21.4, zod-to-json-schema@npm:^3.24.1": + version: 3.24.6 + resolution: "zod-to-json-schema@npm:3.24.6" peerDependencies: - zod: ^3.23.3 - checksum: 10/53d07a419f0f194e0b96711dc11e7e6fa52a366b0ed5fceb405dc55f13252a1bf433712e4fb496c2a5fdc851018ee1acba7b39c2265c43d6fbb180e12c110c3b + zod: ^3.24.1 + checksum: 10/a2c30cf1f250aa79a7f975e65b4236d1abafafd63b43c43475057f28ce6e13f4c882391553c656fb426fd09665e6ae293c2439b4ed8600863beda43fb1a56922 languageName: node linkType: hard @@ -49330,10 +49855,10 @@ __metadata: languageName: node linkType: hard -"zod@npm:^3.22.4": - version: 3.23.8 - resolution: "zod@npm:3.23.8" - checksum: 10/846fd73e1af0def79c19d510ea9e4a795544a67d5b34b7e1c4d0425bf6bfd1c719446d94cdfa1721c1987d891321d61f779e8236fde517dc0e524aa851a6eff1 +"zod@npm:^3.22.4, zod@npm:^3.23.8": + version: 3.25.76 + resolution: "zod@npm:3.25.76" + checksum: 10/f0c963ec40cd96858451d1690404d603d36507c1fc9682f2dae59ab38b578687d542708a7fdbf645f77926f78c9ed558f57c3d3aa226c285f798df0c4da16995 languageName: node linkType: hard