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/bitter-pants-watch.md b/.changeset/bitter-pants-watch.md new file mode 100644 index 0000000000..3accf45826 --- /dev/null +++ b/.changeset/bitter-pants-watch.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': minor +--- + +We are updating the default size of the Button component in Canon to be small instead of medium. diff --git a/.changeset/brave-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-facts-pay.md b/.changeset/brave-facts-pay.md new file mode 100644 index 0000000000..1f8e9663bf --- /dev/null +++ b/.changeset/brave-facts-pay.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch +--- + +Migrate actions to new format diff --git a/.changeset/brave-pandas-beam.md b/.changeset/brave-pandas-beam.md deleted file mode 100644 index 8dd525d749..0000000000 --- a/.changeset/brave-pandas-beam.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -'@backstage/core-app-api': minor ---- - -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. 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-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-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-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-clowns-dream.md b/.changeset/chatty-clowns-dream.md new file mode 100644 index 0000000000..d601f7a40d --- /dev/null +++ b/.changeset/chatty-clowns-dream.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +table actions header support i18n diff --git a/.changeset/chatty-months-grow.md b/.changeset/chatty-months-grow.md deleted file mode 100644 index 1495e7732e..0000000000 --- a/.changeset/chatty-months-grow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/canon': patch ---- - -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. 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/clean-otters-allow.md b/.changeset/clean-otters-allow.md deleted file mode 100644 index 5e0aeb598f..0000000000 --- a/.changeset/clean-otters-allow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -fix: enable `lazyCompilation` and `refreshOptions` for rspack 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-meals-like.md b/.changeset/cold-meals-like.md new file mode 100644 index 0000000000..7feb978337 --- /dev/null +++ b/.changeset/cold-meals-like.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-client': patch +--- + +Fixed `CatalogClient` error responses for `refreshEntity` and `addLocation`. diff --git a/.changeset/common-goats-exist.md b/.changeset/common-goats-exist.md new file mode 100644 index 0000000000..1992b69223 --- /dev/null +++ b/.changeset/common-goats-exist.md @@ -0,0 +1,15 @@ +--- +'@backstage/plugin-notifications-backend': patch +--- + +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. 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-groups-fail.md b/.changeset/cool-groups-fail.md deleted file mode 100644 index 459358676b..0000000000 --- a/.changeset/cool-groups-fail.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -`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`. 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/create-app-1745325336.md b/.changeset/create-app-1748956288.md similarity index 100% rename from .changeset/create-app-1745325336.md rename to .changeset/create-app-1748956288.md diff --git a/.changeset/create-app-1745936753.md b/.changeset/create-app-1749565617.md similarity index 100% rename from .changeset/create-app-1745936753.md rename to .changeset/create-app-1749565617.md diff --git a/.changeset/create-app-1747144256.md b/.changeset/create-app-1749636320.md similarity index 100% rename from .changeset/create-app-1747144256.md rename to .changeset/create-app-1749636320.md 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/cuddly-camels-lose.md b/.changeset/cuddly-camels-lose.md new file mode 100644 index 0000000000..23162bfdc5 --- /dev/null +++ b/.changeset/cuddly-camels-lose.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Migrate to new action format diff --git a/.changeset/curvy-peaches-relate.md b/.changeset/curvy-peaches-relate.md new file mode 100644 index 0000000000..9fafc77abb --- /dev/null +++ b/.changeset/curvy-peaches-relate.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-azure': patch +--- + +Migrating `azure` actions to using the new `zod` schema format diff --git a/.changeset/curvy-words-create.md b/.changeset/curvy-words-create.md new file mode 100644 index 0000000000..b180705ebe --- /dev/null +++ b/.changeset/curvy-words-create.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': minor +--- + +Internal update to promote the modular CLI entrypoint to stable. 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-aliens-camp.md b/.changeset/deep-aliens-camp.md new file mode 100644 index 0000000000..ca5c3d8ed4 --- /dev/null +++ b/.changeset/deep-aliens-camp.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-events-backend-module-kafka': minor +--- + +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. diff --git a/.changeset/deep-moments-love.md b/.changeset/deep-moments-love.md new file mode 100644 index 0000000000..89b3174275 --- /dev/null +++ b/.changeset/deep-moments-love.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Add min-width: 0; by default on every Flex components in Canon to help support truncated texts inside flex elements. 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/dry-shirts-film.md b/.changeset/dry-shirts-film.md deleted file mode 100644 index 35ada3969a..0000000000 --- a/.changeset/dry-shirts-film.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-home': patch ---- - -Added the Catalog presentation API to the HomePageRecentlyVisited and HomePageTopVisited components diff --git a/.changeset/dull-buttons-press.md b/.changeset/dull-buttons-press.md new file mode 100644 index 0000000000..1c8f8fc13c --- /dev/null +++ b/.changeset/dull-buttons-press.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +--- + +Migrate `bitbucket-cloud` to new actions format diff --git a/.changeset/common-goats-raise.md b/.changeset/dull-cars-shout.md similarity index 61% rename from .changeset/common-goats-raise.md rename to .changeset/dull-cars-shout.md index c2d797b719..be213a167c 100644 --- a/.changeset/common-goats-raise.md +++ b/.changeset/dull-cars-shout.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend-module-github': patch --- -Use action context logger in Octokit client +Migrate to using new actions diff --git a/.changeset/dull-cloths-act.md b/.changeset/dull-cloths-act.md new file mode 100644 index 0000000000..e49ee5a289 --- /dev/null +++ b/.changeset/dull-cloths-act.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-react': minor +'@backstage/plugin-scaffolder': minor +--- + +Added information about the `entityRef` and `taskId` to the analytics events whenever is possible. 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-pigs-post.md b/.changeset/eight-pigs-post.md new file mode 100644 index 0000000000..8e50cc7707 --- /dev/null +++ b/.changeset/eight-pigs-post.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': minor +--- + +We set the default size for IconButton in Canon to be small instead of medium. diff --git a/.changeset/eight-planets-see.md b/.changeset/eight-planets-see.md new file mode 100644 index 0000000000..20adcac7ef --- /dev/null +++ b/.changeset/eight-planets-see.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +render details for composite property schemas diff --git a/.changeset/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/eighty-showers-hide.md b/.changeset/eighty-showers-hide.md new file mode 100644 index 0000000000..0871fea4bb --- /dev/null +++ b/.changeset/eighty-showers-hide.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-scaffolder-node-test-utils': minor +--- + +**BREAKING CHANGES** + +Because of the removal of the `logStream` property to the `ActionsContext` this has been removed from the `createMockActionContext` method. + +You can remove this as it's no longer supported in the scaffolder actions. diff --git a/.changeset/eleven-beans-relax.md b/.changeset/eleven-beans-relax.md deleted file mode 100644 index fd01394d2d..0000000000 --- a/.changeset/eleven-beans-relax.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/release-manifests': patch ---- - -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). diff --git a/.changeset/eleven-foxes-mix.md b/.changeset/eleven-foxes-mix.md new file mode 100644 index 0000000000..965be5435e --- /dev/null +++ b/.changeset/eleven-foxes-mix.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': minor +--- + +Enhanced error handling in the auditor service factory to pass errors as objects. Aligned WinstonRootAuditorService with the default service factory's error handling. 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/every-bottles-grow.md b/.changeset/every-bottles-grow.md deleted file mode 100644 index a61a7b42b9..0000000000 --- a/.changeset/every-bottles-grow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -"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." diff --git a/.changeset/fruity-bags-flow.md b/.changeset/evil-cooks-watch.md similarity index 81% rename from .changeset/fruity-bags-flow.md rename to .changeset/evil-cooks-watch.md index bc6034a489..0982f24634 100644 --- a/.changeset/fruity-bags-flow.md +++ b/.changeset/evil-cooks-watch.md @@ -1,7 +1,5 @@ --- '@backstage/plugin-catalog-unprocessed-entities': patch -'@backstage/frontend-test-utils': patch -'@backstage/core-compat-api': patch '@backstage/plugin-app-visualizer': patch '@backstage/plugin-catalog-import': patch '@backstage/plugin-catalog-graph': patch @@ -20,4 +18,4 @@ '@backstage/plugin-org': patch --- -Internal update to use the new `pluginId` option of `createFrontendPlugin`. +Added the `info.packageJson` option to the plugin instance for the new frontend system. diff --git a/.changeset/fair-doors-bathe.md b/.changeset/fair-doors-bathe.md new file mode 100644 index 0000000000..bbb5f238ee --- /dev/null +++ b/.changeset/fair-doors-bathe.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Fixed a bug where linting would fail with the generated clients when defining top-level `enum` schema values. diff --git a/.changeset/famous-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/famous-streets-stay.md b/.changeset/famous-streets-stay.md new file mode 100644 index 0000000000..d517b889c5 --- /dev/null +++ b/.changeset/famous-streets-stay.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-node': patch +--- + +Use `LoggerService` instead of `Logger`. This is a non-breaking change, as the `LoggerService` is a subset of the `Logger` interface. diff --git a/.changeset/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/few-streets-accept.md b/.changeset/few-streets-accept.md new file mode 100644 index 0000000000..4b82cc03e4 --- /dev/null +++ b/.changeset/few-streets-accept.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch +--- + +Migrate the actions to the new format diff --git a/.changeset/fifty-sides-begin.md b/.changeset/fifty-sides-begin.md new file mode 100644 index 0000000000..5cd174e4ff --- /dev/null +++ b/.changeset/fifty-sides-begin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-bitbucket-server': minor +--- + +Add validateLocationsExist option to avoid generating locations for catalog-info.yaml files that do not exist in the source repository. diff --git a/.changeset/fine-spoons-laugh.md b/.changeset/fine-spoons-laugh.md new file mode 100644 index 0000000000..a27ef7e7d5 --- /dev/null +++ b/.changeset/fine-spoons-laugh.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-kubernetes-react': patch +'@backstage/plugin-kubernetes': patch +--- + +chore(deps): Update deprecated xterm to new package under @xterm diff --git a/.changeset/fix-cli-config-docs-feedback.md b/.changeset/fix-cli-config-docs-feedback.md new file mode 100644 index 0000000000..1e7298bafa --- /dev/null +++ b/.changeset/fix-cli-config-docs-feedback.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added user feedback when opening config docs in browser. The command now clearly indicates what it's doing and provides fallback instructions if the browser fails to open. diff --git a/.changeset/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/fluffy-bobcats-tell.md b/.changeset/fluffy-bobcats-tell.md new file mode 100644 index 0000000000..41ed20a7ae --- /dev/null +++ b/.changeset/fluffy-bobcats-tell.md @@ -0,0 +1,5 @@ +--- +'@backstage/eslint-plugin': patch +--- + +Fix custom rules package scanning performance. diff --git a/.changeset/forty-seas-worry.md b/.changeset/forty-seas-worry.md new file mode 100644 index 0000000000..a88c15df53 --- /dev/null +++ b/.changeset/forty-seas-worry.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Fix styling for the title4 prop on the Heading component in Canon. 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-snails-argue.md b/.changeset/four-snails-argue.md deleted file mode 100644 index 905e52a559..0000000000 --- a/.changeset/four-snails-argue.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -'@backstage/integration': minor ---- - -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 -``` diff --git a/.changeset/fresh-brooms-follow.md b/.changeset/fresh-brooms-follow.md new file mode 100644 index 0000000000..f2858779bf --- /dev/null +++ b/.changeset/fresh-brooms-follow.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +The `BACKSTAGE_CLI_EXPERIMENTAL_BUILD_CACHE` flag has been removed. Existing users are encouraged to switch to `EXPERIMENTAL_RSPACK` instead. diff --git a/.changeset/fresh-otters-say.md b/.changeset/fresh-otters-say.md new file mode 100644 index 0000000000..ed91c9fb8d --- /dev/null +++ b/.changeset/fresh-otters-say.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Migrating to latest action format diff --git a/.changeset/funny-papayas-tell.md b/.changeset/funny-papayas-tell.md deleted file mode 100644 index 98beaf89e0..0000000000 --- a/.changeset/funny-papayas-tell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': minor ---- - -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. 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/good-paths-act.md b/.changeset/good-paths-act.md new file mode 100644 index 0000000000..dc33b43766 --- /dev/null +++ b/.changeset/good-paths-act.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Fix an issue where errors were not printed to console when running `backstage-repo-tools schema openapi generate` without the `--watch` flag. diff --git a/.changeset/green-schools-live.md b/.changeset/green-schools-live.md new file mode 100644 index 0000000000..9863021a28 --- /dev/null +++ b/.changeset/green-schools-live.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-yeoman': patch +--- + +Migrate to new actions format diff --git a/.changeset/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/grumpy-dryers-act.md b/.changeset/grumpy-dryers-act.md new file mode 100644 index 0000000000..caaeac5fa8 --- /dev/null +++ b/.changeset/grumpy-dryers-act.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Add missing highlight language for the `package-docs` command. diff --git a/.changeset/grumpy-phones-kiss.md b/.changeset/grumpy-phones-kiss.md deleted file mode 100644 index 96b6ff05d9..0000000000 --- a/.changeset/grumpy-phones-kiss.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-backend-module-elasticsearch': patch ---- - -Fixed an issue where the `search.elasticsearch.queryOptions` config were not picked up by the `ElasticSearchSearchEngine`. 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-moles-bet.md b/.changeset/honest-moles-bet.md new file mode 100644 index 0000000000..22e0cb4533 --- /dev/null +++ b/.changeset/honest-moles-bet.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +'@backstage/plugin-scaffolder-node': patch +--- + +Update pull request creation filter to include .gitignore files in the created pull request diff --git a/.changeset/honest-states-repeat.md b/.changeset/honest-states-repeat.md deleted file mode 100644 index 06b459b673..0000000000 --- a/.changeset/honest-states-repeat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-home': patch ---- - -Added optional title prop to `customHomePageGrid` diff --git a/.changeset/honest-teams-shave.md b/.changeset/honest-teams-shave.md deleted file mode 100644 index 6afbf37039..0000000000 --- a/.changeset/honest-teams-shave.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@backstage/plugin-home-react': patch -'@backstage/plugin-home': patch ---- - -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`. 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/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-mirrors-juggle.md b/.changeset/itchy-mirrors-juggle.md new file mode 100644 index 0000000000..6fd7c7543a --- /dev/null +++ b/.changeset/itchy-mirrors-juggle.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-notifications': patch +'@backstage/plugin-scaffolder': patch +--- + +Remove React import form notification and scaffolder plugin 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/khaki-horses-chew.md b/.changeset/khaki-horses-chew.md new file mode 100644 index 0000000000..b9f5dfda5e --- /dev/null +++ b/.changeset/khaki-horses-chew.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-techdocs-module-addons-contrib': patch +'@backstage/plugin-techdocs': patch +--- + +Improved Keyboard accessibility in techdocs. diff --git a/.changeset/khaki-swans-invite.md b/.changeset/khaki-swans-invite.md new file mode 100644 index 0000000000..4e768a1978 --- /dev/null +++ b/.changeset/khaki-swans-invite.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-notifications-backend-module-email': patch +'@backstage/plugin-notifications-backend': patch +--- + +Internal changes to switch to the non-alpha `catalogServiceRef` diff --git a/.changeset/kind-houses-feel.md b/.changeset/kind-houses-feel.md new file mode 100644 index 0000000000..a4b3ac0d14 --- /dev/null +++ b/.changeset/kind-houses-feel.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Added a render prop to the Button component in Canon to use it as a link. diff --git a/.changeset/large-baboons-prove.md b/.changeset/large-baboons-prove.md new file mode 100644 index 0000000000..072d57de4c --- /dev/null +++ b/.changeset/large-baboons-prove.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-catalog': minor +--- + +Add support to customize the about card icon links via `EntityIconLinkBlueprint` and provide a default catalog view catalog source, launch scaffolder template and read techdocs docs icon links extensions. + +**BREAKING ALPHA** + +The `Scaffolder` launch template and `TechDocs` read documentation icons have been extracted from the default `Catalog` about card links and are now provided respectively by the `Scaffolder` and `TechDocs` plugins in the new frontend system. It means that they will not be available unless you install the `TechDocs` and `Scaffolder` plugins. Also If you are using translation for these icon link titles other than the default, you should now translate them using the scaffolder translation reference or the TechDocs translation reference (the translation keys are still the same, `aboutCard.viewTechdocs` and `aboutCard.launchTemplate`). diff --git a/.changeset/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-buttons-strive.md b/.changeset/late-buttons-strive.md new file mode 100644 index 0000000000..23e1045ba0 --- /dev/null +++ b/.changeset/late-buttons-strive.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +Org plugin support i18n diff --git a/.changeset/lazy-rice-dream.md b/.changeset/lazy-rice-dream.md new file mode 100644 index 0000000000..fe91f85b2e --- /dev/null +++ b/.changeset/lazy-rice-dream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gerrit': patch +--- + +Migrating to use new format for actions diff --git a/.changeset/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/long-candies-greet.md b/.changeset/long-candies-greet.md new file mode 100644 index 0000000000..a59377d784 --- /dev/null +++ b/.changeset/long-candies-greet.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-devtools-backend': patch +--- + +Removed the usage of `permissionIntegrationRouter` in favor of using the new `coreServices.permissionsRegistry` diff --git a/.changeset/stale-symbols-joke.md b/.changeset/long-suits-obey.md similarity index 53% rename from .changeset/stale-symbols-joke.md rename to .changeset/long-suits-obey.md index ea4f172c1c..996bba6026 100644 --- a/.changeset/stale-symbols-joke.md +++ b/.changeset/long-suits-obey.md @@ -2,4 +2,4 @@ '@backstage/plugin-user-settings': patch --- -Uppercase language name in language select +Add some translation to user-setting plugin diff --git a/.changeset/loose-ants-learn.md b/.changeset/loose-ants-learn.md new file mode 100644 index 0000000000..29f357966c --- /dev/null +++ b/.changeset/loose-ants-learn.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-techdocs': minor +--- + +**New Frontend System Only:** +The `TechDocs` plugin is now responsible for providing an entity icon link extension to read documentation from the catalog entity page. diff --git a/.changeset/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/major-heads-scream.md b/.changeset/major-heads-scream.md new file mode 100644 index 0000000000..24caa6e4bb --- /dev/null +++ b/.changeset/major-heads-scream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': minor +--- + +**BREAKING CHANGE**: User and Group discovery will default to ingesting all users in sub groups that belong to the specified root group in config. Disable by setting `restrictUsersToGroup: true` in app-config under your module settings. diff --git a/.changeset/mean-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/mean-tigers-rush.md b/.changeset/mean-tigers-rush.md new file mode 100644 index 0000000000..e15b01cedc --- /dev/null +++ b/.changeset/mean-tigers-rush.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Switched to using the `ModuleFederationPlugin` from `@module-federation/enhanced/rspack` for Rspack, rather than the built-in one. diff --git a/.changeset/mighty-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-windows-boil.md b/.changeset/mighty-windows-boil.md new file mode 100644 index 0000000000..52eb024db1 --- /dev/null +++ b/.changeset/mighty-windows-boil.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-sentry': patch +--- + +Migrate to new actions format diff --git a/.changeset/mira-flygande-spoons.md b/.changeset/mira-flygande-spoons.md new file mode 100644 index 0000000000..c4c50f117a --- /dev/null +++ b/.changeset/mira-flygande-spoons.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +You can now specify an optional value when applying the `HAS_LABEL` permission rule, similar to the `HAS_ANNOTATION` permission rule. diff --git a/.changeset/modern-buses-change.md b/.changeset/modern-buses-change.md new file mode 100644 index 0000000000..b89efab093 --- /dev/null +++ b/.changeset/modern-buses-change.md @@ -0,0 +1,13 @@ +--- +'@backstage/plugin-auth-backend-module-aws-alb-provider': patch +'@backstage/backend-dynamic-feature-service': patch +'@backstage/core-plugin-api': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-events-backend': patch +'@backstage/plugin-search-backend': patch +'@backstage/plugin-user-settings': patch +'@backstage/repo-tools': patch +'@backstage/plugin-home-react': patch +--- + +Fixed some circular or otherwise unclear imports diff --git a/.changeset/moody-women-itch.md b/.changeset/moody-women-itch.md deleted file mode 100644 index f68fddb848..0000000000 --- a/.changeset/moody-women-itch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend-module-email': patch ---- - -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. 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/new-beers-sell.md b/.changeset/new-beers-sell.md new file mode 100644 index 0000000000..fd7407007d --- /dev/null +++ b/.changeset/new-beers-sell.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-scaffolder-backend-module-notifications': patch +'@backstage/plugin-notifications-backend': patch +'@backstage/plugin-notifications-common': patch +'@backstage/plugin-notifications': patch +--- + +Adds ability for user to turn on/off notifications for specific topics within an origin. diff --git a/.changeset/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-ties-film.md b/.changeset/nice-ties-film.md new file mode 100644 index 0000000000..2d7be04222 --- /dev/null +++ b/.changeset/nice-ties-film.md @@ -0,0 +1,9 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-app-api': patch +'@backstage/core-plugin-api': patch +'@backstage/plugin-catalog-node': patch +'@backstage/plugin-auth-node': patch +--- + +Internal tweak to avoid circular dependencies 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/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={``; + +export const selectDefaultSnippet = ``; + +export const selectSizesSnippet = ` + +`; + +export const selectDisabledSnippet = ``; diff --git a/canon-docs/src/content/components/switch.mdx b/canon-docs/src/content/components/switch.mdx new file mode 100644 index 0000000000..068ec96217 --- /dev/null +++ b/canon-docs/src/content/components/switch.mdx @@ -0,0 +1,41 @@ +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 { ComponentInfos } from '@/components/ComponentInfos'; + +# Switch + +A control that indicates whether a setting is on or off. + +} + code={``} +/> + + + +## API reference + + + +## Examples + +### Disabled + +A switch can be disabled using the `isDisabled` prop. + +} + code={``} +/> diff --git a/canon-docs/src/content/components/switch.props.ts b/canon-docs/src/content/components/switch.props.ts new file mode 100644 index 0000000000..ddbca4e049 --- /dev/null +++ b/canon-docs/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/canon'; + +`; diff --git a/canon-docs/src/app/(docs)/components/table/page.mdx b/canon-docs/src/content/components/table.mdx similarity index 100% rename from canon-docs/src/app/(docs)/components/table/page.mdx rename to canon-docs/src/content/components/table.mdx diff --git a/canon-docs/src/content/components/tabs.mdx b/canon-docs/src/content/components/tabs.mdx new file mode 100644 index 0000000000..85cfa34af2 --- /dev/null +++ b/canon-docs/src/content/components/tabs.mdx @@ -0,0 +1,61 @@ +import { PropsTable } from '@/components/PropsTable'; +import { TabsSnippet } from '@/snippets/stories-snippets'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { + tabsRootPropDefs, + tabsListPropDefs, + tabsTabPropDefs, + tabsPanelPropDefs, + tabsUsageSnippet, + tabsDefaultSnippet, +} from './tabs.props'; +import { ComponentInfos } from '@/components/ComponentInfos'; + +# Tabs + +A component for toggling between related panels on the same page. + +} + code={tabsDefaultSnippet} +/> + + + +## API reference + +### Tabs.Root + +Groups the tabs and the corresponding panels. Renders a `
` element. + + + +### Tabs.List + +Groups the individual tab buttons. Renders a `
` element. + + + +### Tabs.Tab + +An individual interactive tab button that toggles the corresponding panel. Renders a ` - - - -`; - -export const iconButtonVariants = ` - - - -`; - -export const flexFAQ1 = ` - Hello World - Hello World - Hello World -`; - -export const flexSimple = ` - Hello World - Hello World - Hello World -`; - -export const flexResponsive = ` - Hello World - Hello World - Hello World -`; - -export const flexAlign = ` - Hello World - Hello World - Hello World -`; diff --git a/canon-docs/src/snippets/stories-snippets.tsx b/canon-docs/src/snippets/stories-snippets.tsx index 75ee2e31c3..95738a6330 100644 --- a/canon-docs/src/snippets/stories-snippets.tsx +++ b/canon-docs/src/snippets/stories-snippets.tsx @@ -17,6 +17,8 @@ import * as MenuStories from '../../../packages/canon/src/components/Menu/Menu.s import * as LinkStories from '../../../packages/canon/src/components/Link/Link.stories'; import * as AvatarStories from '../../../packages/canon/src/components/Avatar/Avatar.stories'; import * as CollapsibleStories from '../../../packages/canon/src/components/Collapsible/Collapsible.stories'; +import * as TabsStories from '../../../packages/canon/src/components/Tabs/Tabs.stories'; +import * as SwitchStories from '../../../packages/canon/src/components/Switch/Switch.stories'; export const BoxSnippet = ({ story }: { story: keyof typeof BoxStories }) => { const stories = composeStories(BoxStories); @@ -165,3 +167,21 @@ export const CollapsibleSnippet = ({ return StoryComponent ? : null; }; + +export const TabsSnippet = ({ story }: { story: keyof typeof TabsStories }) => { + const stories = composeStories(TabsStories); + const StoryComponent = stories[story as keyof typeof stories]; + + return StoryComponent ? : null; +}; + +export const SwitchSnippet = ({ + story, +}: { + story: keyof typeof SwitchStories; +}) => { + const stories = composeStories(SwitchStories); + const StoryComponent = stories[story as keyof typeof stories]; + + return StoryComponent ? : null; +}; diff --git a/canon-docs/src/utils/changelog.ts b/canon-docs/src/utils/changelog.ts new file mode 100644 index 0000000000..94317ab78b --- /dev/null +++ b/canon-docs/src/utils/changelog.ts @@ -0,0 +1,386 @@ +export type Component = + | 'avatar' + | 'box' + | 'button' + | 'heading' + | 'text' + | 'icon-button' + | 'icon' + | 'tabs' + | 'menu' + | 'textfield' + | 'datatable' + | 'select' + | 'collapsible' + | 'checkbox' + | 'container' + | 'link' + | 'tooltip' + | 'scrollarea' + | 'flex' + | 'switch'; + +export type Version = `${number}.${number}.${number}`; + +export interface ChangelogProps { + components: Component[]; + description: string; + version: Version; + platform: 'oss' | 'portal'; + prs: string[]; +} + +export const changelog: ChangelogProps[] = [ + { + components: ['switch'], + version: '0.5.0', + description: '✨ Add new `Switch` component', + platform: 'portal', + prs: ['30251'], + }, + { + components: ['tabs'], + version: '0.4.0', + description: '✨ Add new `Tabs` component', + platform: 'portal', + prs: ['29996'], + }, + { + components: ['text', 'heading'], + version: '0.4.0', + description: '✨ Add `truncate` prop to `Text` and `Heading`', + platform: 'portal', + prs: ['29988'], + }, + { + components: ['menu'], + version: '0.4.0', + description: '✨ Add combobox option to `Menu`', + platform: 'portal', + prs: ['29986'], + }, + { + components: ['textfield'], + version: '0.4.0', + description: '✨ Add icon prop on `TextField`', + platform: 'portal', + prs: ['29820'], + }, + { + components: ['button', 'icon-button'], + version: '0.4.0', + description: 'Improve icon props on `Button` and `IconButton`', + platform: 'portal', + prs: ['29667'], + }, + { + components: ['text', 'heading'], + version: '0.4.0', + description: + 'Improve the way we treat custom render on `Text` and `Heading`', + platform: 'portal', + prs: ['29989'], + }, + { + components: ['menu'], + version: '0.4.0', + description: 'Improve `Menu` styles', + platform: 'portal', + prs: ['29986'], + }, + { + components: ['textfield'], + version: '0.4.0', + description: 'Improve `TextField` styles', + platform: 'portal', + prs: ['29974'], + }, + { + components: ['textfield'], + version: '0.4.0', + description: 'Improve clear button on `TextField`', + platform: 'portal', + prs: ['29878'], + }, + { + components: ['flex'], + version: '0.4.0', + description: 'Fix spacing props on all layout components', + platform: 'portal', + prs: ['30013'], + }, + { + components: [], + version: '0.4.0', + description: 'Fix - Pin Base UI version', + platform: 'portal', + prs: ['29782'], + }, + { + components: ['select'], + version: '0.4.0', + description: 'Fix - Clicking `Select` label moves focus to trigger', + platform: 'portal', + prs: ['29755'], + }, + { + components: ['datatable'], + version: '0.4.0', + description: 'Fix `DataTable.Pagination` count issue', + platform: 'portal', + prs: ['29688'], + }, + { + components: ['datatable'], + version: '0.3.0', + description: 'Add `DataTable` component', + platform: 'portal', + prs: ['29484', '29603'], + }, + { + components: ['select'], + version: '0.3.0', + description: 'Add `Select` component', + platform: 'portal', + prs: ['29440'], + }, + { + components: ['avatar'], + version: '0.3.0', + description: 'Add `Avatar` component', + platform: 'portal', + prs: ['29594'], + }, + { + components: ['collapsible'], + version: '0.3.0', + description: 'Add `Collapsible` component', + platform: 'portal', + prs: ['29617'], + }, + { + components: ['textfield'], + version: '0.3.0', + description: 'Add `TextField` component instead of `Field` + `Input`', + platform: 'portal', + prs: ['29364'], + }, + { + components: ['datatable'], + version: '0.3.0', + description: 'Add `TableCellProfile`', + platform: 'portal', + prs: ['29600'], + }, + { + components: [], + version: '0.3.0', + description: 'Add breakpoint hooks - `up()` and `down()`', + platform: 'portal', + prs: ['29564'], + }, + { + components: [], + version: '0.3.0', + description: 'Add gray scale css tokens', + platform: 'portal', + prs: ['29543'], + }, + { + components: [], + version: '0.3.0', + description: + 'Update CSS styling API using `[data-___]` instead of class names for props', + platform: 'portal', + prs: ['29560'], + }, + { + components: ['checkbox'], + version: '0.3.0', + description: 'Update `Checkbox` dark mode', + platform: 'portal', + prs: ['29544'], + }, + { + components: ['container'], + version: '0.3.0', + description: 'Update `Container` styles', + platform: 'portal', + prs: ['29475'], + }, + { + components: ['menu'], + version: '0.3.0', + description: 'Update `Menu` styles', + platform: 'portal', + prs: ['29351'], + }, + { + components: ['select'], + version: '0.3.0', + description: 'Fix `Select` styles on small sizes + with long option names', + platform: 'portal', + prs: ['29545'], + }, + { + components: ['link'], + version: '0.3.0', + description: 'Fix render prop on `Link`', + platform: 'portal', + prs: ['29247'], + }, + { + components: ['textfield', 'select'], + version: '0.3.0', + description: 'Remove `Field` from `TextField` + `Select`', + platform: 'portal', + prs: ['29482'], + }, + { + components: ['text', 'heading'], + version: '0.3.0', + description: 'Update `textDecoration` to `none` on `Text` / `Heading`', + platform: 'portal', + prs: ['29357'], + }, + { + components: ['text', 'heading'], + version: '0.3.0', + description: 'Update `textDecoration` to `none` on `Text` / `Heading`', + platform: 'portal', + prs: ['29357'], + }, + { + components: [], + version: '0.3.0', + description: 'Docs - Use stories from Storybook for all examples in Nextjs', + platform: 'portal', + prs: ['29306'], + }, + { + components: [], + version: '0.3.0', + description: 'Docs - Add release page (this one 🤗)', + platform: 'portal', + prs: ['29461'], + }, + { + components: [], + version: '0.3.0', + description: 'Docs - Add docs for Menu, Link', + platform: 'portal', + prs: ['29576'], + }, + { + components: [], + version: '0.3.0', + description: 'Fix CSS watch mode', + platform: 'portal', + prs: ['29352'], + }, + { + components: ['menu'], + version: '0.2.0', + description: 'New `Menu` component', + platform: 'portal', + prs: ['29151'], + }, + { + components: ['icon-button'], + version: '0.2.0', + description: 'New `IconButton` component', + platform: 'portal', + prs: ['29239'], + }, + { + components: ['scrollarea'], + version: '0.2.0', + description: 'New `ScrollArea` component', + platform: 'portal', + prs: ['29240'], + }, + { + components: ['button', 'checkbox'], + version: '0.2.0', + description: 'Improve `Button` & `Checkbox` styles', + platform: 'portal', + prs: ['29127', '28789'], + }, + { + components: ['text'], + version: '0.2.0', + description: 'Improve `Text` styles', + platform: 'portal', + prs: ['29200'], + }, + { + components: ['icon'], + version: '0.2.0', + description: 'Renamed `CanonProvider` to `IconProvider`', + platform: 'portal', + prs: ['29002'], + }, + { + components: ['icon'], + version: '0.2.0', + description: 'Added about 40+ new icons', + platform: 'portal', + prs: ['29264'], + }, + { + components: [], + version: '0.2.0', + description: 'Simplified styling into a unique styles.css file', + platform: 'portal', + prs: ['29199'], + }, + { + components: [], + version: '0.2.0', + description: 'Added Canon styles to Backstage', + platform: 'portal', + prs: ['29137'], + }, + { + components: [], + version: '0.2.0', + description: 'Update global CSS tokens', + platform: 'portal', + prs: ['28804'], + }, + { + components: ['flex'], + version: '0.2.0', + description: 'Merge Stack + Inline into Flex', + platform: 'portal', + prs: ['28634'], + }, + { + components: ['button'], + version: '0.2.0', + description: 'Improve `Button` types', + platform: 'portal', + prs: ['29205'], + }, + { + components: [], + version: '0.2.0', + description: 'Move font weight and family back to each components', + platform: 'portal', + prs: ['28972'], + }, + { + components: [], + version: '0.2.0', + description: 'Fix custom values in spacing props', + platform: 'portal', + prs: ['28770'], + }, + { + components: [], + version: '0.2.0', + description: 'Multiple updates on the Canon Docs site', + platform: 'portal', + prs: ['28760'], + }, +]; diff --git a/canon-docs/src/utils/data.ts b/canon-docs/src/utils/data.ts index 891b057ad6..20ff9146a7 100644 --- a/canon-docs/src/utils/data.ts +++ b/canon-docs/src/utils/data.ts @@ -14,8 +14,8 @@ export const overview: Page[] = [ slug: 'about', }, { - title: 'Releases', - slug: 'releases', + title: 'Changelog', + slug: 'changelog', }, ]; @@ -116,11 +116,21 @@ export const components: Page[] = [ slug: 'select', status: 'alpha', }, + { + title: 'Switch', + slug: 'switch', + status: 'alpha', + }, { title: 'Table', slug: 'table', status: 'inProgress', }, + { + title: 'Tabs', + slug: 'tabs', + status: 'alpha', + }, { title: 'Text', slug: 'text', diff --git a/canon-docs/tsconfig.json b/canon-docs/tsconfig.json index 2baa7cde8f..e1b3951aa8 100644 --- a/canon-docs/tsconfig.json +++ b/canon-docs/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/canon-docs/yarn.lock index decb4e7efe..9232edb7af 100644 --- a/canon-docs/yarn.lock +++ b/canon-docs/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": + 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 @@ -122,199 +138,228 @@ __metadata: linkType: hard "@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.35.0": - version: 6.36.2 - resolution: "@codemirror/view@npm:6.36.2" + version: 6.37.1 + resolution: "@codemirror/view@npm:6.37.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/04594b0bad98036c57ef0fb962b0e09186de404e26f1068fd98191edae51f2c5b476938d42239c8c7f0b70deead837f925a9d671b7cc8f9f336bdbb01630bbf9 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,17 +419,192 @@ __metadata: languageName: node linkType: hard -"@isaacs/cliui@npm:^8.0.2": - version: 8.0.2 - resolution: "@isaacs/cliui@npm:8.0.2" +"@img/sharp-darwin-arm64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-darwin-arm64@npm:0.34.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 + "@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 @@ -396,13 +616,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 +635,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 +645,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 +677,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 +721,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.3": + version: 15.3.3 + resolution: "@next/env@npm:15.3.3" + checksum: 10/f71fd8b397c60b80ee2ba836152d2c8bc772f4f840b976bfd82ca0c1452539b4832a1ce6702a90da1d4edb874383944ea2662add5c0729423cd4d1cb2e58fc3d + languageName: node + linkType: hard + +"@next/eslint-plugin-next@npm:15.3.3": + version: 15.3.3 + resolution: "@next/eslint-plugin-next@npm:15.3.3" + dependencies: + fast-glob: "npm:3.3.1" + checksum: 10/08b19a39fa01ac69c7b117a29ebdb193353a29cb3d51b2e3f72fabd7f2b014d08e37d819aed8e440a8a0a6e1acd9053dbf939b5f76603e6a87de0042fbf0bd4b + languageName: node + linkType: hard + +"@next/mdx@npm:15.3.3": + version: 15.3.3 + resolution: "@next/mdx@npm:15.3.3" dependencies: source-map: "npm:^0.7.0" peerDependencies: @@ -530,69 +761,62 @@ __metadata: optional: true "@mdx-js/react": optional: true - checksum: 10/3b46e99934543df94bcb335dddab794db4fcaff09223343e9e00597d3df1e094f16dd619c433fb2fa7fff38c3da57b3118cbc010cdeb9790d0cf01e6c37016e8 + checksum: 10/5583f01e986322bee6eae1052fce27964b33796b8fbd2ea360340a65ccf60cd348c614524bcbf8323be178cace1da5c74f45f8a111cf9e62682aad3a0a9d12e3 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.3": + version: 15.3.3 + resolution: "@next/swc-darwin-arm64@npm:15.3.3" 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.3": + version: 15.3.3 + resolution: "@next/swc-darwin-x64@npm:15.3.3" 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.3": + version: 15.3.3 + resolution: "@next/swc-linux-arm64-gnu@npm:15.3.3" 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.3": + version: 15.3.3 + resolution: "@next/swc-linux-arm64-musl@npm:15.3.3" 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.3": + version: 15.3.3 + resolution: "@next/swc-linux-x64-gnu@npm:15.3.3" 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.3": + version: 15.3.3 + resolution: "@next/swc-linux-x64-musl@npm:15.3.3" 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.3": + version: 15.3.3 + resolution: "@next/swc-win32-arm64-msvc@npm:15.3.3" 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.3": + version: 15.3.3 + resolution: "@next/swc-win32-x64-msvc@npm:15.3.3" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -631,13 +855,6 @@ __metadata: 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 - "@rtsao/scc@npm:^1.1.0": version: 1.1.0 resolution: "@rtsao/scc@npm:1.1.0" @@ -645,97 +862,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 +968,7 @@ __metadata: peerDependenciesMeta: prettier: optional: true - checksum: 10/78776f51b9eae00f9387421b33b646b1dc67ef833fd6272de03399daa7f0ffa248c65b5f24d5d2a9af923a029d06d84d5425e3455302ece542bf47c7a9ec0df6 + checksum: 10/8f8c811edd4ea8dcedcc63a79b3168dc83aa0401e3760990cb5995ea6beaac4026c6ccc1046182f41eed7c09333005f3348d8b42f89fc5dd5b2ce011a9d2a48f languageName: node linkType: hard @@ -762,92 +979,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 +1086,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 +1108,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 +1125,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.7 - resolution: "@types/react-dom@npm:18.3.7" +"@types/react-dom@npm:19.1.6": + version: 19.1.6 + resolution: "@types/react-dom@npm:19.1.6" peerDependencies: - "@types/react": ^18.0.0 - checksum: 10/317569219366d487a3103ba1e5e47154e95a002915fdcf73a44162c48fe49c3a57fcf7f57fc6979e70d447112681e6b13c6c3c1df289db8b544df4aab2d318f3 + "@types/react": ^19.0.0 + checksum: 10/b5b20b7f0797f34c5a11915b74dcf8b3b7a9da9fea90279975ce6f150ca5d31bb069dbb0838638a5e9e168098aa4bb4a6f61d078efa1bbb55d7f0bdfe47bb142 languageName: node linkType: hard -"@types/react@npm:^18.0.0": - version: 18.3.21 - resolution: "@types/react@npm:18.3.21" +"@types/react@npm:19.1.8": + version: 19.1.8 + resolution: "@types/react@npm:19.1.8" dependencies: - "@types/prop-types": "npm:*" csstype: "npm:^3.0.2" - checksum: 10/89adc2fe391fd63b620db0aaa10b2be89fad338039415fd748e60c962e47bf2b0f1897cb5226f181eda963f37a1c8ac1bf0b3b29a0e35313bacd0355e5d6c736 + checksum: 10/a3e6fe0f60f22828ef887f30993aa147b71532d7b1219dd00d246277eb7a9ca01ec533096237fa21ca1bccb3653373b4e8e59e5ae59f9c793058384bbc1f4d5c languageName: node linkType: hard @@ -965,120 +1173,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.23.13": + version: 4.23.13 + resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.23.13" dependencies: "@codemirror/autocomplete": "npm:^6.0.0" "@codemirror/commands": "npm:^6.0.0" @@ -1095,13 +1327,13 @@ __metadata: "@codemirror/search": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: 10/7b8cbb9ccc931bc1a6909d009e653f78797e4b851aa9fb8e11f90ae1cea233e643a1951ad4ad9e10123ef166808acee1acff24732ea9fc15cd77164f6b179b91 + checksum: 10/35fd1894f9f7da8f94fa077a70821f92118597e2e2b12782b254b0844f06c1855508421c4cb47de7099a972ef706218c08203116aa93da4e8e1585c8d234015d 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 +1342,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.23.13 + resolution: "@uiw/react-codemirror@npm:4.23.13" 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.23.13" codemirror: "npm:^6.0.0" peerDependencies: "@babel/runtime": ">=7.11.0" @@ -1132,14 +1364,135 @@ __metadata: codemirror: ">=6.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 10/dc6f1fd2f1863120e29ea331b2e279054bf508f00656d3fed97779f17818ae00b6cf9d74d3b0996800174d444b59702da74ca88c303db563daad85c0824fc952 + checksum: 10/51777d7eb313be0716d3597f829e6ee9982ab2dc3e36dc5219f25cdf680b82a6c0a0951d9adaca85b9c829da92f7971aa6e65bdd50e7610972d89463fe2587a1 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 @@ -1153,11 +1506,11 @@ __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 @@ -1180,14 +1533,7 @@ __metadata: 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:^4.0.0, ansi-styles@npm:^4.1.0": +"ansi-styles@npm:^4.1.0": version: 4.3.0 resolution: "ansi-styles@npm:4.3.0" dependencies: @@ -1196,13 +1542,6 @@ __metadata: 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" @@ -1228,16 +1567,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 +1597,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 +1688,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 +1705,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 @@ -1393,21 +1742,21 @@ __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 "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 @@ -1476,9 +1825,9 @@ __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 @@ -1490,21 +1839,23 @@ __metadata: "@lezer/highlight": "npm:^1.2.1" "@mdx-js/loader": "npm:^3.1.0" "@mdx-js/react": "npm:^3.1.0" - "@next/mdx": "npm:^15.1.4" + "@next/mdx": "npm:15.3.3" "@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" + "@types/react": "npm:19.1.8" + "@types/react-dom": "npm:19.1.6" "@uiw/codemirror-themes": "npm:^4.23.7" "@uiw/react-codemirror": "npm:^4.23.7" eslint: "npm:^8" - eslint-config-next: "npm:14.2.23" + eslint-config-next: "npm:15.3.3" + html-react-parser: "npm:^5.2.5" 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" + next: "npm:15.3.3" + next-mdx-remote-client: "npm:^2.1.2" + react: "npm:19.1.0" + react-dom: "npm:19.1.0" react-frame-component: "npm:^5.2.7" shiki: "npm:^1.26.1" storybook: "npm:^8.6.8" @@ -1595,13 +1946,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 +1987,14 @@ __metadata: languageName: node linkType: hard -"crelt@npm:^1.0.5": +"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": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -1690,24 +2061,24 @@ __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" +"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/1847944c2e3c2c732514b93d11886575625686056cd765336212dc15de2d2b29612b6cd80e1afba767bb8e1803b778caf9973e98169ef1a24a7a7009e1820367 + checksum: 10/8e2709b2144f03c7950f8804d01ccb3786373df01e406a0f66928e47001cf2d336cbed9ee137261d4f90d68d8679468c755e3548ed83ddacdc82b194d2468afe 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 +2125,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 @@ -1788,6 +2159,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" @@ -1799,13 +2208,6 @@ __metadata: 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 - "emoji-regex-xs@npm:^1.0.0": version: 1.0.0 resolution: "emoji-regex-xs@npm:1.0.0" @@ -1813,13 +2215,6 @@ __metadata: 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" @@ -1827,36 +2222,40 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.15.0": - version: 5.18.0 - resolution: "enhanced-resolve@npm:5.18.0" - dependencies: - graceful-fs: "npm:^4.2.4" - tapable: "npm:^2.2.0" - checksum: 10/e88463ef97b68d40d0da0cd0c572e23f43dba0be622d6d44eae5cafed05f0c5dac43e463a83a86c4f70186d029357f82b56d9e1e47e8fc91dce3d6602f8bd6ce +"entities@npm:^4.2.0": + version: 4.5.0 + resolution: "entities@npm:4.5.0" + checksum: 10/ede2a35c9bce1aeccd055a1b445d41c75a14a2bb1cd22e242f20cf04d236cdcd7f9c859eb83f76885327bfae0c25bf03303665ee1ce3d47c5927b98b0e3e3d48 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:^6.0.0": + version: 6.0.1 + resolution: "entities@npm:6.0.1" + checksum: 10/62af1307202884349d2867f0aac5c60d8b57102ea0b0e768b16246099512c28e239254ad772d6834e7e14cb1b6f153fc3d0c031934e3183b086c86d3838d874a + 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 +2267,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 +2293,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 +2357,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 +2413,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 +2494,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10/451daf6a442df29ec5d528587caa4ce783d41ff4acb93252da5a852b8d36c22e9f84d17f6721d4fbef9a1ba9855bc9fe1f167dd732c11665fe53032f2b89f114 + checksum: 10/0fa4c3b42c6ddf1a008e75a4bb3dcab08ce22ac0b31dd59dc01f7fe8e21380bfaec07a2fe3730a7cf430da5a30142d016714b358666325a4733547afa42be405 languageName: node linkType: hard @@ -2103,27 +2505,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.3": + version: 15.3.3 + resolution: "eslint-config-next@npm:15.3.3" dependencies: - "@next/eslint-plugin-next": "npm:14.2.23" - "@rushstack/eslint-patch": "npm:^1.3.3" + "@next/eslint-plugin-next": "npm:15.3.3" + "@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/63c51a9e4cc3f54073d15aff85b1088459439169721c379af1202df62c3a757c3849a194f46d4ec95a9c202322aa25d1ddab24159addcca290532a09afda3532 languageName: node linkType: hard @@ -2139,17 +2541,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 +2560,7 @@ __metadata: optional: true eslint-plugin-import-x: optional: true - checksum: 10/8158730c11e562c56ed9bf7236dc75bce35b6992dc32c39ac2f4177ab77fca97b95999850204a6458054243607b54aee88c028a61fed4184f24f425fa1afff01 + checksum: 10/b8d6a9b2045c70f043f722f78c9e65bc0283126f0ad92c8f07473f7647d77f7b1562f765a472f17e06b81897b407091c0ec9f2e4592b158c9fd92d0b0c33de89 languageName: node linkType: hard @@ -2175,7 +2576,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 +2605,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 +2630,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 +2653,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 +2663,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 +2685,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 @@ -2473,6 +2874,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 +2915,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 +2975,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 @@ -2564,22 +2990,12 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^3.1.0": - version: 3.3.0 - resolution: "foreground-child@npm:3.3.0" +"framer-motion@npm:^12.17.0": + version: 12.17.0 + resolution: "framer-motion@npm:12.17.0" dependencies: - cross-spawn: "npm:^7.0.0" - signal-exit: "npm:^4.0.1" - checksum: 10/e3a60480f3a09b12273ce2c5fcb9514d98dd0e528f58656a1b04680225f918d60a2f81f6a368f2f3b937fcee9cfc0cbf16f1ad9a0bc6a3a6e103a84c9a90087e - languageName: node - linkType: hard - -"framer-motion@npm:^12.5.0": - version: 12.5.0 - resolution: "framer-motion@npm:12.5.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 +3008,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 10/6659e0b704d3216dafdcb11175e3c5f55d536469ddd8ad62b37d00bc7e893d914989791c018cfdfa17b3142b655f0d85226adc94ed390c1524312c3653b8c75f + checksum: 10/803b57cb7ec7200938c9425392a66839c96bf5a800ff14b38d9e689a47341678c53ce54765f68f0a186cfad6c1270b2ad46a75aeff67b2a2e2f3b846852d792c languageName: node linkType: hard @@ -2670,12 +3086,12 @@ __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 @@ -2697,21 +3113,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:10.3.10": - version: 10.3.10 - resolution: "glob@npm:10.3.10" - 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" - bin: - glob: dist/esm/bin.mjs - checksum: 10/38bdb2c9ce75eb5ed168f309d4ed05b0798f640b637034800a6bf306f39d35409bf278b0eaaffaec07591085d3acb7184a201eae791468f0f617771c2486a6a8 - languageName: node - linkType: hard - "glob@npm:^7.1.3": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -2752,13 +3153,6 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4": - 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" @@ -2814,7 +3208,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 +3218,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 +3232,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 +3252,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 +3274,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 +3292,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.5 + resolution: "html-react-parser@npm:5.2.5" + 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.16" + 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/22852dc4826d3be9506e238e37a05c23b432675aac33f22bd5741caa195a32e99de99d2b99037fda532f198afbcdeacfc0b27627d6e529143a98c8f191e99c52 + 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 +3327,39 @@ __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 + +"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 @@ -3002,15 +3443,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 @@ -3024,21 +3473,21 @@ __metadata: linkType: hard "is-boolean-object@npm:^1.2.1": - version: 1.2.1 - resolution: "is-boolean-object@npm:1.2.1" + version: 1.2.2 + resolution: "is-boolean-object@npm:1.2.2" dependencies: - call-bound: "npm:^1.0.2" + call-bound: "npm:^1.0.3" has-tostringtag: "npm:^1.0.2" - checksum: 10/5a15524635c9334ebbd668f20a6cbf023adceed5725ec96a50056d21ae65f52759d04a8fa7d7febf00ff3bc4e6d3837638eb84be572f287bcfd15f8b8facde43 + checksum: 10/051fa95fdb99d7fbf653165a7e6b2cba5d2eb62f7ffa81e793a790f3fb5366c91c1b7b6af6820aa2937dd86c73aa3ca9d9ca98f500988457b1c59692c52ba911 languageName: node linkType: hard -"is-bun-module@npm:^1.0.2": - version: 1.3.0 - resolution: "is-bun-module@npm:1.3.0" +"is-bun-module@npm:^2.0.0": + version: 2.0.0 + resolution: "is-bun-module@npm:2.0.0" dependencies: - semver: "npm:^7.6.3" - checksum: 10/b23d9ec7b4d4bfd89e4e72b5cd52e1bc153facad59fdd7394c656f8859a78740ef35996a2066240a32f39cc9a9da4b4eb69e68df3c71755a61ebbaf56d3daef0 + semver: "npm:^7.7.1" + checksum: 10/cded5a1a58368b847872d08617975d620ad94426d76a932f3e08d55b4574d199e0a62a4fb024fa2dc444200b71719eb0bffc5d3d1e1cc82e29b293bb8d66a990 languageName: node linkType: hard @@ -3111,13 +3560,6 @@ __metadata: 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.10, is-generator-function@npm:^1.0.7": version: 1.1.0 resolution: "is-generator-function@npm:1.1.0" @@ -3153,6 +3595,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 +3661,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 +3698,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 @@ -3305,20 +3754,7 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^2.3.5": - version: 2.3.6 - resolution: "jackspeak@npm:2.3.6" - dependencies: - "@isaacs/cliui": "npm:^8.0.2" - "@pkgjs/parseargs": "npm:^0.11.0" - dependenciesMeta: - "@pkgjs/parseargs": - optional: true - checksum: 10/6e6490d676af8c94a7b5b29b8fd5629f21346911ebe2e32931c2a54210134408171c24cee1a109df2ec19894ad04a429402a8438cbf5cc2794585d35428ace76 - 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 @@ -3422,91 +3858,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 +3964,7 @@ __metadata: optional: true lightningcss-win32-x64-msvc: optional: true - checksum: 10/90933a666975f8e4f2b1bed1ec698a0072f96d2cd118e358932456a46105d62b72b701e4a5da5bd66f0b6e3beac2c65d0be2306acdf3f30603d86143502cce88 + checksum: 10/6c921135216cc498dfcb87e35dffe8432e99306cbd58009c598b1daf20c81cc14535abbd4c1066e5d1faf4080ed44a2995e8ecc343633db4897a2d041b76fb05 languageName: node linkType: hard @@ -3555,7 +3991,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 +4002,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^10.2.0": - version: 10.4.3 - resolution: "lru-cache@npm:10.4.3" - checksum: 10/e6e90267360476720fa8e83cc168aa2bf0311f3f2eea20a6ba78b90a885ae72071d9db132f40fda4129c803e7dcec3a6b6a6fbb44ca90b081630b810b5d6a41a - languageName: node - linkType: hard - "markdown-extensions@npm:^2.0.0": version: 2.0.0 resolution: "markdown-extensions@npm:2.0.0" @@ -3622,8 +4051,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 +4066,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 +4083,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 +4158,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 +4177,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 +4193,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 +4211,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 +4281,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 +4293,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 +4400,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 +4410,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 +4451,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 +4470,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 +4497,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 +4520,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 +4536,27 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0": - 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" +"motion-dom@npm:^12.17.0": + version: 12.17.0 + resolution: "motion-dom@npm:12.17.0" dependencies: - motion-utils: "npm:^12.5.0" - checksum: 10/289e3d5567053e9b84223df9b482bb9180732c0cb6276e7982a73ddad0f76e47f8e0f69e4cdf93d7a689ad021fce837fa8a19c8b61bf56d71bdfc0e590bc3e7e + motion-utils: "npm:^12.12.1" + checksum: 10/54b482d643ca12d895e8a0860809b2b53f2e7c2981d12f3d1230f5bd6154cb59df6ab81a690681df7a526da283aaf76acbf351b148e2b805ad5f8ce97156e061 languageName: node linkType: hard -"motion-utils@npm:^12.5.0": - version: 12.5.0 - resolution: "motion-utils@npm:12.5.0" - checksum: 10/f2d754a9e1c4cd829f0703ce47bbeb3e9967994a9cb35e759f34de16af6c520a4959b04ff6998211ca67dc7bfe0002d9b2465858167b1971b0403f63289f97fc +"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 +4569,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 10/15910d37a14b91236c27c8cc6d12ae157cd1e6b55700fc58a0d271954236fae8e676b1ac9f705cb770c235892925fc6bbdb1f612dfb4018a5d4c0171964f5b3a + checksum: 10/4fde3c4f39fa872cb27a41ddf39dcf20a9a066428ce07cf7455e2a4779d916a47cb3141e8c4c970f9d7b8e8818b822f3eeb86761d61904a18383b82426f5f3ca languageName: node linkType: hard @@ -4161,11 +4581,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 +4605,50 @@ __metadata: languageName: node linkType: hard -"next@npm:14.2.26": - version: 14.2.26 - resolution: "next@npm:14.2.26" +"next-mdx-remote-client@npm:^2.1.2": + version: 2.1.2 + resolution: "next-mdx-remote-client@npm:2.1.2" 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.1.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/610e95bff6c1dcdb4d1fefc9333ac748f1899cbca1ea551e784cd1e8cfd077c14d6bb8ab27826886f354cc3b9cf700fe25810263f996c53f824a2cfdbc5a064f + languageName: node + linkType: hard + +"next@npm:15.3.3": + version: 15.3.3 + resolution: "next@npm:15.3.3" + dependencies: + "@next/env": "npm:15.3.3" + "@next/swc-darwin-arm64": "npm:15.3.3" + "@next/swc-darwin-x64": "npm:15.3.3" + "@next/swc-linux-arm64-gnu": "npm:15.3.3" + "@next/swc-linux-arm64-musl": "npm:15.3.3" + "@next/swc-linux-x64-gnu": "npm:15.3.3" + "@next/swc-linux-x64-musl": "npm:15.3.3" + "@next/swc-win32-arm64-msvc": "npm:15.3.3" + "@next/swc-win32-x64-msvc": "npm:15.3.3" + "@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 +4665,22 @@ __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/287e3b24aade1763b13f95246d7df0e23d5fdde2d8c4b170360b22fabfa38184e102eb64886260e0034f4fc6979a1d47929e423d7946f5da57d306034645f6c7 languageName: node linkType: hard @@ -4241,10 +4691,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 +4719,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 +4775,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 @@ -4441,17 +4892,7 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.10.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 - -"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 @@ -4465,10 +4906,17 @@ __metadata: 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 @@ -4508,10 +4956,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,15 +4977,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.0": + version: 19.1.0 + resolution: "react-dom@npm:19.1.0" 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 + react: ^19.1.0 + checksum: 10/c5b58605862c7b0bb044416b01c73647bb8e89717fb5d7a2c279b11815fb7b49b619fe685c404e59f55eb52c66831236cc565c25ee1c2d042739f4a2cc538aa2 languageName: node linkType: hard @@ -4559,25 +5006,30 @@ __metadata: 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 +"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.0": + version: 19.1.0 + resolution: "react@npm:19.1.0" + checksum: 10/d0180689826fd9de87e839c365f6f361c561daea397d61d724687cae88f432a307d1c0f53a0ee95ddbe3352c10dac41d7ff1ad85530fb24951b27a39e5398db4 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 +5097,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 +5123,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 +5148,19 @@ __metadata: languageName: node linkType: hard +"remark-mdx-remove-esm@npm:^1.1.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 +5184,15 @@ __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 @@ -4805,9 +5263,9 @@ __metadata: 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 @@ -4865,12 +5323,10 @@ __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 +"scheduler@npm:^0.26.0": + version: 0.26.0 + resolution: "scheduler@npm:0.26.0" + checksum: 10/1ecf2e5d7de1a7a132796834afe14a2d589ba7e437615bd8c06f3e0786a3ac3434655e67aac8755d9b14e05754c177e49c064261de2673aaa3c926bc98caa002 languageName: node linkType: hard @@ -4883,12 +5339,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.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 +5394,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" @@ -4946,18 +5486,18 @@ __metadata: 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 @@ -5009,10 +5549,12 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^4.0.1": - version: 4.1.0 - resolution: "signal-exit@npm:4.1.0" - checksum: 10/c9fa63bbbd7431066174a48ba2dd9986dfd930c3a8b59de9c29d7b6854ec1c12a80d15310869ea5166d413b99f041bfa3dd80a7947bcd44ea8e6eb3ffeabfa1f +"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 @@ -5044,18 +5586,28 @@ __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 +"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 +5617,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,28 +5628,6 @@ __metadata: 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 - "string.prototype.includes@npm:^2.0.1": version: 2.0.1 resolution: "string.prototype.includes@npm:2.0.1" @@ -5188,7 +5718,7 @@ __metadata: 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": +"strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" dependencies: @@ -5197,15 +5727,6 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^7.0.1": - 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" @@ -5227,7 +5748,16 @@ __metadata: languageName: node linkType: hard -"style-to-object@npm:^1.0.0": +"style-to-js@npm:1.1.16, style-to-js@npm:^1.0.0": + version: 1.1.16 + resolution: "style-to-js@npm:1.1.16" + dependencies: + style-to-object: "npm:1.0.8" + checksum: 10/a876cc49a29ac90c7723b4d6f002ac6c1ac5ccc6b5bc963d9c607cfc74b15927b704c9324df6f824f576c65689fe4b4ff79caabcd44a13d8a02641f721f1b316 + languageName: node + linkType: hard + +"style-to-object@npm:1.0.8": version: 1.0.8 resolution: "style-to-object@npm:1.0.8" dependencies: @@ -5236,19 +5766,19 @@ __metadata: languageName: node linkType: hard -"styled-jsx@npm:5.1.1": - version: 5.1.1 - resolution: "styled-jsx@npm:5.1.1" +"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 @@ -5268,13 +5798,6 @@ __metadata: languageName: node linkType: hard -"tapable@npm:^2.2.0": - version: 2.2.1 - resolution: "tapable@npm:2.2.1" - checksum: 10/1769336dd21481ae6347611ca5fca47add0962fd8e80466515032125eca0084a4f0ede11e65341b9c0018ef4e1cf1ad820adbb0fba7cc99865c6005734000b0a - languageName: node - linkType: hard - "text-table@npm:^0.2.0": version: 0.2.0 resolution: "text-table@npm:0.2.0" @@ -5289,6 +5812,16 @@ __metadata: languageName: node linkType: hard +"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" @@ -5312,12 +5845,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 +5866,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.1, tslib@npm:^2.4.0": +"tslib@npm:^2.0.1, tslib@npm:^2.4.0, tslib@npm:^2.8.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 @@ -5356,6 +5889,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 +5981,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 @@ -5490,6 +6030,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 +6071,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 +6154,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 +6174,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 +6237,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: @@ -5648,28 +6270,6 @@ __metadata: 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 - "wrappy@npm:1": version: 1.0.2 resolution: "wrappy@npm:1.0.2" @@ -5678,8 +6278,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 +6288,16 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10/3f38e9594f2af5b6324138e86b74df7d77bbb8e310bf8188679dd80bac0d1f47e51536a1923ac3365f31f3d8b25ea0b03e4ade466aa8292a86cd5defca64b19b + checksum: 10/018e04ec95561d88248d53a2eaf094b4ae131e9b062f2679e6e8a62f04649bc543448f1e038125225ac6bbb25f54c1e65d7a2cc9dbc1e28b43e5e6b7162ad88e + 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 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/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/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/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/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/root-http-router.md b/docs/backend-system/core-services/root-http-router.md index 7a268e27fc..5f745966db 100644 --- a/docs/backend-system/core-services/root-http-router.md +++ b/docs/backend-system/core-services/root-http-router.md @@ -60,7 +60,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 @@ -161,3 +161,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..08e4379620 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 --- 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/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/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..0e54ba2690 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 --- 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/experimental.md b/docs/features/software-templates/experimental.md index 929fe088cf..1df041d7a2 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 --- diff --git a/docs/features/software-templates/index.md b/docs/features/software-templates/index.md index 10550f7c01..16f7c3cbbd 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 --- 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..830c343033 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 --- 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/writing-templates.md b/docs/features/software-templates/writing-templates.md index eeaa4ad203..275d5c26b2 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -274,6 +274,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). 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..7d8c99ffda 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -942,6 +942,35 @@ metadata: backstage.io/techdocs-entity: system:default/example ``` +### Deep linking into TechDocs + +The `backstage.io/techdocs-entity-path` annotation can be use to deep link into a specific page within the components TechDocs. +This can be used in conjunction with `backstage.io/techdocs-entity` or standalone. + +```yaml +apiVersion: backstage.io/v1alpha1 +kind: System +metadata: + name: example + namespace: default + title: Example + description: This is the parent entity + annotations: + backstage.io/techdocs-ref: dir:. + +--- +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: example-platfrom + title: Example Application Platform + namespace: default + description: This is the child entity + annotations: + backstage.io/techdocs-entity: system:default/example + backstage.io/techdocs-entity-path: /path/to/component/docs +``` + ## How to resolve broken links from moved or renamed pages in your documentation site TechDocs supports using the [mkdocs-redirects](https://github.com/mkdocs/mkdocs-redirects/tree/master) plugin to create a redirect map for any TechDocs site. This allows broken links from renamed or moved pages in your site to be redirected to their specified replacement. diff --git a/docs/frontend-system/architecture/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..3fcfec9e9b 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()`. @@ -77,3 +76,98 @@ app: ``` Note that you do not need to manually exclude packages that you also import explicitly in code, since plugin instances are deduplicated by the app. You will never end up with duplicate plugin installations except if they are in fact two different plugin instances with different IDs. + +## Plugin Info Resolution + +When a plugin is installed in an app it may provide sources of information about the plugin that can be useful to end users and admins. This includes things like what version of a plugin is running, what team owns the plugin, and who to contact for support. You can read more about how the plugins provide this information in the [plugins `info` option section](./15-plugins.md#info). + +By default the app will pick a few common fields from `package.json` files, and assume that the opaque manifests are `catalog-info.yaml` files that some information can be gathered from too. This information will then be available via the `info()` method on plugin instances, returning a structure of the `FrontendPluginInfo` type. + +### Extending Plugin Info + +The default plugin info is intended as a base to build upon. As part of setting up an app you can both customize the way that the plugin info is resolved, as well as extend the `FrontendPluginInfo` type to include more information. + +In order to extend the `FrontendPluginInfo` type you use [TypeScript module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation). This makes it possible to extend the `FrontendPluginInfo` interface with additional fields, which you can then add custom resolution logic for as well as access within the app. For example, you might add a `slackChannel` field as follows: + +```ts +declare module '@backstage/frontend-plugin-api' { + interface FrontendPluginInfo { + /** + * The slack channel to use for support requests for this plugin. + */ + slackChannel?: string; + } +} +``` + +### Customizing Plugin Info Resolution + +With the new `slackChannel` field in place, we now need to provide a custom resolver that knows how to extract this information from the plugin information sources. This is done by passing a custom `pluginInfoResolver` to `createApp`, which in our example is declared like this: + +```ts title="pluginInfoResolver.ts" +import { createPluginInfoResolver } from '@backstage/frontend-plugin-api'; + +// It is recommended to keep the above module augmentation in this file too + +export const pluginInfoResolver: FrontendPluginInfoResolver = async ctx => { + // In our particular example app we assume that all plugin manifests are catalog-info.yaml files + const manifest = (await ctx.manifest?.()) as Entity | undefined; + + // Call the default resolver to populate common fields + const { info } = await ctx.defaultResolver({ + packageJson: await ctx.packageJson(), + manifest: manifest, + }); + + // In this example the catalog model has been extended with a metadata.slackChannel field + const slackChannel = manifest?.metadata?.slackChannel?.toString(); + + if (slackChannel) { + info.slackChannel = slackChannel; + info.links = [ + ...(info.links ?? []), + { + title: 'Slack Channel', + url: `https://our-workspace.enterprise.slack.com/archives/${slackChannel}`, + }, + ]; + } + + return { info }; +}; +``` + +And included in the app as follows: + +```ts title="App.tsx" +import { pluginInfoResolver } from './pluginInfoResolver'; + +const app = createApp({ + pluginInfoResolver, + // ... other options +}); +``` + +### Overriding Plugin Info + +Another way to customize the plugin info is to use the `app.pluginOverrides` static configuration key. These overrides are applied after the plugin info has been resolved as a final step before making it available to users. These overrides are particularly useful to override information in third-party plugins. For example, if your organization has an individual team that is responsible for the maintenance of the Software Catalog, you might configure the following override: + +```yaml +app: + pluginOverrides: + - match: + pluginId: catalog + info: + ownerEntityRefs: [catalog-owners] +``` + +You can match on both the `pluginId` and/or `packageName` of the plugin, although the `packageName` will only be supported if the plugin provides an loader for the `package.json` file. Using `//` you are also able to use a regex pattern for this matching. For example, if you wanted to override the owner for all plugins from the `@acme` namespace, you could do the following: + +```yaml +app: + pluginOverrides: + - match: + packageName: /@acme/.*/ + info: + ownerEntityRefs: [acme-owners] +``` diff --git a/docs/frontend-system/architecture/15-plugins.md b/docs/frontend-system/architecture/15-plugins.md index 825f2a3982..13c93c6962 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 --- @@ -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. 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..081497655e 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 --- diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md index faaf446f41..6b07fe4faa 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 --- 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..c07eea304d 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 --- 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..4cca7cebe2 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 --- diff --git a/docs/frontend-system/building-apps/06-plugin-conversion.md b/docs/frontend-system/building-apps/06-plugin-conversion.md index 2fab6cf828..720a29ddb7 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 diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index 861be2e39c..6b1332834a 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,6 +195,8 @@ 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: { @@ -154,9 +209,7 @@ const scmIntegrationsApi = ApiBlueprint.make({ }); ``` -### `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 +245,8 @@ 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' + experimental: + packages: all # ✨ ``` ### `featureFlags` @@ -218,6 +272,8 @@ createApp({ Can be converted to the following plugin configuration: ```tsx +import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; + createFrontendPlugin({ pluginId: 'tech-radar', // ... @@ -226,6 +282,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 +315,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 +335,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 +347,7 @@ For example, the following theme configuration: const app = createApp({ themes: [ { - id: 'light', + id: 'custom-light', title: 'Light', variant: 'light', Provider: ({ children }) => ( @@ -301,27 +363,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 +471,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 +488,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 +501,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..e9d6f1cbe2 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 --- 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..e1e903eadd 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 --- 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..483ebf68f8 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 --- 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..d02b74cbf6 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 --- diff --git a/docs/frontend-system/utility-apis/03-consuming.md b/docs/frontend-system/utility-apis/03-consuming.md index bdf3b8dcdb..d5ea537284 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 --- 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/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 61c54287c9..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 --- 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 index 6cae169b58..ce8ad9d360 100644 --- a/docs/integrations/gitea/discovery.md +++ b/docs/integrations/gitea/discovery.md @@ -30,7 +30,7 @@ backend.add(import('@backstage/plugin-catalog-backend-module-gitea')); ## Configuration -To use the discovery processor, you'll need a Gitea integration +To use the discovery provider, you'll need a Gitea integration [set up](locations.md). Then you can add any number of providers. ```yaml diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index 01567a7191..5dd13a7bec 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 --- diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 3d0e0a4221..b054daf343 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 --- 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/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/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/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/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/references/glossary.md b/docs/references/glossary.md index 906260e710..273749323f 100644 --- a/docs/references/glossary.md +++ b/docs/references/glossary.md @@ -1,7 +1,6 @@ --- id: glossary title: Glossary -# prettier-ignore description: List of terms, abbreviations, and phrases used in Backstage, together with their explanations. --- 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.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-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/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..2008e0c5e8 100644 --- a/docs/tutorials/setup-opentelemetry.md +++ b/docs/tutorials/setup-opentelemetry.md @@ -4,7 +4,7 @@ title: Setup OpenTelemetry description: Tutorial to setup OpenTelemetry metrics and traces exporters in Backstage --- -Backstage uses [OpenTelemetery](https://opentelemetry.io/) to instrument its components by reporting traces and metrics. +Backstage uses [OpenTelemetry](https://opentelemetry.io/) to instrument its components by reporting traces and metrics. This tutorial shows how to setup exporters in your Backstage backend package. For demonstration purposes we will use a Prometheus exporter, but you can adjust your solution to use another one that suits your needs; see for example the article on [OTLP exporters](https://opentelemetry.io/docs/instrumentation/js/exporters/). @@ -26,7 +26,7 @@ yarn --cwd packages/backend add \ In your `packages/backend/src` folder, create an `instrumentation.js` file. -```typescript title="in packages/backend/src/instrumentation.js" +```js title="in packages/backend/src/instrumentation.js" // Prevent from running more than once (due to worker threads) const { isMainThread } = require('node:worker_threads'); @@ -52,6 +52,42 @@ if (isMainThread) { You probably won't need all of the instrumentation inside `getNodeAutoInstrumentations()` so make sure to check the [documentation](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node) and tweak it properly. +### Views + +The default histogram buckets for OpenTelemetry are in milliseconds, but the histograms that are created for Catalog processing emit metrics in second. You might want to adjust this to what fits your need. To do this you can use the [Views feature](https://opentelemetry.io/docs/concepts/signals/metrics/#views) like this: + +```js +const prometheus = new PrometheusExporter(); +const sdk = new NodeSDK({ + metricReader: prometheus, + views: [ + new View({ + instrumentName: 'catalog.test', + aggregation: new ExplicitBucketHistogramAggregation([ + 0.01, 0.1, 0.5, 1, 5, 10, 25, 50, 100, 500, 1000, + ]), + }), + ], +}); +``` + +The above will make all the histogram buckets use the same config. If you would like to take a more targeted approach you can do this: + +```js +const prometheus = new PrometheusExporter(); +const sdk = new NodeSDK({ + metricReader: prometheus, + views: [ + new View({ + instrumentName: 'catalog.test', + aggregation: new ExplicitBucketHistogramAggregation([ + 0, 0.01, 0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, 30, 60, 120, 300, 1000, + ]), + }), + ], +}); +``` + ## Local Development Setup It's important to setup the NodeSDK and the automatic instrumentation **before** @@ -67,13 +103,13 @@ For local development, you can add the required flag in your `packages/backend/p ... ``` -You can now start your Backstage instance as usual, using `yarn start`. +You can now start your Backstage instance as usual, using `yarn start` and you'll be able to see your metrics at: ## Production Setup In your `.dockerignore`, add this line: -``` +```text !packages/backend/src/instrumentation.js ``` diff --git a/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 c82db9ff6c..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 --- 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/assets/2025-05-12/changelog.png b/microsite/blog/assets/2025-05-12/changelog.png new file mode 100644 index 0000000000..4a97d31a55 Binary files /dev/null and b/microsite/blog/assets/2025-05-12/changelog.png differ diff --git a/microsite/blog/assets/2025-05-12/cncf_1.jpeg b/microsite/blog/assets/2025-05-12/cncf_1.jpeg new file mode 100644 index 0000000000..7157be6805 Binary files /dev/null and b/microsite/blog/assets/2025-05-12/cncf_1.jpeg differ diff --git a/microsite/blog/assets/2025-05-12/cncf_2.png b/microsite/blog/assets/2025-05-12/cncf_2.png new file mode 100644 index 0000000000..1a7766d665 Binary files /dev/null and b/microsite/blog/assets/2025-05-12/cncf_2.png differ diff --git a/microsite/blog/assets/2025-05-12/cncf_3.jpeg b/microsite/blog/assets/2025-05-12/cncf_3.jpeg new file mode 100644 index 0000000000..cf9070a35b Binary files /dev/null and b/microsite/blog/assets/2025-05-12/cncf_3.jpeg differ diff --git a/microsite/blog/assets/2025-05-12/first_contributions.png b/microsite/blog/assets/2025-05-12/first_contributions.png new file mode 100644 index 0000000000..26ecf77565 Binary files /dev/null and b/microsite/blog/assets/2025-05-12/first_contributions.png differ diff --git a/microsite/blog/assets/2025-05-12/stages.png b/microsite/blog/assets/2025-05-12/stages.png new file mode 100644 index 0000000000..1d6aea0b64 Binary files /dev/null and b/microsite/blog/assets/2025-05-12/stages.png differ diff --git a/microsite/blog/assets/2025-05-12/tech_stack.png b/microsite/blog/assets/2025-05-12/tech_stack.png new file mode 100644 index 0000000000..6af9194488 Binary files /dev/null and b/microsite/blog/assets/2025-05-12/tech_stack.png differ diff --git a/microsite/data/plugins/daily-weather-update.yaml b/microsite/data/plugins/daily-weather-update.yaml new file mode 100644 index 0000000000..4118a4d5d6 --- /dev/null +++ b/microsite/data/plugins/daily-weather-update.yaml @@ -0,0 +1,10 @@ +--- +title: Daily Weather +author: Infosys Limited +authorUrl: https://github.com/Infosys +category: Utility +description: The Daily Weather Plugin for Backstage provides real-time weather information for a specified location using the Weatherstack API. +documentation: https://github.com/Infosys/daily-weather-plugin/tree/main/plugins/weather +iconUrl: https://github.com/Infosys/daily-weather-plugin/blob/main/plugins/weather/src/docs/plugin-logo.png?raw=true +npmPackageName: '@infosys_ltd/daily-weather-plugin' +addedDate: '2025-04-16' diff --git a/microsite/data/plugins/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/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/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/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 1f8d3defb7..4bf0da08a8 100644 --- a/microsite/sidebars.ts +++ b/microsite/sidebars.ts @@ -330,7 +330,7 @@ export default { { type: 'category', label: 'Gitea', - items: ['integrations/gitea/locations'], + items: ['integrations/gitea/locations', 'integrations/gitea/discovery'], }, { type: 'category', diff --git a/microsite/src/components/bannerSection/bannerSection.module.scss b/microsite/src/components/bannerSection/bannerSection.module.scss index 64f4967d11..f6fe0536dc 100644 --- a/microsite/src/components/bannerSection/bannerSection.module.scss +++ b/microsite/src/components/bannerSection/bannerSection.module.scss @@ -38,13 +38,12 @@ } &:global(.greyBackground) { - background: linear-gradient(90.49deg, #121212 15.36%, #282828 70.44%); + background: var(--ifm-grey-background); } &:global(.greenGradientBackground) { - background: linear-gradient(70.44deg, #121212 55%, #5d817b); + background: var(--ifm-green-gradient-background); } - &:global(.greenBottomGradientBackground) { background: linear-gradient( 178.64deg, diff --git a/microsite/src/pages/community/index.tsx b/microsite/src/pages/community/index.tsx index 8c1736e1e9..9770c91e8e 100644 --- a/microsite/src/pages/community/index.tsx +++ b/microsite/src/pages/community/index.tsx @@ -5,6 +5,8 @@ import { BannerSectionGrid } from '@site/src/components/bannerSection/bannerSect import Layout from '@theme/Layout'; import { clsx } from 'clsx'; import React from 'react'; +import ThemedImage from '@theme/ThemedImage'; +import useBaseUrl from '@docusaurus/useBaseUrl'; import { ContentBlock } from '../../components/contentBlock/contentBlock'; import communityStyles from './community.module.scss'; @@ -83,47 +85,47 @@ const Community = () => { { name: 'Frontside Software', url: 'https://frontside.com/backstage/', - logo: 'img/partner-logo-frontside.png', + logo: 'img/partner-logo-frontside', }, { name: 'RedHat', url: 'https://developers.redhat.com/rhdh', - logo: 'img/partner-logo-redhat.png', + logo: 'img/partner-logo-redhat', }, { name: 'Roadie', url: 'https://roadie.io/', - logo: 'img/partner-logo-roadie.png', + logo: 'img/partner-logo-roadie', }, { name: 'solo.io', url: 'https://www.solo.io/spotlight/', - logo: 'img/partner-logo-solo.png', + logo: 'img/partner-logo-solo', }, { name: 'ThoughtWorks', url: 'https://www.thoughtworks.com/about-us/partnerships/technology/backstage-by-spotify', - logo: 'img/partner-logo-thoughtworks.png', + logo: 'img/partner-logo-thoughtworks', }, { name: 'VMWare', url: 'https://tanzu.vmware.com/developer-portal', - logo: 'img/partner-logo-tanzubybroadcom.png', + logo: 'img/partner-logo-tanzubybroadcom', }, { name: 'StatusNeo', url: 'https://statusneo.com/backstage', - logo: 'img/partner-logo-statusneo.png', + logo: 'img/partner-logo-statusneo', }, { name: 'Alauda', url: 'https://www.alauda.io/community/169249', - logo: 'img/partner-logo-alauda.png', + logo: 'img/partner-logo-alauda', }, { name: 'Liatrio', url: 'https://www.liatrio.com/service-offerings/backstage', - logo: 'img/partner-logo-liatrio.png', + logo: 'img/partner-logo-liatrio', }, ]; //#endregion @@ -233,7 +235,13 @@ const Community = () => { {partners.map(({ name, url, logo }, index) => (
- {name} +
))} diff --git a/microsite/src/pages/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/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 e6454c4e82..a200d0f9da 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -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 diff --git a/mkdocs.yml b/mkdocs.yml index 688ca1a078..eea5fc9c40 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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 5f8bf49205..fc773cb0d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.39.0-next.3", + "version": "1.40.0-next.3", "backstage": { "cli": { "new": { diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index e15b844a8d..d2689dc318 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,27 @@ # @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 + +## 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 diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index 18a5106385..93baf003bf 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.2", + "version": "1.6.3-next.0", "description": "Provides the default wiring of a Backstage App", "backstage": { "role": "web-library" diff --git a/packages/app-next-example-plugin/CHANGELOG.md b/packages/app-next-example-plugin/CHANGELOG.md index f39080a253..7c8641ae87 100644 --- a/packages/app-next-example-plugin/CHANGELOG.md +++ b/packages/app-next-example-plugin/CHANGELOG.md @@ -1,5 +1,28 @@ # 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 + +## 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/package.json b/packages/app-next-example-plugin/package.json index e0fd8ffff7..b6692df969 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.24-next.1", "description": "Backstage internal example plugin", "backstage": { "role": "frontend-plugin", diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index c7f21def01..64f4c94f60 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,224 @@ # 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 + +## 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 diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml index 9d2bcf198a..da233b0188 100644 --- a/packages/app-next/app-config.yaml +++ b/packages/app-next/app-config.yaml @@ -8,6 +8,20 @@ app: catalog.createComponent: catalog-import.importPage org.catalogIndex: catalog.catalogIndex + pluginOverrides: + - match: + pluginId: pages + info: + description: 'This description was overridden in packages/app-next/app-config.yaml' + - match: + pluginId: /^catalog(-.*)?$/ + info: + ownerEntityRefs: [cubic-belugas] + - match: + packageName: '@backstage/plugin-scaffolder' + info: + ownerEntityRefs: [cubic-belugas] + extensions: # - apis.plugin.graphiql.browse.gitlab: true # - graphiql-endpoint:graphiql/gitlab: true @@ -56,7 +70,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/package.json b/packages/app-next/package.json index 66e6d1ffc5..eab318f7c1 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.3", + "version": "0.0.24-next.3", "backstage": { "role": "frontend" }, diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index 0ecb46e06a..43cc63d1a4 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -43,6 +43,7 @@ import kubernetesPlugin from '@backstage/plugin-kubernetes/alpha'; import { convertLegacyPlugin } from '@backstage/core-compat-api'; import { convertLegacyPageExtension } from '@backstage/core-compat-api'; import { convertLegacyEntityContentExtension } from '@backstage/plugin-catalog-react/alpha'; +import { pluginInfoResolver } from './pluginInfoResolver'; /* @@ -132,6 +133,7 @@ const app = createApp({ customHomePageModule, ...collectedLegacyPlugins, ], + pluginInfoResolver, /* Handled through config instead */ // bindRoutes({ bind }) { // bind(pagesPlugin.externalRoutes, { pageX: pagesPlugin.routes.pageX }); diff --git a/packages/app-next/src/examples/pagesPlugin.tsx b/packages/app-next/src/examples/pagesPlugin.tsx index ddd080c28b..33fc7d8c0b 100644 --- a/packages/app-next/src/examples/pagesPlugin.tsx +++ b/packages/app-next/src/examples/pagesPlugin.tsx @@ -21,7 +21,10 @@ import { createExternalRouteRef, useRouteRef, PageBlueprint, + FrontendPluginInfo, + useAppNode, } from '@backstage/frontend-plugin-api'; +import { useEffect, useState } from 'react'; import { Route, Routes } from 'react-router-dom'; const indexRouteRef = createRouteRef(); @@ -36,6 +39,22 @@ export const pageXRouteRef = createRouteRef(); // path: '/page2', // }); +function PluginInfo() { + const node = useAppNode(); + const [info, setInfo] = useState(undefined); + + useEffect(() => { + node?.spec.source?.info().then(setInfo); + }, [node]); + + return ( +
+

Plugin Info

+
{JSON.stringify(info, null, 2)}
+
+ ); +} + const IndexPage = PageBlueprint.make({ name: 'index', params: { @@ -64,6 +83,7 @@ const IndexPage = PageBlueprint.make({
Settings
+
); }; @@ -139,6 +159,10 @@ export const pagesPlugin = createFrontendPlugin({ // // OR // // 'page1' // }, + info: { + packageJson: () => import('../../package.json'), + manifest: () => import('../../catalog-info.yaml'), + }, routes: { page1: page1RouteRef, pageX: pageXRouteRef, diff --git a/packages/app-next/src/pluginInfoResolver.ts b/packages/app-next/src/pluginInfoResolver.ts new file mode 100644 index 0000000000..12f730875c --- /dev/null +++ b/packages/app-next/src/pluginInfoResolver.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from '@backstage/catalog-model'; +import { FrontendPluginInfoResolver } from '@backstage/frontend-app-api'; + +// This file shows an example of what it looks like to extend the plugin info +// resolution with custom logic and fields. In this case we're reading the +// `spec.type` field from the plugin manifest (catalog-info.yaml). +// +// Using module augmentation we extend the `FrontendPluginInfo` interface to +// include our custom fields. This makes these fields available throughout our project. + +declare module '@backstage/frontend-plugin-api' { + export interface FrontendPluginInfo { + /** + * **DO NOT USE** + * + * This field is added in the example app to showcase module augmentation + * for extending the plugin info in internal apps. It only exists as an + * example in this project. + */ + exampleFieldDoNotUse?: string; + } +} + +export const pluginInfoResolver: FrontendPluginInfoResolver = async ctx => { + const manifest = (await ctx.manifest?.()) as Entity | undefined; + const { info: defaultInfo } = await ctx.defaultResolver({ + packageJson: await ctx.packageJson(), + manifest: manifest, + }); + return { + info: { + ...defaultInfo, + exampleFieldDoNotUse: manifest?.spec?.type?.toString(), + }, + }; +}; diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index add9e984a7..b71dba1a1c 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,204 @@ # 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 + +## 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 diff --git a/packages/app/package.json b/packages/app/package.json index 2211307884..43b3141754 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.109-next.3", + "version": "0.2.110-next.3", "backstage": { "role": "frontend" }, diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index e39122337f..5d300d67cb 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -24,11 +24,14 @@ import { configApiRef, createApiFactory, discoveryApiRef, + fetchApiRef, } from '@backstage/core-plugin-api'; import { AuthProxyDiscoveryApi } from './AuthProxyDiscoveryApi'; import { formDecoratorsApiRef } from '@backstage/plugin-scaffolder/alpha'; import { DefaultScaffolderFormDecoratorsApi } from '@backstage/plugin-scaffolder/alpha'; import { mockDecorator } from './components/scaffolder/decorators'; +import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; +import { ScaffolderClient } from '@backstage/plugin-scaffolder'; export const apis: AnyApiFactory[] = [ createApiFactory({ @@ -42,6 +45,22 @@ export const apis: AnyApiFactory[] = [ factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), }), + createApiFactory({ + api: scaffolderApiRef, + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, + }, + factory: ({ discoveryApi, fetchApi, scmIntegrationsApi }) => + new ScaffolderClient({ + useLongPollingLogs: true, + discoveryApi, + fetchApi, + scmIntegrationsApi, + }), + }), + createApiFactory({ api: formDecoratorsApiRef, deps: {}, diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 6f2c85c934..cbf1bcb5f8 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -17,14 +17,9 @@ import { PropsWithChildren } from 'react'; import { makeStyles } from '@material-ui/core/styles'; import HomeIcon from '@material-ui/icons/Home'; -import RuleIcon from '@material-ui/icons/AssignmentTurnedIn'; -import MapIcon from '@material-ui/icons/MyLocation'; -import LayersIcon from '@material-ui/icons/Layers'; -import PlaylistPlayIcon from '@material-ui/icons/PlaylistPlay'; import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import SearchIcon from '@material-ui/icons/Search'; import MenuIcon from '@material-ui/icons/Menu'; -import MoneyIcon from '@material-ui/icons/MonetizationOn'; import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; import { @@ -48,10 +43,11 @@ import { } from '@backstage/core-components'; import { MyGroupsSidebarItem } from '@backstage/plugin-org'; import { SearchModal } from '../search/SearchModal'; -import Score from '@material-ui/icons/Score'; import { useApp } from '@backstage/core-plugin-api'; import BuildIcon from '@material-ui/icons/Build'; import { NotificationsSidebarItem } from '@backstage/plugin-notifications'; +import UpdateIcon from '@material-ui/icons/Update'; +import CategoryIcon from '@material-ui/icons/Category'; const useSidebarLogoStyles = makeStyles({ root: { @@ -93,7 +89,8 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( }> {/* Global nav, not org-specific */} - + + ) => ( to="docs" text="Docs" /> - - {/* End global nav */} - - - - - - + + } diff --git a/packages/app/src/components/home/CustomizableHomePage.tsx b/packages/app/src/components/home/CustomizableHomePage.tsx index 8998538e54..b592490f2e 100644 --- a/packages/app/src/components/home/CustomizableHomePage.tsx +++ b/packages/app/src/components/home/CustomizableHomePage.tsx @@ -28,6 +28,7 @@ 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 = [ { @@ -81,6 +82,7 @@ export const CustomizableHomePage = () => { + diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index efb30c1d7c..82bff8d26a 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,73 @@ # @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 + +## 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 diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 296630a1f6..67222fc10a 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.2", + "version": "1.2.4-next.2", "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 8487aa29f9..bf35f7b3ab 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,96 @@ # @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 + +## 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 diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts index 14732bbccd..1ef054a537 100644 --- a/packages/backend-defaults/config.d.ts +++ b/packages/backend-defaults/config.d.ts @@ -121,6 +121,16 @@ export interface Config { }; }; + /** + * Options used by the default actions service. + */ + actions?: { + /** + * List of plugin sources to load actions from. + */ + pluginSources?: string[]; + }; + /** * Options used by the default auth, httpAuth and userInfo services. */ diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index b3ff563e17..9ae78c119a 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.3", + "version": "0.11.0-next.2", "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" @@ -20,6 +20,8 @@ "license": "Apache-2.0", "exports": { ".": "./src/index.ts", + "./actions": "./src/entrypoints/actions/index.ts", + "./actionsRegistry": "./src/entrypoints/actionsRegistry/index.ts", "./auditor": "./src/entrypoints/auditor/index.ts", "./auth": "./src/entrypoints/auth/index.ts", "./cache": "./src/entrypoints/cache/index.ts", @@ -45,6 +47,12 @@ "types": "src/index.ts", "typesVersions": { "*": { + "actions": [ + "src/entrypoints/actions/index.ts" + ], + "actionsRegistry": [ + "src/entrypoints/actionsRegistry/index.ts" + ], "auditor": [ "src/entrypoints/auditor/index.ts" ], @@ -188,7 +196,8 @@ "winston-transport": "^4.5.0", "yauzl": "^3.0.0", "yn": "^4.0.0", - "zod": "^3.22.4" + "zod": "^3.22.4", + "zod-to-json-schema": "^3.20.4" }, "devDependencies": { "@aws-sdk/util-stream-node": "^3.350.0", diff --git a/packages/backend-defaults/report-actions.api.md b/packages/backend-defaults/report-actions.api.md new file mode 100644 index 0000000000..4c072af02a --- /dev/null +++ b/packages/backend-defaults/report-actions.api.md @@ -0,0 +1,17 @@ +## API Report File for "@backstage/backend-defaults" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { ActionsService } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; + +// @public (undocumented) +export const actionsServiceFactory: ServiceFactory< + ActionsService, + 'plugin', + 'singleton' +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-defaults/report-actionsRegistry.api.md b/packages/backend-defaults/report-actionsRegistry.api.md new file mode 100644 index 0000000000..d35bf4cef4 --- /dev/null +++ b/packages/backend-defaults/report-actionsRegistry.api.md @@ -0,0 +1,17 @@ +## API Report File for "@backstage/backend-defaults" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { ActionsRegistryService } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; + +// @public (undocumented) +export const actionsRegistryServiceFactory: ServiceFactory< + ActionsRegistryService, + 'plugin', + 'singleton' +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-defaults/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/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index d618c94138..032978c806 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -35,8 +35,12 @@ import { schedulerServiceFactory } from '@backstage/backend-defaults/scheduler'; import { urlReaderServiceFactory } from '@backstage/backend-defaults/urlReader'; import { userInfoServiceFactory } from '@backstage/backend-defaults/userInfo'; import { eventsServiceFactory } from '@backstage/plugin-events-node'; +import { actionsRegistryServiceFactory } from './entrypoints/actionsRegistry'; +import { actionsServiceFactory } from './entrypoints/actions'; export const defaultServiceFactories = [ + actionsRegistryServiceFactory, + actionsServiceFactory, auditorServiceFactory, authServiceFactory, cacheServiceFactory, diff --git a/packages/backend-defaults/src/entrypoints/actions/DefaultActionsService.ts b/packages/backend-defaults/src/entrypoints/actions/DefaultActionsService.ts new file mode 100644 index 0000000000..2421482f4c --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/actions/DefaultActionsService.ts @@ -0,0 +1,139 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + ActionsService, + ActionsServiceAction, + AuthService, + BackstageCredentials, + DiscoveryService, + LoggerService, + RootConfigService, +} from '@backstage/backend-plugin-api'; +import { ResponseError } from '@backstage/errors'; +import { JsonObject } from '@backstage/types'; + +export class DefaultActionsService implements ActionsService { + private constructor( + private readonly discovery: DiscoveryService, + private readonly config: RootConfigService, + private readonly logger: LoggerService, + private readonly auth: AuthService, + ) {} + + static create({ + discovery, + config, + logger, + auth, + }: { + discovery: DiscoveryService; + config: RootConfigService; + logger: LoggerService; + auth: AuthService; + }) { + return new DefaultActionsService(discovery, config, logger, auth); + } + + async list({ credentials }: { credentials: BackstageCredentials }) { + const pluginSources = + this.config.getOptionalStringArray('backend.actions.pluginSources') ?? []; + + const remoteActionsList = await Promise.all( + pluginSources.map(async source => { + try { + const response = await this.makeRequest({ + path: `/.backstage/actions/v1/actions`, + pluginId: source, + credentials, + }); + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + const { actions } = (await response.json()) as { + actions: ActionsServiceAction; + }; + + return actions; + } catch (error) { + this.logger.warn(`Failed to fetch actions from ${source}`, error); + return []; + } + }), + ); + + return { actions: remoteActionsList.flat() }; + } + + async invoke(opts: { + id: string; + input?: JsonObject; + credentials: BackstageCredentials; + }) { + const pluginId = this.pluginIdFromActionId(opts.id); + const response = await this.makeRequest({ + path: `/.backstage/actions/v1/actions/${encodeURIComponent( + opts.id, + )}/invoke`, + pluginId, + credentials: opts.credentials, + options: { + method: 'POST', + body: JSON.stringify(opts.input), + headers: { + 'Content-Type': 'application/json', + }, + }, + }); + + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + + const { output } = await response.json(); + return { output }; + } + + private async makeRequest(opts: { + path: string; + pluginId: string; + options?: RequestInit; + credentials: BackstageCredentials; + }) { + const { path, pluginId, credentials, options } = opts; + const baseUrl = await this.discovery.getBaseUrl(pluginId); + + const { token } = await this.auth.getPluginRequestToken({ + onBehalfOf: credentials, + targetPluginId: opts.pluginId, + }); + + return fetch(`${baseUrl}${path}`, { + ...options, + headers: { + ...options?.headers, + Authorization: `Bearer ${token}`, + }, + }); + } + + private pluginIdFromActionId(id: string): string { + const colonIndex = id.indexOf(':'); + if (colonIndex === -1) { + throw new Error(`Invalid action id: ${id}`); + } + return id.substring(0, colonIndex); + } +} diff --git a/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts new file mode 100644 index 0000000000..c43945f1d5 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts @@ -0,0 +1,344 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + mockCredentials, + mockServices, + registerMswTestHooks, + ServiceFactoryTester, + startTestBackend, +} from '@backstage/backend-test-utils'; +import { actionsRegistryServiceFactory } from '../actionsRegistry'; +import { httpRouterServiceFactory } from '../httpRouter'; +import { actionsServiceFactory } from './actionsServiceFactory'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import { + ActionsServiceAction, + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { json } from 'express'; +import Router from 'express-promise-router'; +import request from 'supertest'; + +const server = setupServer(); + +describe('actionsServiceFactory', () => { + describe('client', () => { + registerMswTestHooks(server); + const mockActionsListEndpoint = jest.fn(); + const mockNotFoundActionsListEndpoint = jest.fn(); + + const defaultServices = [ + mockServices.rootConfig.factory({ + data: { + backend: { + actions: { + pluginSources: ['my-plugin', 'not-found-plugin'], + }, + }, + }, + }), + actionsServiceFactory, + httpRouterServiceFactory, + mockServices.httpAuth.factory({ + defaultCredentials: mockCredentials.service('user:default/mock'), + }), + mockServices.discovery.factory(), + actionsRegistryServiceFactory, + ]; + + const mockActionsDefinition: ActionsServiceAction = { + description: 'my mock description', + id: 'my-plugin:test', + name: 'testy', + title: 'Test', + schema: { + input: {}, + output: {}, + }, + 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: coreServices.actionsRegistry }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'with-validation', + title: 'Test', + description: 'Test', + schema: { + input: z => + z.object({ + name: z.string(), + }), + output: z => + z.object({ + ok: z.boolean(), + string: z.string(), + }), + }, + action: async ({ input }) => { + return { + output: { + ok: true, + string: `hello ${input.name}`, + }, + }; + }, + }); + }, + }); + }, + }), + createBackendPlugin({ + pluginId: 'test-harness', + register({ registerInit }) { + registerInit({ + deps: { + actionsService: coreServices.actions, + router: coreServices.httpRouter, + httpAuth: coreServices.httpAuth, + }, + async init({ actionsService, router, httpAuth }) { + const innerRouter = Router(); + innerRouter.use(json()); + router.use(innerRouter); + + innerRouter.post('/invoke', async (req, res) => { + const { id, input } = req.body; + const response = await actionsService.invoke({ + id, + input, + credentials: await httpAuth.credentials(req), + }); + res.json(response); + }); + + innerRouter.get('/actions', async (req, res) => { + const response = await actionsService.list({ + credentials: await httpAuth.credentials(req), + }); + + res.json(response); + }); + }, + }); + }, + }), + ]; + + it('should list the actions and return the output', async () => { + const { server: testHarnessServer } = await startTestBackend({ + features, + }); + + const { body, status } = await request(testHarnessServer).get( + '/api/test-harness/actions', + ); + + expect(status).toBe(200); + expect(body).toEqual({ + actions: [ + { + description: 'Test', + id: 'plugin-with-action:with-validation', + name: 'with-validation', + schema: { + input: { + $schema: 'http://json-schema.org/draft-07/schema#', + additionalProperties: false, + properties: { + name: { + type: 'string', + }, + }, + required: ['name'], + type: 'object', + }, + output: { + $schema: 'http://json-schema.org/draft-07/schema#', + additionalProperties: false, + properties: { + ok: { + type: 'boolean', + }, + string: { + type: 'string', + }, + }, + required: ['ok', 'string'], + type: 'object', + }, + }, + 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/entrypoints/actions/actionsServiceFactory.ts b/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.ts new file mode 100644 index 0000000000..466cc02121 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createServiceFactory } from '@backstage/backend-plugin-api'; +import { coreServices } from '@backstage/backend-plugin-api'; +import { DefaultActionsService } from './DefaultActionsService'; + +/** + * @public + */ +export const actionsServiceFactory = createServiceFactory({ + service: coreServices.actions, + deps: { + discovery: coreServices.discovery, + config: coreServices.rootConfig, + logger: coreServices.logger, + auth: coreServices.auth, + }, + factory: ({ discovery, config, logger, auth }) => + DefaultActionsService.create({ + discovery, + config, + logger, + auth, + }), +}); diff --git a/plugins/scaffolder-backend/src/lib/templating/index.ts b/packages/backend-defaults/src/entrypoints/actions/index.ts similarity index 83% rename from plugins/scaffolder-backend/src/lib/templating/index.ts rename to packages/backend-defaults/src/entrypoints/actions/index.ts index 1f9e5eb855..bc34150a5f 100644 --- a/plugins/scaffolder-backend/src/lib/templating/index.ts +++ b/packages/backend-defaults/src/entrypoints/actions/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -export type { TemplateFilter, TemplateGlobal } from './SecureTemplater'; +export { actionsServiceFactory } from './actionsServiceFactory'; diff --git a/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts b/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts new file mode 100644 index 0000000000..bee3f7d565 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts @@ -0,0 +1,164 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ActionsRegistryActionOptions, + ActionsRegistryService, + AuthService, + HttpAuthService, + LoggerService, + PluginMetadataService, +} from '@backstage/backend-plugin-api'; +import PromiseRouter from 'express-promise-router'; +import { Router, json } from 'express'; +import { z, AnyZodObject } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; +import { + ForwardedError, + InputError, + NotAllowedError, + NotFoundError, +} from '@backstage/errors'; + +export class DefaultActionsRegistryService implements ActionsRegistryService { + private actions: Map> = + new Map(); + + private constructor( + private readonly logger: LoggerService, + private readonly httpAuth: HttpAuthService, + private readonly auth: AuthService, + private readonly metadata: PluginMetadataService, + ) {} + + static create({ + httpAuth, + logger, + auth, + metadata, + }: { + httpAuth: HttpAuthService; + logger: LoggerService; + auth: AuthService; + metadata: PluginMetadataService; + }): DefaultActionsRegistryService { + return new DefaultActionsRegistryService(logger, httpAuth, auth, metadata); + } + + createRouter(): Router { + const router = PromiseRouter(); + router.use(json()); + + router.get('/.backstage/actions/v1/actions', (_, res) => { + return res.json({ + actions: Array.from(this.actions.entries()).map(([id, action]) => ({ + id, + ...action, + 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/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts new file mode 100644 index 0000000000..2de6272365 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts @@ -0,0 +1,540 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { + mockCredentials, + mockServices, + startTestBackend, +} from '@backstage/backend-test-utils'; +import { httpRouterServiceFactory } from '../httpRouter'; +import request from 'supertest'; +import { actionsRegistryServiceFactory } from './actionsRegistryServiceFactory'; +import { InputError } from '@backstage/errors'; + +describe('actionsRegistryServiceFactory', () => { + const defaultServices = [ + actionsRegistryServiceFactory, + httpRouterServiceFactory, + mockServices.httpAuth.factory({ + defaultCredentials: mockCredentials.service('user:default/mock'), + }), + ]; + + describe('typescript tests', () => { + it('should properly infer the input types', () => { + createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { + actionsRegistry: coreServices.actionsRegistry, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => + z.object({ + test: z.string(), + }), + output: z => + z.object({ + ok: z.boolean(), + }), + }, + action: async ({ input: { test } }) => { + // @ts-expect-error - test is not a boolean + const _t: boolean = test; + return { output: { ok: true } }; + }, + }); + }, + }); + }, + }); + + expect(true).toBe(true); + }); + + it('should properly infer the output types', () => { + createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { + actionsRegistry: coreServices.actionsRegistry, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => + z.object({ + test: z.string(), + }), + output: z => + z.object({ + ok: z.boolean(), + }), + }, + // @ts-expect-error - ok is not a boolean + action: async () => { + return { output: { ok: 'bo' } }; + }, + }); + }, + }); + }, + }); + + expect(true).toBe(true); + }); + }); + + describe('/.backstage/actions/v1/actions', () => { + it('should allow registering of actions', async () => { + const pluginSubject = createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { + actionsRegistry: coreServices.actionsRegistry, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => + z.object({ + name: z.string(), + }), + output: z => + z.object({ + ok: z.boolean(), + }), + }, + action: async () => ({ output: { ok: true } }), + }); + }, + }); + }, + }); + + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + const { body, status } = await request(server).get( + '/api/my-plugin/.backstage/actions/v1/actions', + ); + + expect(status).toBe(200); + + expect(body).toMatchObject({ + actions: [ + { + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: { + type: 'object', + properties: { + name: { + type: 'string', + }, + }, + }, + output: { + type: 'object', + properties: { + ok: { + type: 'boolean', + }, + }, + }, + }, + }, + ], + }); + }); + + it('should set default attributes', async () => { + const pluginSubject = createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { + actionsRegistry: coreServices.actionsRegistry, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({}), + 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: coreServices.actionsRegistry, + }, + 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: coreServices.actionsRegistry, + }, + 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: coreServices.actionsRegistry, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => + z.object({ + name: z.string(), + }), + output: z => + z.object({ + ok: z.boolean(), + }), + }, + action: mockAction, + }); + }, + }); + }, + }); + + it('should throw an error if the action is not found', async () => { + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + const { body, status } = await request(server).post( + '/api/my-plugin/.backstage/actions/v1/actions/test/invoke', + ); + + expect(status).toBe(404); + expect(body).toMatchObject({ + error: { + message: 'Action "test" not found', + }, + }); + }); + + it('should throw an error if the action input does not match the schema', async () => { + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + const { body, status } = await request(server) + .post( + '/api/my-plugin/.backstage/actions/v1/actions/my-plugin:test/invoke', + ) + .send({ + name: 123, + }); + + expect(status).toBe(400); + expect(body).toMatchObject({ + error: { + message: expect.stringMatching( + 'Invalid input to action "my-plugin:test"', + ), + }, + }); + }); + + it('should call the action with the input', async () => { + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + await request(server) + .post( + '/api/my-plugin/.backstage/actions/v1/actions/my-plugin:test/invoke', + ) + .send({ + name: 'test', + }); + + expect(mockAction).toHaveBeenCalledWith({ + input: { + name: 'test', + }, + credentials: { + $$type: '@backstage/BackstageCredentials', + principal: { + type: 'service', + subject: 'user:default/mock', + }, + }, + logger: expect.anything(), + }); + }); + + it('should throw an error if the action is invoked by a user', async () => { + const testServices = [ + actionsRegistryServiceFactory, + httpRouterServiceFactory, + mockServices.httpAuth.factory({ + defaultCredentials: mockCredentials.user(), + }), + ]; + + const { server } = await startTestBackend({ + features: [pluginSubject, ...testServices], + }); + + const { body, status } = await request(server) + .post( + '/api/my-plugin/.backstage/actions/v1/actions/my-plugin:test/invoke', + ) + .send({ + name: 'test', + }); + + expect(status).toBe(403); + expect(body).toMatchObject({ + error: { + message: 'Actions must be invoked by a service, not a user', + }, + }); + }); + + it('should validate the output of the action if provided', async () => { + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + mockAction.mockResolvedValue({ ok: 'blob' }); + + const { body, status } = await request(server) + .post( + '/api/my-plugin/.backstage/actions/v1/actions/my-plugin:test/invoke', + ) + .send({ + name: 'test', + }); + + expect(status).toBe(400); + expect(body).toMatchObject({ + error: { + message: expect.stringMatching( + 'Invalid output from action "my-plugin:test"', + ), + }, + }); + }); + + it('should return the output of the action', async () => { + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + const { body, status } = await request(server) + .post( + '/api/my-plugin/.backstage/actions/v1/actions/my-plugin:test/invoke', + ) + .send({ + name: 'test', + }); + + expect(status).toBe(200); + expect(body).toMatchObject({ output: { ok: true } }); + }); + + it('should return the error from the action if it throws', async () => { + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + mockAction.mockRejectedValue(new InputError('test')); + + const { body, status } = await request(server) + .post( + '/api/my-plugin/.backstage/actions/v1/actions/my-plugin:test/invoke', + ) + .send({ + name: 'test', + }); + + expect(status).toBe(400); + expect(body).toMatchObject({ + error: { + message: expect.stringContaining( + 'Failed execution of action "my-plugin:test"', + ), + }, + }); + }); + }); +}); diff --git a/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts b/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts new file mode 100644 index 0000000000..85515a6046 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts @@ -0,0 +1,47 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { DefaultActionsRegistryService } from './DefaultActionsRegistryService'; + +/** + * @public + */ +export const actionsRegistryServiceFactory = createServiceFactory({ + service: coreServices.actionsRegistry, + deps: { + metadata: coreServices.pluginMetadata, + httpRouter: coreServices.httpRouter, + httpAuth: coreServices.httpAuth, + logger: coreServices.logger, + auth: coreServices.auth, + }, + factory: ({ metadata, httpRouter, httpAuth, logger, auth }) => { + const actionsRegistryService = DefaultActionsRegistryService.create({ + httpAuth, + logger, + auth, + metadata, + }); + + httpRouter.use(actionsRegistryService.createRouter()); + + return actionsRegistryService; + }, +}); diff --git a/packages/backend-defaults/src/entrypoints/actionsRegistry/index.ts b/packages/backend-defaults/src/entrypoints/actionsRegistry/index.ts new file mode 100644 index 0000000000..ac65dd7a90 --- /dev/null +++ b/packages/backend-defaults/src/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/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/packages/backend-defaults/src/entrypoints/auditor/types.ts b/packages/backend-defaults/src/entrypoints/auditor/types.ts new file mode 100644 index 0000000000..7827a31d1f --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/auditor/types.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. + */ + +import { z } from 'zod'; + +/** @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-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index bf49582494..9dd24cf091 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,105 @@ # @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 + +## 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 diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index aec40551a4..b5ddeca71d 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.3", + "version": "0.7.1-next.2", "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-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md index 31b3497a33..bfdc32df2e 100644 --- a/packages/backend-openapi-utils/CHANGELOG.md +++ b/packages/backend-openapi-utils/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/backend-openapi-utils +## 0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## 0.5.3 + +### Patch Changes + +- 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 diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index 6dc909925a..d4b17a4e61 100644 --- a/packages/backend-openapi-utils/package.json +++ b/packages/backend-openapi-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-openapi-utils", - "version": "0.5.3-next.2", + "version": "0.5.4-next.1", "description": "OpenAPI typescript support.", "backstage": { "role": "node-library" diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index d627deabf6..3aea27d1de 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/backend-plugin-api +## 1.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## 1.4.0-next.0 + +### Minor Changes + +- 664c07a: Added `coreServices.actionsRegistry` and `coreServices.actions` to allow registration of distributed actions from plugins, and the ability to invoke these actions + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + +## 1.3.1 + +### Patch Changes + +- 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 diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 2c56ac85bf..52c1c6a66a 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-plugin-api", - "version": "1.3.1-next.2", + "version": "1.4.0-next.1", "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" @@ -61,9 +61,12 @@ "@backstage/plugin-permission-node": "workspace:^", "@backstage/types": "workspace:^", "@types/express": "^4.17.6", + "@types/json-schema": "^7.0.6", "@types/luxon": "^3.0.0", + "json-schema": "^0.4.0", "knex": "^3.0.0", - "luxon": "^3.0.0" + "luxon": "^3.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/packages/backend-plugin-api/report.api.md b/packages/backend-plugin-api/report.api.md index 52843dec96..d5030adb9c 100644 --- a/packages/backend-plugin-api/report.api.md +++ b/packages/backend-plugin-api/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 { AnyZodObject } from 'zod'; import { AuthorizePermissionRequest } from '@backstage/plugin-permission-common'; import { AuthorizePermissionResponse } from '@backstage/plugin-permission-common'; import { Config } from '@backstage/config'; @@ -12,6 +13,7 @@ import type { Handler } from 'express'; import { HumanDuration } from '@backstage/types'; import { isChildPath } from '@backstage/cli-common'; import { JsonObject } from '@backstage/types'; +import { JSONSchema7 } from 'json-schema'; import { JsonValue } from '@backstage/types'; import { Knex } from 'knex'; import { Permission } from '@backstage/plugin-permission-common'; @@ -25,6 +27,84 @@ import { QueryPermissionResponse } from '@backstage/plugin-permission-common'; import { Readable } from 'stream'; import type { Request as Request_2 } from 'express'; import type { Response as Response_2 } from 'express'; +import { z } from 'zod'; + +// @public (undocumented) +export type ActionsRegistryActionContext = { + input: z.infer; + logger: LoggerService; + credentials: BackstageCredentials; +}; + +// @public (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; + } + >; +}; + +// @public (undocumented) +export interface ActionsRegistryService { + // (undocumented) + register< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, + >( + options: ActionsRegistryActionOptions, + ): void; +} + +// @public (undocumented) +export interface ActionsService { + // (undocumented) + invoke(opts: { + id: string; + input?: JsonObject; + credentials: BackstageCredentials; + }): Promise<{ + output: JsonValue; + }>; + // (undocumented) + list: (opts: { credentials: BackstageCredentials }) => Promise<{ + actions: ActionsServiceAction[]; + }>; +} + +// @public (undocumented) +export type ActionsServiceAction = { + id: string; + name: string; + title: string; + description: string; + schema: { + input: JSONSchema7; + output: JSONSchema7; + }; + attributes: { + readOnly: boolean; + destructive: boolean; + idempotent: boolean; + }; +}; // @public export interface AuditorService { @@ -205,6 +285,12 @@ export type CacheServiceSetOptions = { // @public export namespace coreServices { const auth: ServiceRef; + const actions: ServiceRef; + const actionsRegistry: ServiceRef< + ActionsRegistryService, + 'plugin', + 'singleton' + >; const userInfo: ServiceRef; const cache: ServiceRef; const rootConfig: ServiceRef; diff --git a/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts b/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts new file mode 100644 index 0000000000..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/coreServices.ts b/packages/backend-plugin-api/src/services/definitions/coreServices.ts index 8c8c6c0f82..c5282368ac 100644 --- a/packages/backend-plugin-api/src/services/definitions/coreServices.ts +++ b/packages/backend-plugin-api/src/services/definitions/coreServices.ts @@ -35,6 +35,36 @@ export namespace coreServices { id: 'core.auth', }); + /** + * Service for calling distributed actions + * + * See {@link ActionsService} + * and {@link https://backstage.io/docs/backend-system/core-services/actions | the service docs} + * for more information. + * + * @public + */ + export const actions = createServiceRef< + import('./ActionsService').ActionsService + >({ + id: 'core.actions', + }); + + /** + * Service for registering and managing distributed actions. + * + * See {@link ActionsRegistryService} + * and {@link https://backstage.io/docs/backend-system/core-services/actions-registry | the service docs} + * for more information. + * + * @public + */ + export const actionsRegistry = createServiceRef< + import('./ActionsRegistryService').ActionsRegistryService + >({ + id: 'core.actionsRegistry', + }); + /** * Authenticated user information retrieval. * diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index d6346cbe4e..a01544b60f 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -14,6 +14,13 @@ * limitations under the License. */ +export type { + ActionsRegistryService, + ActionsRegistryActionOptions, + ActionsRegistryActionContext, +} from './ActionsRegistryService'; + +export type { ActionsService, ActionsServiceAction } from './ActionsService'; export type { AuditorService, AuditorServiceCreateEventOptions, diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 33e7ce200e..62aacfee99 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,68 @@ # @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 + +## 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 diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 89fa65b09c..d58720723d 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.3", + "version": "1.6.0-next.2", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" @@ -52,6 +52,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", diff --git a/packages/backend-test-utils/report.api.md b/packages/backend-test-utils/report.api.md index 7edcf6e1b7..c74d975920 100644 --- a/packages/backend-test-utils/report.api.md +++ b/packages/backend-test-utils/report.api.md @@ -3,7 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { ActionsRegistryService } from '@backstage/backend-plugin-api'; +import { ActionsService } from '@backstage/backend-plugin-api'; import { AuditorService } from '@backstage/backend-plugin-api'; +import { 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'; @@ -161,6 +164,28 @@ export function mockErrorHandler(): ErrorRequestHandler< // @public export namespace mockServices { + // (undocumented) + export namespace actions { + const // (undocumented) + factory: () => ServiceFactory; + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ServiceMock; + } + // (undocumented) + export namespace actionsRegistry { + const // (undocumented) + factory: () => ServiceFactory< + ActionsRegistryService, + 'plugin', + 'singleton' + >; + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ServiceMock; + } // (undocumented) export namespace auditor { const // (undocumented) @@ -275,14 +300,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/services/MockPermissionsService.test.ts b/packages/backend-test-utils/src/next/services/MockPermissionsService.test.ts new file mode 100644 index 0000000000..eb98fe9457 --- /dev/null +++ b/packages/backend-test-utils/src/next/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/next/services/MockPermissionsService.ts b/packages/backend-test-utils/src/next/services/MockPermissionsService.ts new file mode 100644 index 0000000000..3637c34554 --- /dev/null +++ b/packages/backend-test-utils/src/next/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/mockCredentials.ts b/packages/backend-test-utils/src/next/services/mockCredentials.ts index 231c108ac4..617ffdb240 100644 --- a/packages/backend-test-utils/src/next/services/mockCredentials.ts +++ b/packages/backend-test-utils/src/next/services/mockCredentials.ts @@ -111,16 +111,24 @@ export namespace mockCredentials { options?: { actor?: { subject: string } }, ): BackstageCredentials { validateUserEntityRef(userEntityRef); - return { - $$type: '@backstage/BackstageCredentials', - principal: { - type: 'user', - userEntityRef, - ...(options?.actor && { - actor: { type: 'service', subject: options.actor.subject }, - }), + return Object.defineProperty( + { + $$type: '@backstage/BackstageCredentials', + principal: { + type: 'user', + userEntityRef, + ...(options?.actor && { + actor: { type: 'service', subject: options.actor.subject }, + }), + }, }, - }; + 'token', + { + enumerable: false, + configurable: true, + value: user.token(), + }, + ); } /** diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts index f68f1337b9..95f8b084c9 100644 --- a/packages/backend-test-utils/src/next/services/mockServices.ts +++ b/packages/backend-test-utils/src/next/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,9 @@ import { MockRootLoggerService } from './MockRootLoggerService'; import { MockUserInfoService } from './MockUserInfoService'; import { mockCredentials } from './mockCredentials'; import { MockEventsService } from './MockEventsService'; +import { actionsServiceFactory } from '@backstage/backend-defaults/actions'; +import { actionsRegistryServiceFactory } from '@backstage/backend-defaults/actionsRegistry'; +import { MockPermissionsService } from './MockPermissionsService'; /** @internal */ function createLoggerMock() { @@ -473,8 +478,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(), @@ -518,6 +552,20 @@ export namespace mockServices { search: jest.fn(), })); } + export namespace actions { + export const factory = () => actionsServiceFactory; + export const mock = simpleMock(coreServices.actions, () => ({ + list: jest.fn(), + invoke: jest.fn(), + })); + } + + export namespace actionsRegistry { + export const factory = () => actionsRegistryServiceFactory; + export const mock = simpleMock(coreServices.actionsRegistry, () => ({ + register: jest.fn(), + })); + } /** * Creates a functional mock implementation of the diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index cc5809695e..9ed058ea7d 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,159 @@ # 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 + +## 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 diff --git a/packages/backend/package.json b/packages/backend/package.json index c6675f7e38..66cf7dccfc 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.38-next.3", + "version": "0.0.39-next.2", "backstage": { "role": "backend" }, diff --git a/packages/canon/CHANGELOG.md b/packages/canon/CHANGELOG.md index dea67aa06c..519a21a72b 100644 --- a/packages/canon/CHANGELOG.md +++ b/packages/canon/CHANGELOG.md @@ -1,5 +1,52 @@ # @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. + +## 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: + ), }; @@ -129,6 +131,18 @@ export const Disabled: Story = { ), }; +export const AsLink: Story = { + args: { + children: 'I am a link', + }, + render: args => ( + - ); + const { renderElement } = useRender({ + render, + props: { + className: clsx('canon-Button', className), + ['data-variant']: responsiveVariant, + ['data-size']: responsiveSize, + ...rest, + children: ( + <> + {iconStart && ( + + )} + {children} + {iconEnd && ( + + )} + + ), + }, + refs: [ref, internalRef], + }); + + return renderElement(); }, ); diff --git a/packages/canon/src/components/Button/types.ts b/packages/canon/src/components/Button/types.ts index 4ab62d5683..e91538299e 100644 --- a/packages/canon/src/components/Button/types.ts +++ b/packages/canon/src/components/Button/types.ts @@ -16,14 +16,14 @@ import type { ButtonOwnProps } from './Button.props'; import { ReactElement } from 'react'; +import type { useRender } from '@base-ui-components/react/use-render'; /** * Properties for {@link Button} * * @public */ -export interface ButtonProps - extends Omit, 'children'> { +export interface ButtonProps extends useRender.ComponentProps<'button'> { /** * The size of the button * @defaultValue 'medium' @@ -36,11 +36,6 @@ export interface ButtonProps */ variant?: ButtonOwnProps['variant']; - /** - * The content of the button - */ - children: React.ReactNode; - /** * Optional icon to display at the start of the button */ diff --git a/packages/canon/src/components/Collapsible/Collapsible.stories.tsx b/packages/canon/src/components/Collapsible/Collapsible.stories.tsx index 175dadba85..8e6a394672 100644 --- a/packages/canon/src/components/Collapsible/Collapsible.stories.tsx +++ b/packages/canon/src/components/Collapsible/Collapsible.stories.tsx @@ -15,7 +15,7 @@ */ import type { Meta, StoryObj } from '@storybook/react'; -import { Collapsible } from './'; +import { Collapsible } from './Collapsible'; import { Button } from '../Button'; import { Box } from '../Box'; import { Text } from '../Text'; diff --git a/packages/canon/src/components/Container/Container.stories.tsx b/packages/canon/src/components/Container/Container.stories.tsx index 5e434b7eb0..ca1165f44c 100644 --- a/packages/canon/src/components/Container/Container.stories.tsx +++ b/packages/canon/src/components/Container/Container.stories.tsx @@ -21,6 +21,9 @@ import { Container } from './Container'; const meta = { title: 'Layout/Container', component: Container, + parameters: { + layout: 'fullscreen', + }, argTypes: { children: { control: false, @@ -48,14 +51,9 @@ const DecorativeBox = () => ( ); export const Default: Story = { - parameters: { - layout: 'fullscreen', + args: { + children: , }, - render: args => ( - - - - ), }; export const Preview: Story = { @@ -65,3 +63,45 @@ export const Preview: Story = {
), }; + +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/canon/src/components/Container/Container.tsx index 46a5356d56..ebdc17ac85 100644 --- a/packages/canon/src/components/Container/Container.tsx +++ b/packages/canon/src/components/Container/Container.tsx @@ -19,14 +19,26 @@ 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'; /** @public */ export const Container = forwardRef( (props, ref) => { const { children } = props; + // 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); diff --git a/packages/canon/src/components/Container/styles.css b/packages/canon/src/components/Container/styles.css index 1ebf63987f..ae2bad2c29 100644 --- a/packages/canon/src/components/Container/styles.css +++ b/packages/canon/src/components/Container/styles.css @@ -1,18 +1,18 @@ .canon-Container { max-width: 120rem; - padding: 0 var(--canon-space-4); - margin: 0 auto; + padding-inline: var(--canon-space-4); + margin-inline: auto; transition: padding 0.2s ease-in-out; } @media (min-width: 640px) { .canon-Container { - padding: 0 var(--canon-space-8); + padding-inline: var(--canon-space-8); } } @media (min-width: 1024px) { .canon-Container { - padding: 0 var(--canon-space-12); + padding-inline: var(--canon-space-12); } } diff --git a/packages/canon/src/components/Flex/Flex.stories.tsx b/packages/canon/src/components/Flex/Flex.stories.tsx index bbb5e84692..30c527a117 100644 --- a/packages/canon/src/components/Flex/Flex.stories.tsx +++ b/packages/canon/src/components/Flex/Flex.stories.tsx @@ -16,6 +16,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { Flex } from './Flex'; +import { Text } from '../Text'; const meta = { title: 'Layout/Flex', @@ -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/canon/src/components/Flex/Flex.tsx index 7c84ed08f8..a53e0d28f4 100644 --- a/packages/canon/src/components/Flex/Flex.tsx +++ b/packages/canon/src/components/Flex/Flex.tsx @@ -20,12 +20,14 @@ 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'; /** @public */ export const Flex = forwardRef((props, ref) => { const propDefs = { ...gapPropDefs, ...flexPropDefs, + ...spacingPropDefs, }; const { className, style } = extractProps(props, propDefs); diff --git a/packages/canon/src/components/Flex/styles.css b/packages/canon/src/components/Flex/styles.css index 094edffca7..61fb53d257 100644 --- a/packages/canon/src/components/Flex/styles.css +++ b/packages/canon/src/components/Flex/styles.css @@ -16,4 +16,7 @@ .canon-Flex { display: flex; + + /* This helps when using `truncate` on text inside a flex container */ + min-width: 0; } diff --git a/packages/canon/src/components/Grid/Grid.stories.tsx b/packages/canon/src/components/Grid/Grid.stories.tsx index 233d66bae7..2482afbaca 100644 --- a/packages/canon/src/components/Grid/Grid.stories.tsx +++ b/packages/canon/src/components/Grid/Grid.stories.tsx @@ -22,18 +22,7 @@ import { Flex } from '../Flex'; const meta = { title: 'Layout/Grid', - component: Grid, - argTypes: { - children: { - control: false, - }, - className: { - control: 'text', - }, - }, - args: { - gap: '4', - }, + 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/canon/src/components/Grid/Grid.tsx index aedce886ed..7bb5486a2d 100644 --- a/packages/canon/src/components/Grid/Grid.tsx +++ b/packages/canon/src/components/Grid/Grid.tsx @@ -20,11 +20,13 @@ 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'; -const GridBase = forwardRef((props, ref) => { +const GridRoot = forwardRef((props, ref) => { const propDefs = { ...gapPropDefs, ...gridPropDefs, + ...spacingPropDefs, }; const { className, style } = extractProps(props, propDefs); @@ -53,4 +55,7 @@ const GridItem = forwardRef((props, ref) => { }); /** @public */ -export const Grid = Object.assign(GridBase, { Item: GridItem }); +export const Grid = { + Root: GridRoot, + Item: GridItem, +}; diff --git a/packages/canon/src/components/Heading/Heading.stories.tsx b/packages/canon/src/components/Heading/Heading.stories.tsx index 1fbc882d5d..956118ddeb 100644 --- a/packages/canon/src/components/Heading/Heading.stories.tsx +++ b/packages/canon/src/components/Heading/Heading.stories.tsx @@ -46,10 +46,31 @@ export const AllVariants: Story = { Title 2 Title 3 Title 4 + Title 5 ), }; +export const AllColors: Story = { + args: { + ...Default.args, + }, + render: args => ( + + + + + ), +}; + +export const Truncate: Story = { + args: { + ...Title1.args, + truncate: true, + style: { maxWidth: '400px' }, + }, +}; + export const Responsive: Story = { args: { variant: { @@ -59,13 +80,6 @@ export const Responsive: Story = { }, }; -export const CustomTag: Story = { - args: { - variant: 'title5', - as: 'h2', - }, -}; - export const WrappedInLink: Story = { args: { ...Default.args, @@ -79,6 +93,13 @@ export const WrappedInLink: Story = { ], }; +export const CustomRender: Story = { + args: { + ...Default.args, + render:

, + }, +}; + export const Playground: Story = { render: () => ( diff --git a/packages/canon/src/components/Heading/Heading.tsx b/packages/canon/src/components/Heading/Heading.tsx index 994b1de06e..6ea2fe006b 100644 --- a/packages/canon/src/components/Heading/Heading.tsx +++ b/packages/canon/src/components/Heading/Heading.tsx @@ -14,44 +14,41 @@ * limitations under the License. */ -import { forwardRef } from 'react'; +import { forwardRef, useRef } from 'react'; import clsx from 'clsx'; import { useResponsiveValue } from '../../hooks/useResponsiveValue'; - +import { useRender } from '@base-ui-components/react/use-render'; import type { HeadingProps } from './types'; /** @public */ export const Heading = forwardRef( (props, ref) => { const { - children, variant = 'title1', - as = 'h1', + color = 'primary', + truncate, className, + render =

, ...restProps } = props; - // Get the responsive value for the variant const responsiveVariant = useResponsiveValue(variant); + const responsiveColor = useResponsiveValue(color); + const internalRef = useRef(null); - // Determine the component to render based on the variant - let Component = as; - if (variant === 'title2') Component = 'h2'; - if (variant === 'title3') Component = 'h3'; - if (variant === 'title4') Component = 'h4'; - if (variant === 'title5') Component = 'h5'; - if (as) Component = as; + const { renderElement } = useRender({ + render, + props: { + className: clsx('canon-Heading', className), + ['data-variant']: responsiveVariant, + ['data-color']: responsiveColor, + ['data-truncate']: truncate, + ...restProps, + }, + refs: [ref, internalRef], + }); - return ( - - {children} - - ); + return renderElement(); }, ); diff --git a/packages/canon/src/components/Heading/styles.css b/packages/canon/src/components/Heading/styles.css index c85bbca135..ce3da94f7f 100644 --- a/packages/canon/src/components/Heading/styles.css +++ b/packages/canon/src/components/Heading/styles.css @@ -43,7 +43,7 @@ } .canon-Heading[data-variant='title4'] { - font-size: var(--canon-font-size-title4); + font-size: var(--canon-font-size-6); font-weight: var(--canon-font-weight-bold); } @@ -51,3 +51,17 @@ font-size: var(--canon-font-size-5); font-weight: var(--canon-font-weight-bold); } + +.canon-Heading[data-color='primary'] { + color: var(--canon-fg-primary); +} + +.canon-Heading[data-color='secondary'] { + color: var(--canon-fg-secondary); +} + +.canon-Heading[data-truncate] { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/packages/canon/src/components/Heading/types.ts b/packages/canon/src/components/Heading/types.ts index 6250e8d2eb..7a94bdf2a4 100644 --- a/packages/canon/src/components/Heading/types.ts +++ b/packages/canon/src/components/Heading/types.ts @@ -15,10 +15,11 @@ */ import { Breakpoint } from '../../types'; +import type { useRender } from '@base-ui-components/react/use-render'; /** @public */ -export interface HeadingProps { - children: React.ReactNode; +export interface HeadingProps + extends Omit, 'color'> { variant?: | 'display' | 'title1' @@ -32,7 +33,11 @@ export interface HeadingProps { 'display' | 'title1' | 'title2' | 'title3' | 'title4' | 'title5' > >; - as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; + color?: + | 'primary' + | 'secondary' + | Partial>; + truncate?: boolean; className?: string; style?: React.CSSProperties; } diff --git a/packages/canon/src/components/IconButton/IconButton.stories.tsx b/packages/canon/src/components/IconButton/IconButton.stories.tsx index d16dcd1fcb..7a7272ad05 100644 --- a/packages/canon/src/components/IconButton/IconButton.stories.tsx +++ b/packages/canon/src/components/IconButton/IconButton.stories.tsx @@ -34,29 +34,23 @@ const meta = { options: ['primary', 'secondary'], }, }, - args: { - size: 'medium', - variant: 'primary', - }, } satisfies Meta; export default meta; type Story = StoryObj; -export const Variants: Story = { +export const Default: Story = { args: { icon: , - 'aria-label': 'Cloud icon button', }, - parameters: { - argTypes: { - variant: { - control: false, - }, - }, +}; + +export const Variants: Story = { + args: { + ...Default.args, }, render: args => ( - + @@ -66,12 +60,11 @@ export const Variants: Story = { export const Sizes: Story = { args: { icon: , - 'aria-label': 'Cloud icon button', }, render: args => ( - - + + ), }; @@ -83,7 +76,7 @@ export const Disabled: Story = { 'aria-label': 'Cloud icon button', }, render: args => ( - + diff --git a/packages/canon/src/components/IconButton/IconButton.tsx b/packages/canon/src/components/IconButton/IconButton.tsx index bca012cac0..fd80c91ddd 100644 --- a/packages/canon/src/components/IconButton/IconButton.tsx +++ b/packages/canon/src/components/IconButton/IconButton.tsx @@ -24,7 +24,7 @@ import type { IconButtonProps } from './types'; export const IconButton = forwardRef( (props: IconButtonProps, ref) => { const { - size = 'medium', + size = 'small', variant = 'primary', icon, className, diff --git a/packages/canon/src/components/Menu/Combobox.tsx b/packages/canon/src/components/Menu/Combobox.tsx new file mode 100644 index 0000000000..c9593ed2a4 --- /dev/null +++ b/packages/canon/src/components/Menu/Combobox.tsx @@ -0,0 +1,245 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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, + useId, + ChangeEvent, + KeyboardEvent, + useRef, + useEffect, +} from 'react'; +import clsx from 'clsx'; +import { MenuComboboxOption, MenuComboboxProps } from './types'; +import { Icon } from '@backstage/canon'; + +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/canon/src/components/Menu/Menu.stories.tsx b/packages/canon/src/components/Menu/Menu.stories.tsx index 5d04a2f19c..578ce945ae 100644 --- a/packages/canon/src/components/Menu/Menu.stories.tsx +++ b/packages/canon/src/components/Menu/Menu.stories.tsx @@ -16,8 +16,8 @@ import type { Meta, StoryObj } from '@storybook/react'; import { Menu } from './Menu'; -import { Button } from '../Button'; -import { Icon } from '../Icon'; +import { Text, Icon, Button, Flex } from '../../index'; +import { useState } from 'react'; const meta = { title: 'Components/Menu', @@ -27,6 +27,18 @@ const 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: ( @@ -35,7 +47,7 @@ export const Default: Story = { render={props => ( + )} + /> + + + + 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/canon/src/components/Menu/Menu.styles.css b/packages/canon/src/components/Menu/Menu.styles.css index 403d34294e..7ccaa7a634 100644 --- a/packages/canon/src/components/Menu/Menu.styles.css +++ b/packages/canon/src/components/Menu/Menu.styles.css @@ -3,16 +3,20 @@ } .canon-MenuPopup { - padding: var(--canon-space-2); + display: flex; + flex-direction: column; border-radius: 0.375rem; background-color: var(--canon-bg-surface-1); border: 1px solid var(--canon-border); color: var(--canon-fg-primary); outline: none; - overflow: hidden; + overflow: auto; transform-origin: var(--transform-origin); - max-width: 340px; + max-width: min(var(--available-width), 340px); + max-height: min(var(--available-height), 500px); transition: transform 150ms, opacity 150ms; + position: relative; + padding-bottom: var(--canon-space-1); &[data-starting-style], &[data-ending-style] { @@ -23,25 +27,61 @@ .canon-MenuItem { outline: 0; - cursor: default; user-select: none; display: flex; + height: 32px; + align-items: center; gap: var(--canon-space-2); color: var(--canon-fg-primary); text-decoration: none; border-radius: var(--canon-radius-2); - padding: var(--canon-space-2) var(--canon-space-2); - padding-right: var(--canon-space-4); + margin-inline: var(--canon-space-1); + padding-inline: var(--canon-space-2); + font-size: var(--canon-font-size-3); cursor: pointer; + flex-shrink: 0; - &:last-child { - border-bottom: none; + &:first-child { + margin-top: var(--canon-space-1); } &[data-highlighted] { - z-index: 0; - position: relative; - background-color: var(--canon-bg-tint-hover); + background-color: var(--canon-gray-3); + } +} + +.canon-MenuSubmenuTrigger { + outline: 0; + user-select: none; + display: flex; + height: 32px; + align-items: center; + justify-content: space-between; + gap: var(--canon-space-2); + color: var(--canon-fg-primary); + text-decoration: none; + border-radius: var(--canon-radius-2); + margin-inline: var(--canon-space-1); + padding-inline: var(--canon-space-2); + font-size: var(--canon-font-size-3); + cursor: pointer; + flex-shrink: 0; + + & .canon-Icon { + color: var(--canon-fg-secondary); + } + + &:first-child { + margin-top: var(--canon-space-1); + } + + &[data-popup-open], + &[data-highlighted] { + background-color: var(--canon-gray-3); + + .canon-Icon { + color: var(--canon-fg-primary); + } } } @@ -50,3 +90,92 @@ height: 1px; background-color: var(--color-gray-200); } + +.canon-SubmenuComboboxSearch { + padding-inline: var(--canon-space-3); + width: 100%; + height: 32px; + border: none; + border-bottom: 1px solid var(--canon-border); + background-color: var(--canon-bg-surface-1); + color: var(--canon-fg-primary); + line-height: 140%; + outline: none; + font-size: var(--canon-font-size-3); + position: sticky; + top: 0; + z-index: 1; + + &::placeholder { + color: var(--canon-fg-secondary); + } + + &:disabled { + opacity: 0.6; + cursor: not-allowed; + } +} + +.canon-SubmenuComboboxItems { + overflow-y: auto; + display: flex; + flex-direction: column; + padding-top: var(--canon-space-2); + outline: none; +} + +.canon-SubmenuComboboxNoResults { + padding-inline: var(--canon-space-3); + padding-top: var(--canon-space-2); + padding-bottom: var(--canon-space-4); + color: var(--canon-fg-secondary); + font-size: var(--canon-font-size-3); +} + +.canon-SubmenuComboboxItem { + outline: 0; + user-select: none; + display: flex; + height: 32px; + align-items: center; + justify-content: space-between; + gap: var(--canon-space-2); + color: var(--canon-fg-primary); + text-decoration: none; + border-radius: var(--canon-radius-2); + margin-inline: var(--canon-space-1); + padding-inline: var(--canon-space-2); + font-size: var(--canon-font-size-3); + cursor: pointer; + user-select: none; + flex-shrink: 0; + + &[data-highlighted] { + background-color: var(--canon-gray-3); + } + + &[data-disabled] { + opacity: 0.5; + cursor: not-allowed; + } +} + +.canon-SubmenuComboboxItemCheckbox { + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + color: var(--canon-fg-primary); + flex-shrink: 0; + border-radius: var(--canon-radius-2); + border: 1px solid var(--canon-border); + background: var(--canon-bg-surface-1); +} + +.canon-SubmenuComboboxItemLabel { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/packages/canon/src/components/Menu/Menu.tsx b/packages/canon/src/components/Menu/Menu.tsx index 6b93d14865..123b01b283 100644 --- a/packages/canon/src/components/Menu/Menu.tsx +++ b/packages/canon/src/components/Menu/Menu.tsx @@ -18,6 +18,8 @@ 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'; const MenuTrigger = forwardRef< React.ElementRef, @@ -180,12 +182,15 @@ MenuCheckboxItemIndicator.displayName = const MenuSubmenuTrigger = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( +>(({ className, children, ...props }, ref) => ( + > +
{children}
+ +
)); MenuSubmenuTrigger.displayName = MenuPrimitive.SubmenuTrigger.displayName; @@ -220,4 +225,5 @@ export const Menu: MenuComponent = { CheckboxItemIndicator: MenuCheckboxItemIndicator, SubmenuTrigger: MenuSubmenuTrigger, Separator: MenuSeparator, + Combobox, }; diff --git a/packages/canon/src/components/Menu/types.ts b/packages/canon/src/components/Menu/types.ts index 63f8069f61..9071f9a921 100644 --- a/packages/canon/src/components/Menu/types.ts +++ b/packages/canon/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/canon/src/components/Switch/Switch.stories.tsx b/packages/canon/src/components/Switch/Switch.stories.tsx new file mode 100644 index 0000000000..165fefc97b --- /dev/null +++ b/packages/canon/src/components/Switch/Switch.stories.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 type { Meta, StoryObj } from '@storybook/react'; +import { Switch } from './Switch'; + +const meta = { + title: 'Components/Switch', + component: Switch, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + label: 'Switch', + }, +}; + +export const Disabled: Story = { + args: { + ...Default.args, + isDisabled: true, + }, +}; diff --git a/packages/canon/src/components/Switch/Switch.styles.css b/packages/canon/src/components/Switch/Switch.styles.css new file mode 100644 index 0000000000..98368efa13 --- /dev/null +++ b/packages/canon/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. + */ + +.canon-Switch { + display: flex; + /* This is needed so the HiddenInput is positioned correctly */ + position: relative; + align-items: center; + gap: var(--canon-space-3); + font-size: var(--canon-font-size-3); + color: var(--canon-fg-primary); + cursor: pointer; + + &[data-pressed] .canon-SwitchIndicator { + &:before { + background: var(--canon-fg-solid); + } + } + + &[data-selected] { + .canon-SwitchIndicator { + background: var(--canon-bg-solid); + + &:before { + background: var(--canon-fg-solid); + transform: translateX(100%); + } + } + + &[data-pressed] { + .indicator { + background: var(--canon-gray-3); + } + } + } + + &[data-focus-visible] .canon-SwitchIndicator { + outline: 2px solid; + outline-offset: 2px; + } +} + +.canon-SwitchIndicator { + width: 2rem; + height: 1.143rem; + border: 2px; + background: var(--canon-gray-3); + border-radius: 1.143rem; + transition: all 200ms; + + &:before { + content: ''; + display: block; + margin: 0.143rem; + width: 0.857rem; + height: 0.857rem; + background: var(--canon-fg-solid); + border-radius: 16px; + transition: all 200ms; + } +} diff --git a/packages/canon/src/components/Switch/Switch.tsx b/packages/canon/src/components/Switch/Switch.tsx new file mode 100644 index 0000000000..78bbaa3930 --- /dev/null +++ b/packages/canon/src/components/Switch/Switch.tsx @@ -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. + */ + +import { forwardRef } from 'react'; +import { Switch as AriaSwitch } from 'react-aria-components'; +import type { SwitchProps } from './types'; + +/** @public */ +export const Switch = forwardRef( + ({ label, ...props }, ref) => { + return ( + +
+ {label} + + ); + }, +); + +Switch.displayName = 'Switch'; diff --git a/plugins/scaffolder-backend/src/service/index.ts b/packages/canon/src/components/Switch/index.tsx similarity index 86% rename from plugins/scaffolder-backend/src/service/index.ts rename to packages/canon/src/components/Switch/index.tsx index dc5508d47f..171969809d 100644 --- a/plugins/scaffolder-backend/src/service/index.ts +++ b/packages/canon/src/components/Switch/index.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. @@ -14,4 +14,5 @@ * limitations under the License. */ -export * from './conditionExports'; +export * from './Switch'; +export * from './types'; diff --git a/packages/canon/src/components/Switch/types.ts b/packages/canon/src/components/Switch/types.ts new file mode 100644 index 0000000000..725671edd5 --- /dev/null +++ b/packages/canon/src/components/Switch/types.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. + */ + +import type { SwitchProps as AriaSwitchProps } from 'react-aria-components'; + +/** @public */ +export interface SwitchProps extends AriaSwitchProps { + /** + * The label of the switch + */ + label?: string; +} diff --git a/packages/canon/src/components/Tabs/Tabs.stories.tsx b/packages/canon/src/components/Tabs/Tabs.stories.tsx new file mode 100644 index 0000000000..31945b1e02 --- /dev/null +++ b/packages/canon/src/components/Tabs/Tabs.stories.tsx @@ -0,0 +1,48 @@ +/* + * 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 { Tabs } from './Tabs'; + +const meta = { + title: 'Components/Tabs', + component: Tabs.Root, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const panelStyles = { + padding: 'var(--canon-space-3)', + fontSize: 'var(--canon-font-size-2)', +}; + +export const Default: Story = { + args: { + children: ( + + + Tab 1 + Tab 2 + Tab 3 With long title + + Content for Tab 1 + Content for Tab 2 + Content for Tab 3 + + ), + }, +}; diff --git a/packages/canon/src/components/Tabs/Tabs.styles.css b/packages/canon/src/components/Tabs/Tabs.styles.css new file mode 100644 index 0000000000..84a4d378c1 --- /dev/null +++ b/packages/canon/src/components/Tabs/Tabs.styles.css @@ -0,0 +1,76 @@ +.canon-TabsRoot { + border: 1px solid var(--color-gray-200); + border-radius: 0.375rem; +} + +.canon-TabsList { + display: flex; + position: relative; + z-index: 0; +} + +.canon-TabsTab { + display: flex; + align-items: center; + justify-content: center; + border: 0; + margin: 0; + outline: 0; + background: none; + appearance: none; + color: var(--canon-fg-secondary); + font-family: inherit; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 500; + user-select: none; + padding-inline: var(--canon-space-3); + padding-block: 0; + height: 2rem; + font-size: var(--canon-font-size-2); + transition: color 200ms ease-in-out; + cursor: pointer; + + &[data-selected] { + color: var(--canon-fg-primary); + } + + @media (hover: hover) { + &:hover { + color: var(--canon-fg-primary); + } + } + + &:focus-visible { + position: relative; + + &::before { + content: ''; + position: absolute; + inset: 0.25rem 0; + border-radius: 0.25rem; + outline: 1px solid var(--canon-ring); + outline-offset: -1px; + } + } +} + +.canon-TabsIndicator { + position: absolute; + z-index: -1; + left: 0; + bottom: 0; + translate: var(--active-tab-left) -50%; + width: var(--active-tab-width); + height: 1px; + background-color: var(--canon-bg-solid); + transition-property: translate, width; + transition-duration: 200ms; + transition-timing-function: ease-in-out; +} + +.canon-TabsPanel { + &[hidden] { + display: none; + } +} diff --git a/packages/canon/src/components/Tabs/Tabs.tsx b/packages/canon/src/components/Tabs/Tabs.tsx new file mode 100644 index 0000000000..b4f93a5b31 --- /dev/null +++ b/packages/canon/src/components/Tabs/Tabs.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 { forwardRef } from 'react'; +import { Tabs as TabsPrimitive } from '@base-ui-components/react/tabs'; +import type { TabsRootWithoutOrientation } from './types'; +import clsx from 'clsx'; + +const TabsRoot = forwardRef< + React.ElementRef, + TabsRootWithoutOrientation +>(({ className, ...props }, ref) => ( + +)); +TabsRoot.displayName = TabsPrimitive.Root.displayName; + +const TabsList = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children} + + +)); +TabsList.displayName = TabsPrimitive.List.displayName; + +const TabsTab = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsTab.displayName = TabsPrimitive.Tab.displayName; + +const TabsPanel = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsPanel.displayName = TabsPrimitive.Panel.displayName; + +/** @public */ +export const Tabs = { + Root: TabsRoot, + List: TabsList, + Tab: TabsTab, + Panel: TabsPanel, +}; diff --git a/packages/canon/src/components/Tabs/index.ts b/packages/canon/src/components/Tabs/index.ts new file mode 100644 index 0000000000..ba6ca91de7 --- /dev/null +++ b/packages/canon/src/components/Tabs/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 { Tabs } from './Tabs'; +export type { TabsRootWithoutOrientation } from './types'; diff --git a/packages/canon/src/components/Tabs/types.ts b/packages/canon/src/components/Tabs/types.ts new file mode 100644 index 0000000000..f3b6342761 --- /dev/null +++ b/packages/canon/src/components/Tabs/types.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. + */ +import { Tabs } from '@base-ui-components/react/tabs'; + +// We are not supporting the orientation prop, so we need to omit it + +/** @public */ +export interface TabsRootWithoutOrientation + extends Omit< + React.ComponentPropsWithoutRef, + 'orientation' + > {} diff --git a/packages/canon/src/components/Text/Text.stories.tsx b/packages/canon/src/components/Text/Text.stories.tsx index 0b0ffd2712..5fe0c12cb5 100644 --- a/packages/canon/src/components/Text/Text.stories.tsx +++ b/packages/canon/src/components/Text/Text.stories.tsx @@ -78,6 +78,13 @@ export const AllColors: Story = { ), }; +export const Truncate: Story = { + args: { + ...Default.args, + truncate: true, + }, +}; + export const Responsive: Story = { args: { ...Default.args, @@ -101,6 +108,13 @@ export const WrappedInLink: Story = { ], }; +export const CustomRender: Story = { + args: { + ...Default.args, + render: , + }, +}; + export const Playground: Story = { render: () => ( diff --git a/packages/canon/src/components/Text/Text.tsx b/packages/canon/src/components/Text/Text.tsx index 76f5adbb43..7e4dc74720 100644 --- a/packages/canon/src/components/Text/Text.tsx +++ b/packages/canon/src/components/Text/Text.tsx @@ -14,8 +14,9 @@ * limitations under the License. */ -import { forwardRef } from 'react'; +import { forwardRef, useRef } from 'react'; import { useResponsiveValue } from '../../hooks/useResponsiveValue'; +import { useRender } from '@base-ui-components/react/use-render'; import clsx from 'clsx'; import type { TextProps } from './types'; @@ -24,12 +25,12 @@ import type { TextProps } from './types'; export const Text = forwardRef( (props, ref) => { const { - children, variant = 'body', weight = 'regular', color = 'primary', - style, className, + truncate, + render =

, ...restProps } = props; @@ -37,20 +38,22 @@ export const Text = forwardRef( const responsiveVariant = useResponsiveValue(variant); const responsiveWeight = useResponsiveValue(weight); const responsiveColor = useResponsiveValue(color); + const internalRef = useRef(null); - return ( -

- {children} -

- ); + const { renderElement } = useRender({ + render, + props: { + className: clsx('canon-Text', className), + ['data-variant']: responsiveVariant, + ['data-weight']: responsiveWeight, + ['data-color']: responsiveColor, + ['data-truncate']: truncate, + ...restProps, + }, + refs: [ref, internalRef], + }); + + return renderElement(); }, ); diff --git a/packages/canon/src/components/Text/styles.css b/packages/canon/src/components/Text/styles.css index 9f0ed703d9..c0cd781007 100644 --- a/packages/canon/src/components/Text/styles.css +++ b/packages/canon/src/components/Text/styles.css @@ -67,3 +67,9 @@ .canon-Text[data-color='success'] { color: var(--canon-fg-success); } + +.canon-Text[data-truncate] { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/packages/canon/src/components/Text/types.ts b/packages/canon/src/components/Text/types.ts index d4f1615694..f9c3c0aaca 100644 --- a/packages/canon/src/components/Text/types.ts +++ b/packages/canon/src/components/Text/types.ts @@ -14,12 +14,13 @@ * limitations under the License. */ -import type { CSSProperties, ReactNode } from 'react'; +import type { CSSProperties } from 'react'; import type { Breakpoint } from '../../types'; +import type { useRender } from '@base-ui-components/react/use-render'; /** @public */ -export interface TextProps { - children: ReactNode; +export interface TextProps + extends Omit, 'color'> { variant?: | 'subtitle' | 'body' @@ -39,6 +40,7 @@ export interface TextProps { 'primary' | 'secondary' | 'danger' | 'warning' | 'success' > >; + truncate?: boolean; className?: string; style?: CSSProperties; } diff --git a/packages/canon/src/components/TextField/TextField.stories.tsx b/packages/canon/src/components/TextField/TextField.stories.tsx index 0615a157a1..f052377acb 100644 --- a/packages/canon/src/components/TextField/TextField.stories.tsx +++ b/packages/canon/src/components/TextField/TextField.stories.tsx @@ -17,10 +17,19 @@ import type { Meta, StoryObj } from '@storybook/react'; import { TextField } from './TextField'; import { Flex } from '../Flex'; +import { Icon } from '../Icon'; const meta = { title: 'Components/TextField', component: TextField, + argTypes: { + secondaryLabel: { + control: 'text', + }, + required: { + control: 'boolean', + }, + }, } satisfies Meta; export default meta; @@ -64,6 +73,28 @@ export const Required: Story = { }, }; +export const LabelSizes: Story = { + args: { + ...Default.args, + label: 'Label', + description: 'Description', + required: true, + }, + render: args => ( + + + + + ), +}; + +export const HideLabelAndDescription: Story = { + args: { + ...WithLabel.args, + hideLabelAndDescription: true, + }, +}; + export const Disabled: Story = { args: { ...WithLabel.args, @@ -79,8 +110,8 @@ export const Sizes: Story = { }, render: args => ( - - + } /> + } /> ), }; @@ -114,7 +145,7 @@ export const WithIcon: Story = { args: { ...WithLabel.args, placeholder: 'Search...', - icon: 'search', + icon: , }, }; diff --git a/packages/canon/src/components/TextField/TextField.styles.css b/packages/canon/src/components/TextField/TextField.styles.css index ad3ccc931b..d7bc147ba1 100644 --- a/packages/canon/src/components/TextField/TextField.styles.css +++ b/packages/canon/src/components/TextField/TextField.styles.css @@ -21,25 +21,63 @@ width: 100%; } +.canon-TextFieldLabelWrapper { + display: flex; + flex-direction: column; + margin-bottom: var(--canon-space-3); + gap: var(--canon-space-1); +} + +.canon-TextFieldLabelWrapper[data-hidden] { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + .canon-TextFieldLabel { - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-primary); - margin-bottom: var(--canon-space-1_5); margin-right: auto; cursor: pointer; } +.canon-TextFieldLabel[data-size='small'] { + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); +} + +.canon-TextFieldLabel[data-size='medium'] { + font-weight: var(--canon-font-weight-bold); + font-size: var(--canon-font-size-3); +} + .canon-TextFieldLabel[data-disabled] { cursor: default; } +.canon-TextFieldSecondaryLabel { + color: var(--canon-fg-secondary); + font-weight: var(--canon-font-weight-regular); + margin-left: var(--canon-space-1); +} + .canon-TextFieldDescription { - font-size: var(--canon-font-size-2); font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-secondary); margin: 0; - padding-top: var(--canon-space-1_5); +} + +.canon-TextFieldDescription[data-size='small'] { + font-size: var(--canon-font-size-2); +} + +.canon-TextFieldDescription[data-size='medium'] { + font-size: var(--canon-font-size-3); } .canon-TextFieldError { @@ -59,15 +97,24 @@ background-color: var(--canon-bg-surface-1); } -.canon-TextFieldInputIcon { - display: block; - padding-right: var(--canon-space-1); - width: 1.5rem; - height: 1.5rem; +.canon-TextFieldIcon { + margin-right: var(--canon-space-1); color: var(--canon-fg-primary); flex-shrink: 0; } +.canon-TextFieldIcon[data-size='small'], +.canon-TextFieldIcon[data-size='small'] svg { + width: 1rem; + height: 1rem; +} + +.canon-TextFieldIcon[data-size='medium'], +.canon-TextFieldIcon[data-size='medium'] svg { + width: 1.25rem; + height: 1.25rem; +} + .canon-TextFieldInput { border: none; background: none; @@ -147,10 +194,3 @@ .canon-TextFieldInputWrapper[data-size='medium'] { height: 2.5rem; } - -.canon-TextFieldRequired { - color: var(--canon-fg-secondary); - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - margin-left: var(--canon-space-1); -} diff --git a/packages/canon/src/components/TextField/TextField.tsx b/packages/canon/src/components/TextField/TextField.tsx index bb32033da4..07efc47100 100644 --- a/packages/canon/src/components/TextField/TextField.tsx +++ b/packages/canon/src/components/TextField/TextField.tsx @@ -27,9 +27,12 @@ export const TextField = forwardRef( (props: TextFieldProps, ref) => { const { className, - size = 'medium', + size = 'small', label, + labelSize = 'small', + secondaryLabel, description, + hideLabelAndDescription, error, required, style, @@ -42,6 +45,9 @@ export const TextField = forwardRef( // Get the responsive value for the variant const responsiveSize = useResponsiveValue(size); + // If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required. + const secondaryLabelText = secondaryLabel || (required ? 'Required' : null); + return ( ( ref={ref} > {label && ( - - {label} - {required && ( - +
+ {label && ( + + {label} + {secondaryLabelText && ( + + )} + )} - + {description && ( + + {description} + + )} +
)}
- {icon && } + {icon && ( + + )} ( )}
- {description && ( - - {description} - - )} {error && ( {error} diff --git a/packages/canon/src/components/TextField/types.ts b/packages/canon/src/components/TextField/types.ts index 8111749845..8a81492f2f 100644 --- a/packages/canon/src/components/TextField/types.ts +++ b/packages/canon/src/components/TextField/types.ts @@ -15,8 +15,7 @@ */ import type { Breakpoint } from '../../types'; -import type { IconNames } from '../Icon'; - +import type { ReactNode, MouseEventHandler } from 'react'; /** @public */ export interface TextFieldProps extends Omit, 'size'> { @@ -36,6 +35,21 @@ export interface TextFieldProps */ label?: string; + /** + * The secondary label of the text field + */ + secondaryLabel?: string; + + /** + * The size of the label and description + */ + labelSize?: 'small' | 'medium'; + + /** + * Hide the label and description but still visible to screen readers + */ + hideLabelAndDescription?: boolean; + /** * The description of the text field */ @@ -54,10 +68,10 @@ export interface TextFieldProps /** * An icon to render before the input */ - icon?: IconNames; + icon?: ReactNode; /** * Handler to call when the clear button is pressed */ - onClear?: React.MouseEventHandler; + onClear?: MouseEventHandler; } diff --git a/packages/canon/src/css/components.css b/packages/canon/src/css/components.css index 53c2882302..609ebea769 100644 --- a/packages/canon/src/css/components.css +++ b/packages/canon/src/css/components.css @@ -30,6 +30,7 @@ @import '../components/Table/TableCellText/TableCellText.styles.css'; @import '../components/Table/TableCellLink/TableCellLink.styles.css'; @import '../components/Table/TableCellProfile/TableCellProfile.styles.css'; +@import '../components/Tabs/Tabs.styles.css'; @import '../components/Text/styles.css'; @import '../components/Heading/styles.css'; @import '../components/IconButton/styles.css'; @@ -39,3 +40,4 @@ @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/canon/src/css/stories.module.css b/packages/canon/src/css/stories.module.css new file mode 100644 index 0000000000..10ed37bb7a --- /dev/null +++ b/packages/canon/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/index.ts b/packages/canon/src/index.ts index 7677a539c7..310a7896c8 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -40,12 +40,14 @@ export * from './components/Icon'; export * from './components/IconButton'; export * from './components/Checkbox'; export * from './components/Table'; +export * from './components/Tabs'; export * from './components/TextField'; export * from './components/Tooltip'; export * from './components/Menu'; export * from './components/ScrollArea'; export * from './components/Link'; export * from './components/Select'; +export * from './components/Switch'; // Types export * from './types'; diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 5d8d058518..d521818703 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/catalog-client +## 1.10.1-next.0 + +### Patch Changes + +- 22fad0d: Fixed `CatalogClient` error responses for `refreshEntity` and `addLocation`. +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + +## 1.10.0 + +### Minor Changes + +- 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 + ## 1.10.0-next.0 ### Minor Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 95e5931121..b87a3d4348 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-client", - "version": "1.10.0-next.0", + "version": "1.10.1-next.0", "description": "An isomorphic client for the catalog backend", "backstage": { "role": "common-library" diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index b3f8df1aa6..e69f6ad434 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -305,7 +305,7 @@ export class CatalogClient implements CatalogApi { ); if (response.status !== 200) { - throw new Error(await response.text()); + throw await ResponseError.fromResponse(response); } } @@ -345,7 +345,7 @@ export class CatalogClient implements CatalogApi { ); if (response.status !== 201) { - throw new Error(await response.text()); + throw await ResponseError.fromResponse(response); } const { location, entities, exists } = await response.json(); diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index daee1fbab0..59df385827 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 + ## 1.7.3 ### Patch Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 743eccc50d..6d20f69531 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "1.7.3", + "version": "1.7.4", "description": "Types and validators that help describe the model of a Backstage Catalog", "backstage": { "role": "common-library" diff --git a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts index 9fe041a839..3efef8691a 100644 --- a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts +++ b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts @@ -197,7 +197,7 @@ describe('FieldFormatEntityPolicy', () => { }, ); - it.each([[123], [{}], [[]], ['abc xyz']])( + it.each([[123], [{}], [[]]])( 'rejects bad link icon value %s', async (icon: unknown) => { data.metadata.links = [{ url: 'http://foo', icon }]; @@ -208,10 +208,8 @@ describe('FieldFormatEntityPolicy', () => { it('rejects a single bad link icon value', async () => { data.metadata.links = [ { url: 'http://foo', icon: 'good' }, - { url: 'http://foo', icon: 'not good' }, + { url: 'http://foo', icon: 123 }, ]; - await expect(policy.enforce(data)).rejects.toThrow( - /links.1.icon.*"not good"/i, - ); + await expect(policy.enforce(data)).rejects.toThrow(/links.1.icon.*"123"/i); }); }); diff --git a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.ts b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.ts index 18e9523478..961eb16ecd 100644 --- a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.ts +++ b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.ts @@ -161,7 +161,7 @@ export class FieldFormatEntityPolicy implements EntityPolicy { optional( `links.${i}.icon`, links[i]?.icon, - KubernetesValidatorFunctions.isValidObjectName, + CommonValidatorFunctions.isNonEmptyString, ); } diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index fb537faff4..1998945c38 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,93 @@ # @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 + +## 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 + +## 0.32.2-next.0 + +### Patch Changes + +- ce70439: The `BACKSTAGE_CLI_EXPERIMENTAL_BUILD_CACHE` flag has been removed. Existing users are encouraged to switch to `EXPERIMENTAL_RSPACK` instead. +- 1d8f00b: Switched to using the `ModuleFederationPlugin` from `@module-federation/enhanced/rspack` for Rspack, rather than the built-in one. +- 2b9633f: The experimental `FORCE_REACT_DEVELOPMENT` flag has been removed. +- d8c4a54: Only use the caching Jest module loader for frontend packages in order to avoid breaking real ESM module imports. +- 1bab255: Internal refactor to combine alpha `build` and `start` modules. +- 713e957: fix: merge eslint reports when using json format +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.1.10 + - @backstage/integration@1.17.0 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.1 + +## 0.32.1 + +### Patch Changes + +- 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 + ## 0.32.1-next.3 ### Patch Changes diff --git a/packages/cli/cli-report.backstage-cli-alpha.md b/packages/cli/cli-report.backstage-cli-alpha.md deleted file mode 100644 index 386a642c21..0000000000 --- a/packages/cli/cli-report.backstage-cli-alpha.md +++ /dev/null @@ -1,468 +0,0 @@ -## CLI Report file for "@backstage/cli" - -> Do not edit this file. It is a report generated by `yarn build:api-reports` - -### `backstage-cli-alpha` - -``` -Usage: backstage-cli [options] [command] - -Options: - -V, --version - -h, --help - -Commands: - build-workspace - config [command] - config:check - config:docs - config:print - config:schema - create-github-app - help [command] - info - migrate [command] - new - package [command] - repo [command] - versions:bump - versions:migrate -``` - -### `backstage-cli-alpha build-workspace` - -``` -Usage: program [options] [packages...] - -Options: - --alwaysPack - -h, --help -``` - -### `backstage-cli-alpha config` - -``` -Usage: backstage-cli config [options] [command] [command] - -Options: - -h, --help - -Commands: - docs - help [command] - schema -``` - -### `backstage-cli-alpha config docs` - -``` -Usage: backstage-cli config docs [options] - -Options: - --package - -h, --help -``` - -### `backstage-cli-alpha config schema` - -``` -Usage: - -Options: - --format - --help - --merge - --no-merge - --package - --version -``` - -### `backstage-cli-alpha config:check` - -``` -Usage: - -Options: - --config - --deprecated - --frontend - --help - --lax - --package - --strict - --version -``` - -### `backstage-cli-alpha config:docs` - -``` -Usage: program [options] - -Options: - --package - -h, --help -``` - -### `backstage-cli-alpha config:print` - -``` -Usage: - -Options: - --config - --format - --frontend - --help - --lax - --package - --version - --with-secrets -``` - -### `backstage-cli-alpha config:schema` - -``` -Usage: - -Options: - --format - --help - --merge - --no-merge - --package - --version -``` - -### `backstage-cli-alpha create-github-app` - -``` -Usage: program [options] - -Options: - -h, --help -``` - -### `backstage-cli-alpha info` - -``` -Usage: - -Options: - --help - --version -``` - -### `backstage-cli-alpha migrate` - -``` -Usage: backstage-cli migrate [options] [command] [command] - -Options: - -h, --help - -Commands: - help [command] - package-exports - package-lint-configs - package-roles - package-scripts - react-router-deps -``` - -### `backstage-cli-alpha migrate package-exports` - -``` -Usage: program [options] - -Options: - -h, --help -``` - -### `backstage-cli-alpha migrate package-lint-configs` - -``` -Usage: program [options] - -Options: - -h, --help -``` - -### `backstage-cli-alpha migrate package-roles` - -``` -Usage: program [options] - -Options: - -h, --help -``` - -### `backstage-cli-alpha migrate package-scripts` - -``` -Usage: program [options] - -Options: - -h, --help -``` - -### `backstage-cli-alpha migrate react-router-deps` - -``` -Usage: program [options] - -Options: - -h, --help -``` - -### `backstage-cli-alpha new` - -``` -Usage: program [options] - -Options: - --baseVersion - --license - --no-private - --npm-registry - --option = - --scope - --select - --skip-install - -h, --help -``` - -### `backstage-cli-alpha package` - -``` -Usage: backstage-cli package [options] [command] [command] - -Options: - -h, --help - -Commands: - build - help [command] - lint - test -``` - -### `backstage-cli-alpha package build` - -``` -Usage: program [options] - -Options: - --config - --minify - --role - --skip-build-dependencies - --stats - -h, --help -``` - -### `backstage-cli-alpha package lint` - -``` -Usage: program [options] [directories...] - -Options: - --fix - --format - --max-warnings - --output-file - -h, --help -``` - -### `backstage-cli-alpha package test` - -``` -Usage: backstage-cli-alpha [--config=] [TestPathPattern] - -Options: - --all - --automock - --cache - --cacheDirectory - --changedFilesWithAncestor - --changedSince - --ci - --clearCache - --clearMocks - --collectCoverage - --collectCoverageFrom - --color - --colors - --coverage - --coverageDirectory - --coveragePathIgnorePatterns - --coverageProvider - --coverageReporters - --coverageThreshold - --debug - --detectLeaks - --detectOpenHandles - --env - --errorOnDeprecated - --filter - --findRelatedTests - --forceExit - --globalSetup - --globalTeardown - --globals - --haste - --ignoreProjects - --init - --injectGlobals - --json - --lastCommit - --listTests - --logHeapUsage - --maxConcurrency - --moduleDirectories - --moduleFileExtensions - --moduleNameMapper - --modulePathIgnorePatterns - --modulePaths - --noStackTrace - --notify - --notifyMode - --openHandlesTimeout - --outputFile - --passWithNoTests - --preset - --prettierPath - --projects - --randomize - --reporters - --resetMocks - --resetModules - --resolver - --restoreMocks - --rootDir - --roots - --runTestsByPath - --runner - --seed - --selectProjects - --setupFiles - --setupFilesAfterEnv - --shard - --showConfig - --showSeed - --silent - --skipFilter - --snapshotSerializers - --testEnvironment - --testEnvironmentOptions - --testFailureExitCode - --testLocationInResults - --testMatch - --testPathIgnorePatterns - --testPathPattern - --testRegex - --testResultsProcessor - --testRunner - --testSequencer - --testTimeout - --transform - --transformIgnorePatterns - --unmockedModulePathPatterns - --useStderr - --verbose - --version - --watch - --watchAll - --watchPathIgnorePatterns - --watchman - --workerThreads - -b, --bail - -c, --config - -e, --expand - -f, --onlyFailures - -h, --help - -i, --runInBand - -o, --onlyChanged - -t, --testNamePattern - -u, --updateSnapshot - -w, --maxWorkers -``` - -### `backstage-cli-alpha repo` - -``` -Usage: backstage-cli repo [options] [command] [command] - -Options: - -h, --help - -Commands: - build - help [command] - lint - test -``` - -### `backstage-cli-alpha repo build` - -``` -Usage: program [options] [command] - -Options: - --all - --minify - --since - -h, --help -``` - -### `backstage-cli-alpha repo lint` - -``` -Usage: program [options] - -Options: - --fix - --format - --max-warnings - --output-file - --since - --successCache - --successCacheDir - -h, --help -``` - -### `backstage-cli-alpha repo test` - -``` -Usage: program [options] - -Options: - --jest-help - --since - --successCache - --successCacheDir - -h, --help -``` - -### `backstage-cli-alpha versions:bump` - -``` -Usage: program [options] - -Options: - --pattern - --release - --skip-install - --skip-migrate - -h, --help -``` - -### `backstage-cli-alpha versions:migrate` - -``` -Usage: program [options] - -Options: - --pattern - --skip-code-changes - -h, --help -``` diff --git a/packages/cli/cli-report.backstage-cli.md b/packages/cli/cli-report.md similarity index 74% rename from packages/cli/cli-report.backstage-cli.md rename to packages/cli/cli-report.md index ed6ae9049e..e2f5bb9cc6 100644 --- a/packages/cli/cli-report.backstage-cli.md +++ b/packages/cli/cli-report.md @@ -12,51 +12,91 @@ Options: -h, --help Commands: - build-workspace [options] [packages...] - config:check [options] - config:docs [options] - config:print [options] - config:schema [options] - create-github-app + build-workspace + config [command] + config:check + config:docs + config:print + config:schema + create-github-app help [command] info migrate [command] - new [options] + new package [command] repo [command] - versions:bump [options] - versions:migrate [options] + versions:bump + versions:migrate ``` ### `backstage-cli build-workspace` ``` -Usage: backstage-cli build-workspace [options] [packages...] +Usage: program [options] [packages...] Options: --alwaysPack -h, --help ``` +### `backstage-cli config` + +``` +Usage: backstage-cli config [options] [command] [command] + +Options: + -h, --help + +Commands: + docs + help [command] + schema +``` + +### `backstage-cli config docs` + +``` +Usage: backstage-cli config docs [options] + +Options: + --package + -h, --help +``` + +### `backstage-cli config schema` + +``` +Usage: + +Options: + --format + --help + --merge + --no-merge + --package + --version +``` + ### `backstage-cli config:check` ``` -Usage: backstage-cli config:check [options] +Usage: Options: - --config + --config --deprecated --frontend + --help --lax - --package + --package --strict - -h, --help + --version ``` ### `backstage-cli config:docs` ``` -Usage: backstage-cli config:docs [options] +Usage: program [options] Options: --package @@ -66,35 +106,37 @@ Options: ### `backstage-cli config:print` ``` -Usage: backstage-cli config:print [options] +Usage: Options: - --config - --format + --config + --format --frontend + --help --lax - --package + --package + --version --with-secrets - -h, --help ``` ### `backstage-cli config:schema` ``` -Usage: backstage-cli config:schema [options] +Usage: Options: - --format + --format + --help --merge --no-merge - --package - -h, --help + --package + --version ``` ### `backstage-cli create-github-app` ``` -Usage: backstage-cli create-github-app [options] +Usage: program [options] Options: -h, --help @@ -103,10 +145,11 @@ Options: ### `backstage-cli info` ``` -Usage: backstage-cli info [options] +Usage: Options: - -h, --help + --help + --version ``` ### `backstage-cli migrate` @@ -129,7 +172,7 @@ Commands: ### `backstage-cli migrate package-exports` ``` -Usage: backstage-cli migrate package-exports [options] +Usage: program [options] Options: -h, --help @@ -138,7 +181,7 @@ Options: ### `backstage-cli migrate package-lint-configs` ``` -Usage: backstage-cli migrate package-lint-configs [options] +Usage: program [options] Options: -h, --help @@ -147,7 +190,7 @@ Options: ### `backstage-cli migrate package-roles` ``` -Usage: backstage-cli migrate package-roles [options] +Usage: program [options] Options: -h, --help @@ -156,7 +199,7 @@ Options: ### `backstage-cli migrate package-scripts` ``` -Usage: backstage-cli migrate package-scripts [options] +Usage: program [options] Options: -h, --help @@ -165,7 +208,7 @@ Options: ### `backstage-cli migrate react-router-deps` ``` -Usage: backstage-cli migrate react-router-deps [options] +Usage: program [options] Options: -h, --help @@ -174,7 +217,7 @@ Options: ### `backstage-cli new` ``` -Usage: backstage-cli new [options] +Usage: program [options] Options: --baseVersion @@ -197,20 +240,20 @@ Options: -h, --help Commands: - build [options] + build clean help [command] - lint [options] [directories...] + lint postpack prepack - start [options] + start test ``` ### `backstage-cli package build` ``` -Usage: backstage-cli package build [options] +Usage: program [options] Options: --config @@ -224,7 +267,7 @@ Options: ### `backstage-cli package clean` ``` -Usage: backstage-cli package clean [options] +Usage: program [options] Options: -h, --help @@ -233,7 +276,7 @@ Options: ### `backstage-cli package lint` ``` -Usage: backstage-cli package lint [options] [directories...] +Usage: program [options] [directories...] Options: --fix @@ -246,7 +289,7 @@ Options: ### `backstage-cli package postpack` ``` -Usage: backstage-cli package postpack [options] +Usage: program [options] Options: -h, --help @@ -255,7 +298,7 @@ Options: ### `backstage-cli package prepack` ``` -Usage: backstage-cli package prepack [options] +Usage: program [options] Options: -h, --help @@ -264,7 +307,7 @@ Options: ### `backstage-cli package start` ``` -Usage: backstage-cli package start [options] +Usage: program [options] Options: --check @@ -400,20 +443,20 @@ Options: -h, --help Commands: - build [options] + build clean - fix [options] + fix help [command] - lint [options] - list-deprecations [options] - start [options] [packageNameOrPath...] - test [options] + lint + list-deprecations + start + test ``` ### `backstage-cli repo build` ``` -Usage: backstage-cli repo build [options] +Usage: program [options] [command] Options: --all @@ -425,7 +468,7 @@ Options: ### `backstage-cli repo clean` ``` -Usage: backstage-cli repo clean [options] +Usage: program [options] Options: -h, --help @@ -434,7 +477,7 @@ Options: ### `backstage-cli repo fix` ``` -Usage: backstage-cli repo fix [options] +Usage: program [options] Options: --check @@ -445,7 +488,7 @@ Options: ### `backstage-cli repo lint` ``` -Usage: backstage-cli repo lint [options] +Usage: program [options] [command] Options: --fix @@ -461,7 +504,7 @@ Options: ### `backstage-cli repo list-deprecations` ``` -Usage: backstage-cli repo list-deprecations [options] +Usage: program [options] Options: --json @@ -471,7 +514,7 @@ Options: ### `backstage-cli repo start` ``` -Usage: backstage-cli repo start [options] [packageNameOrPath...] +Usage: program [options] [packageNameOrPath...] Options: --config @@ -486,7 +529,7 @@ Options: ### `backstage-cli repo test` ``` -Usage: backstage-cli repo test [options] +Usage: program [options] Options: --jest-help @@ -499,7 +542,7 @@ Options: ### `backstage-cli versions:bump` ``` -Usage: backstage-cli versions:bump [options] +Usage: program [options] Options: --pattern @@ -512,7 +555,7 @@ Options: ### `backstage-cli versions:migrate` ``` -Usage: backstage-cli versions:migrate [options] +Usage: program [options] Options: --pattern diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index 560c9bf708..f011d14b19 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -212,6 +212,11 @@ function getRoleConfig(role, pkgJson) { if (FRONTEND_ROLES.includes(role)) { return { testEnvironment: require.resolve('jest-environment-jsdom'), + // The caching module loader is only used to speed up frontend tests, + // as it breaks real dynamic imports of ESM modules. + runtime: envOptions.oldTests + ? undefined + : require.resolve('./jestCachingModuleLoader'), transform, }; } @@ -257,10 +262,6 @@ async function getProjectConfig(targetPath, extraConfig, extraOptions) { // A bit more opinionated testMatch: [`**/*.test.{${SRC_EXTS.join(',')}}`], - runtime: envOptions.oldTests - ? undefined - : require.resolve('./jestCachingModuleLoader'), - transformIgnorePatterns: [`/node_modules/(?:${transformIgnorePattern})/`], ...getRoleConfig(pkgJson.backstage?.role, pkgJson), }; diff --git a/packages/cli/package.json b/packages/cli/package.json index 7afa1637b8..a23e9173ff 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.32.1-next.3", + "version": "0.33.0-next.2", "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" @@ -20,8 +20,7 @@ "license": "Apache-2.0", "main": "dist/index.cjs.js", "bin": { - "backstage-cli": "bin/backstage-cli", - "backstage-cli-alpha": "bin/backstage-cli-alpha" + "backstage-cli": "bin/backstage-cli" }, "files": [ "asset-types", diff --git a/packages/cli/src/alpha.ts b/packages/cli/src/alpha.ts deleted file mode 100644 index 3324ed8d75..0000000000 --- a/packages/cli/src/alpha.ts +++ /dev/null @@ -1,37 +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 { CliInitializer } from './wiring/CliInitializer'; -import chalk from 'chalk'; - -(async () => { - // Logging as stdout to prevent failures from stderr during api-report generation. - console.log( - chalk.yellow( - 'THIS ENTRYPOINT IS IN ALPHA AND MAY CHANGE IN THE FUTURE - DO NOT USE THIS FOR NORMAL DEVELOPMENT', - ), - ); - const initializer = new CliInitializer(); - initializer.add(import('./modules/new/alpha')); - initializer.add(import('./modules/create-github-app/alpha')); - initializer.add(import('./modules/info/alpha')); - initializer.add(import('./modules/config/alpha')); - initializer.add(import('./modules/build/alpha')); - initializer.add(import('./modules/migrate/alpha')); - initializer.add(import('./modules/test/alpha')); - initializer.add(import('./modules/lint/alpha')); - await initializer.run(); -})(); diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts deleted file mode 100644 index 8483fd79c0..0000000000 --- a/packages/cli/src/commands/index.ts +++ /dev/null @@ -1,103 +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 { Command } from 'commander'; -import { registerCommands as registerConfigCommands } from '../modules/config'; -import { - registerPackageCommands as registerPackageBuildCommands, - registerRepoCommands as registerRepoBuildCommands, - registerCommands as registerBuildCommands, -} from '../modules/build'; -import { - registerPackageCommands as registerPackageStartCommands, - registerRepoCommands as registerRepoStartCommands, -} from '../modules/start'; -import { registerCommands as registerInfoCommands } from '../modules/info'; -import { registerCommands as registerMigrateCommand } from '../modules/migrate'; -import { - registerRepoCommands as registerRepoTestCommands, - registerPackageCommands as registerPackageTestCommands, -} from '../modules/test'; -import { - registerPackageCommands as registerPackageLintCommands, - registerRepoCommands as registerRepoLintCommands, -} from '../modules/lint'; -import { - registerPackageCommands as registerMaintenancePackageCommands, - registerRepoCommands as registerMaintenanceRepoCommands, -} from '../modules/maintenance'; -import { removed } from '../lib/removed'; -import { registerCommands as registerNewCommands } from '../modules/new'; -import { registerCommands as registerCreateGithubAppCommands } from '../modules/create-github-app'; - -export function registerRepoCommand(program: Command) { - const command = program - .command('repo [command]') - .description('Command that run across an entire Backstage project'); - - registerRepoStartCommands(command); - registerRepoBuildCommands(command); - registerRepoTestCommands(command); - registerRepoLintCommands(command); - registerMaintenanceRepoCommands(command); -} - -export function registerScriptCommand(program: Command) { - const command = program - .command('package [command]') - .description('Lifecycle scripts for individual packages'); - - registerPackageStartCommands(command); - registerPackageBuildCommands(command); - registerPackageTestCommands(command); - registerMaintenancePackageCommands(command); - registerPackageLintCommands(command); -} - -export function registerCommands(program: Command) { - registerConfigCommands(program); - registerRepoCommand(program); - registerScriptCommand(program); - registerMigrateCommand(program); - registerBuildCommands(program); - registerInfoCommands(program); - registerNewCommands(program); - registerCreateGithubAppCommands(program); - // Notifications for removed commands - - program - .command('plugin:diff') - .allowUnknownOption(true) - .action(removed("use 'backstage-cli fix' instead")); - program - .command('test') - .allowUnknownOption(true) - .action( - removed( - "use 'backstage-cli repo test' or 'backstage-cli package test' instead", - ), - ); - program - .command('clean') - .allowUnknownOption(true) - .action(removed("use 'backstage-cli package clean' instead")); - program - .command('versions:check') - .allowUnknownOption(true) - .action(removed("use 'yarn dedupe' or 'yarn-deduplicate' instead")); - program.command('install').allowUnknownOption(true).action(removed()); - program.command('onboard').allowUnknownOption(true).action(removed()); -} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index d1ca1bcd04..ece1e413a0 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,40 +14,18 @@ * limitations under the License. */ -/** - * CLI for developing Backstage plugins and apps - * - * @packageDocumentation - */ +import { CliInitializer } from './wiring/CliInitializer'; -import { program } from 'commander'; -import chalk from 'chalk'; -import { exitWithError } from './lib/errors'; -import { version } from './lib/version'; -import { registerCommands } from './commands'; - -const main = (argv: string[]) => { - program.name('backstage-cli').version(version); - - registerCommands(program); - - program.on('command:*', () => { - console.log(); - console.log(chalk.red(`Invalid command: ${program.args.join(' ')}`)); - console.log(); - program.outputHelp(); - process.exit(1); - }); - - program.parse(argv); -}; - -process.on('unhandledRejection', rejection => { - if (rejection instanceof Error) { - exitWithError(rejection); - } else { - exitWithError(new Error(`Unknown rejection: '${rejection}'`)); - } -}); - -main(process.argv); +(async () => { + const initializer = new CliInitializer(); + initializer.add(import('./modules/build')); + initializer.add(import('./modules/config')); + initializer.add(import('./modules/create-github-app')); + initializer.add(import('./modules/info')); + initializer.add(import('./modules/lint')); + initializer.add(import('./modules/maintenance')); + initializer.add(import('./modules/migrate')); + initializer.add(import('./modules/new')); + initializer.add(import('./modules/test')); + await initializer.run(); +})(); diff --git a/packages/cli/src/modules/build/alpha.ts b/packages/cli/src/modules/build/alpha.ts deleted file mode 100644 index 5e2c6d6e7d..0000000000 --- a/packages/cli/src/modules/build/alpha.ts +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Command, Option } from 'commander'; -import { createCliPlugin } from '../../wiring/factory'; -import { lazy } from '../../lib/lazy'; -import { registerPackageCommands } from '.'; - -export const buildPlugin = createCliPlugin({ - pluginId: 'build', - init: async reg => { - reg.addCommand({ - path: ['package', 'build'], - description: 'Build a package for production deployment or publishing', - execute: async ({ args }) => { - const command = new Command(); - - const defaultCommand = command - .option( - '--role ', - 'Run the command with an explicit package role', - ) - .option( - '--minify', - 'Minify the generated code. Does not apply to app package (app is minified by default).', - ) - .option( - '--skip-build-dependencies', - 'Skip the automatic building of local dependencies. Applies to backend packages only.', - ) - .option( - '--stats', - 'If bundle stats are available, write them to the output directory. Applies to app packages only.', - ) - .option( - '--config ', - 'Config files to load instead of app-config.yaml. Applies to app packages only.', - (opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]), - Array(), - ) - .action(lazy(() => import('./commands/package/build'), 'command')); - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['repo', 'build'], - description: - 'Build packages in the project, excluding bundled app and backend packages.', - execute: async ({ args }) => { - const command = new Command(); - - // This command expect `package build` to be registered, as its used to parse - // individual plugins' package build scripts. - registerPackageCommands(command.command('package')); - - const defaultCommand = command - .option( - '--all', - 'Build all packages, including bundled app and backend packages.', - ) - .option( - '--since ', - 'Only build packages and their dev dependents that changed since the specified ref', - ) - .option( - '--minify', - 'Minify the generated code. Does not apply to app package (app is minified by default).', - ) - .action(lazy(() => import('./commands/repo/build'), 'command')); - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['build-workspace'], - description: - 'Builds a temporary dist workspace from the provided packages', - execute: async ({ args }) => { - const command = new Command(); - const defaultCommand = command - .arguments(' [packages...]') - .addOption( - new Option( - '--alwaysYarnPack', - 'Alias for --alwaysPack for backwards compatibility.', - ) - .implies({ alwaysPack: true }) - .hideHelp(true), - ) - .option( - '--alwaysPack', - 'Force workspace output to be a result of running `yarn pack` on each package (warning: very slow)', - ) - .action(lazy(() => import('./commands/buildWorkspace'), 'default')); - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - }, -}); - -export default buildPlugin; diff --git a/packages/cli/src/modules/start/commands/package/start/command.ts b/packages/cli/src/modules/build/commands/package/start/command.ts similarity index 100% rename from packages/cli/src/modules/start/commands/package/start/command.ts rename to packages/cli/src/modules/build/commands/package/start/command.ts diff --git a/packages/cli/src/modules/start/commands/package/start/index.ts b/packages/cli/src/modules/build/commands/package/start/index.ts similarity index 100% rename from packages/cli/src/modules/start/commands/package/start/index.ts rename to packages/cli/src/modules/build/commands/package/start/index.ts diff --git a/packages/cli/src/modules/start/commands/package/start/resolveLinkedWorkspace.ts b/packages/cli/src/modules/build/commands/package/start/resolveLinkedWorkspace.ts similarity index 100% rename from packages/cli/src/modules/start/commands/package/start/resolveLinkedWorkspace.ts rename to packages/cli/src/modules/build/commands/package/start/resolveLinkedWorkspace.ts diff --git a/packages/cli/src/modules/start/commands/package/start/startBackend.ts b/packages/cli/src/modules/build/commands/package/start/startBackend.ts similarity index 100% rename from packages/cli/src/modules/start/commands/package/start/startBackend.ts rename to packages/cli/src/modules/build/commands/package/start/startBackend.ts diff --git a/packages/cli/src/modules/start/commands/package/start/startFrontend.ts b/packages/cli/src/modules/build/commands/package/start/startFrontend.ts similarity index 85% rename from packages/cli/src/modules/start/commands/package/start/startFrontend.ts rename to packages/cli/src/modules/build/commands/package/start/startFrontend.ts index d4b2f6a1cd..aaadf3ed95 100644 --- a/packages/cli/src/modules/start/commands/package/start/startFrontend.ts +++ b/packages/cli/src/modules/build/commands/package/start/startFrontend.ts @@ -22,6 +22,7 @@ import { } from '../../../../build/lib/bundler'; import { paths } from '../../../../../lib/paths'; import { BackstagePackageJson } from '@backstage/cli-node'; +import { hasReactDomClient } from '../../../../build/lib/bundler/hasReactDomClient'; interface StartAppOptions { verifyVersions?: boolean; @@ -40,6 +41,12 @@ export async function startFrontend(options: StartAppOptions) { resolvePath(options.targetDir ?? paths.targetDir, 'package.json'), )) as BackstagePackageJson; + if (!hasReactDomClient()) { + console.warn( + 'React 17 is now deprecated! Please follow the Backstage migration guide to update to React 18: https://backstage.io/docs/tutorials/react18-migration/', + ); + } + const waitForExit = await serveBundle({ entry: options.entry, targetDir: options.targetDir, diff --git a/packages/cli/src/modules/start/commands/package/start/startPackage.ts b/packages/cli/src/modules/build/commands/package/start/startPackage.ts similarity index 100% rename from packages/cli/src/modules/start/commands/package/start/startPackage.ts rename to packages/cli/src/modules/build/commands/package/start/startPackage.ts diff --git a/packages/cli/src/modules/start/commands/repo/start.test.ts b/packages/cli/src/modules/build/commands/repo/start.test.ts similarity index 100% rename from packages/cli/src/modules/start/commands/repo/start.test.ts rename to packages/cli/src/modules/build/commands/repo/start.test.ts diff --git a/packages/cli/src/modules/start/commands/repo/start.ts b/packages/cli/src/modules/build/commands/repo/start.ts similarity index 100% rename from packages/cli/src/modules/start/commands/repo/start.ts rename to packages/cli/src/modules/build/commands/repo/start.ts diff --git a/packages/cli/src/modules/build/index.ts b/packages/cli/src/modules/build/index.ts index 0135517cd8..fd31a6c782 100644 --- a/packages/cli/src/modules/build/index.ts +++ b/packages/cli/src/modules/build/index.ts @@ -13,29 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Command, Option } from 'commander'; -import { lazy } from '../../lib/lazy'; -export function registerRepoCommands(command: Command) { - command - .command('build') - .description( - 'Build packages in the project, excluding bundled app and backend packages.', - ) - .option( - '--all', - 'Build all packages, including bundled app and backend packages.', - ) - .option( - '--since ', - 'Only build packages and their dev dependents that changed since the specified ref', - ) - .option( - '--minify', - 'Minify the generated code. Does not apply to app package (app is minified by default).', - ) - .action(lazy(() => import('./commands/repo/build'), 'command')); -} +import { Command, Option } from 'commander'; +import { createCliPlugin } from '../../wiring/factory'; +import { lazy } from '../../lib/lazy'; +import { configOption } from '../config'; export function registerPackageCommands(command: Command) { command @@ -63,21 +45,171 @@ export function registerPackageCommands(command: Command) { .action(lazy(() => import('./commands/package/build'), 'command')); } -export function registerCommands(program: Command) { - program - .command('build-workspace [packages...]') - .addOption( - new Option( - '--alwaysYarnPack', - 'Alias for --alwaysPack for backwards compatibility.', - ) - .implies({ alwaysPack: true }) - .hideHelp(true), - ) - .option( - '--alwaysPack', - 'Force workspace output to be a result of running `yarn pack` on each package (warning: very slow)', - ) - .description('Builds a temporary dist workspace from the provided packages') - .action(lazy(() => import('./commands/buildWorkspace'), 'default')); -} +export const buildPlugin = createCliPlugin({ + pluginId: 'build', + init: async reg => { + reg.addCommand({ + path: ['package', 'build'], + description: 'Build a package for production deployment or publishing', + execute: async ({ args }) => { + const command = new Command(); + + const defaultCommand = command + .option( + '--role ', + 'Run the command with an explicit package role', + ) + .option( + '--minify', + 'Minify the generated code. Does not apply to app package (app is minified by default).', + ) + .option( + '--skip-build-dependencies', + 'Skip the automatic building of local dependencies. Applies to backend packages only.', + ) + .option( + '--stats', + 'If bundle stats are available, write them to the output directory. Applies to app packages only.', + ) + .option( + '--config ', + 'Config files to load instead of app-config.yaml. Applies to app packages only.', + (opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]), + Array(), + ) + .action(lazy(() => import('./commands/package/build'), 'command')); + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['repo', 'build'], + description: + 'Build packages in the project, excluding bundled app and backend packages.', + execute: async ({ args }) => { + const command = new Command(); + + // This command expect `package build` to be registered, as its used to parse + // individual plugins' package build scripts. + registerPackageCommands(command.command('package')); + + const defaultCommand = command + .option( + '--all', + 'Build all packages, including bundled app and backend packages.', + ) + .option( + '--since ', + 'Only build packages and their dev dependents that changed since the specified ref', + ) + .option( + '--minify', + 'Minify the generated code. Does not apply to app package (app is minified by default).', + ) + .action(lazy(() => import('./commands/repo/build'), 'command')); + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['package', 'start'], + description: 'Start a package for local development', + execute: async ({ args }) => { + const command = new Command(); + + const defaultCommand = command + .option(...configOption) + .option( + '--role ', + 'Run the command with an explicit package role', + ) + .option('--check', 'Enable type checking and linting if available') + .option('--inspect [host]', 'Enable debugger in Node.js environments') + .option( + '--inspect-brk [host]', + 'Enable debugger in Node.js environments, breaking before code starts', + ) + .option( + '--require ', + 'Add a --require argument to the node process', + ) + .option( + '--link ', + 'Link an external workspace for module resolution', + ) + .action(lazy(() => import('./commands/package/start'), 'command')); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['repo', 'start'], + description: 'Starts packages in the repo for local development', + execute: async ({ args }) => { + const command = new Command(); + + const defaultCommand = command + .argument( + '[packageNameOrPath...]', + 'Run the specified package instead of the defaults.', + ) + .option( + '--plugin ', + 'Start the dev entry-point for any matching plugin package in the repo', + (opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]), + Array(), + ) + .option(...configOption) + .option( + '--inspect [host]', + 'Enable debugger in Node.js environments. Applies to backend package only', + ) + .option( + '--inspect-brk [host]', + 'Enable debugger in Node.js environments, breaking before code starts. Applies to backend package only', + ) + .option( + '--require ', + 'Add a --require argument to the node process. Applies to backend package only', + ) + .option( + '--link ', + 'Link an external workspace for module resolution', + ) + .action( + lazy(() => import('../build/commands/repo/start'), 'command'), + ); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['build-workspace'], + description: + 'Builds a temporary dist workspace from the provided packages', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command + .arguments(' [packages...]') + .addOption( + new Option( + '--alwaysYarnPack', + 'Alias for --alwaysPack for backwards compatibility.', + ) + .implies({ alwaysPack: true }) + .hideHelp(true), + ) + .option( + '--alwaysPack', + 'Force workspace output to be a result of running `yarn pack` on each package (warning: very slow)', + ) + .action(lazy(() => import('./commands/buildWorkspace'), 'default')); + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + }, +}); + +export default buildPlugin; diff --git a/packages/cli/src/modules/build/lib/bundler/config.ts b/packages/cli/src/modules/build/lib/bundler/config.ts index d7c824e3fa..655ce4fd8b 100644 --- a/packages/cli/src/modules/build/lib/bundler/config.ts +++ b/packages/cli/src/modules/build/lib/bundler/config.ts @@ -15,8 +15,7 @@ */ import { BundlingOptions, ModuleFederationOptions } from './types'; -import { resolve as resolvePath, dirname } from 'path'; -import chalk from 'chalk'; +import { resolve as resolvePath } from 'path'; import webpack from 'webpack'; import { BundlingPaths } from './paths'; @@ -39,8 +38,6 @@ import { hasReactDomClient } from './hasReactDomClient'; import { createWorkspaceLinkingPlugins } from './linkWorkspaces'; import { ConfigInjectingHtmlWebpackPlugin } from './ConfigInjectingHtmlWebpackPlugin'; -const BUILD_CACHE_ENV_VAR = 'BACKSTAGE_CLI_EXPERIMENTAL_BUILD_CACHE'; - export function resolveBaseUrl( config: Config, moduleFederation?: ModuleFederationOptions, @@ -231,8 +228,8 @@ export async function createConfig( const isRemote = options.moduleFederation?.mode === 'remote'; const AdaptedModuleFederationPlugin = rspack - ? (rspack.container - .ModuleFederationPlugin as unknown as typeof ModuleFederationPlugin) + ? (require('@module-federation/enhanced/rspack') + .ModuleFederationPlugin as typeof ModuleFederationPlugin) : ModuleFederationPlugin; const exposes = options.moduleFederation?.exposes @@ -348,49 +345,6 @@ export async function createConfig( const mode = isDev ? 'development' : 'production'; const optimization = optimizationConfig(options); - if ( - mode === 'production' && - process.env.EXPERIMENTAL_MODULE_FEDERATION && - process.env.FORCE_REACT_DEVELOPMENT - ) { - console.log( - chalk.yellow( - `⚠️ WARNING: Forcing react and react-dom into development mode. This build should not be used in production.`, - ), - ); - - const reactPackageDirs = [ - `${dirname(require.resolve('react/package.json'))}/`, - `${dirname(require.resolve('react-dom/package.json'))}/`, - ]; - - // Don't define process.env.NODE_ENV with value matching config.mode. If we - // don't set this to false, webpack will define the value of - // process.env.NODE_ENV for us, and the definition below will be ignored. - optimization.nodeEnv = false; - - // Instead, provide a custom definition which always uses "development" if - // the module is part of `react` or `react-dom`, and `config.mode` otherwise. - plugins.push( - new bundler.DefinePlugin({ - 'process.env.NODE_ENV': rspack - ? // FIXME: see also https://github.com/web-infra-dev/rspack/issues/5606 - JSON.stringify(mode) - : webpack.DefinePlugin.runtimeValue(({ module }) => { - if ( - reactPackageDirs.some(val => module.resource.startsWith(val)) - ) { - return '"development"'; - } - - return `"${mode}"`; - }), - }), - ); - } - - const withCache = yn(process.env[BUILD_CACHE_ENV_VAR], { default: false }); - return { mode, profile: false, @@ -473,13 +427,5 @@ export async function createConfig( }), }, plugins, - ...(withCache && { - cache: { - type: 'filesystem', - buildDependencies: { - config: [__filename], - }, - }, - }), }; } diff --git a/packages/cli/src/modules/start/lib/ipc/IpcServer.ts b/packages/cli/src/modules/build/lib/ipc/IpcServer.ts similarity index 100% rename from packages/cli/src/modules/start/lib/ipc/IpcServer.ts rename to packages/cli/src/modules/build/lib/ipc/IpcServer.ts diff --git a/packages/cli/src/modules/start/lib/ipc/ServerDataStore.ts b/packages/cli/src/modules/build/lib/ipc/ServerDataStore.ts similarity index 100% rename from packages/cli/src/modules/start/lib/ipc/ServerDataStore.ts rename to packages/cli/src/modules/build/lib/ipc/ServerDataStore.ts diff --git a/packages/cli/src/modules/start/lib/ipc/index.ts b/packages/cli/src/modules/build/lib/ipc/index.ts similarity index 100% rename from packages/cli/src/modules/start/lib/ipc/index.ts rename to packages/cli/src/modules/build/lib/ipc/index.ts diff --git a/packages/cli/src/modules/start/lib/runner/index.ts b/packages/cli/src/modules/build/lib/runner/index.ts similarity index 100% rename from packages/cli/src/modules/start/lib/runner/index.ts rename to packages/cli/src/modules/build/lib/runner/index.ts diff --git a/packages/cli/src/modules/start/lib/runner/runBackend.ts b/packages/cli/src/modules/build/lib/runner/runBackend.ts similarity index 100% rename from packages/cli/src/modules/start/lib/runner/runBackend.ts rename to packages/cli/src/modules/build/lib/runner/runBackend.ts diff --git a/packages/cli/src/modules/config/alpha.ts b/packages/cli/src/modules/config/alpha.ts deleted file mode 100644 index 43bce858a1..0000000000 --- a/packages/cli/src/modules/config/alpha.ts +++ /dev/null @@ -1,137 +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 { createCliPlugin } from '../../wiring/factory'; -import yargs from 'yargs'; -import { Command } from 'commander'; -import { lazy } from '../../lib/lazy'; - -export default createCliPlugin({ - pluginId: 'config', - init: async reg => { - reg.addCommand({ - path: ['config:docs'], - description: 'Browse the configuration reference documentation', - execute: async ({ args }) => { - const command = new Command(); - const defaultCommand = command - .option( - '--package ', - 'Only include the schema that applies to the given package', - ) - .description('Browse the configuration reference documentation') - .action(lazy(() => import('./commands/docs'), 'default')); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - reg.addCommand({ - path: ['config', 'docs'], - description: 'Browse the configuration reference documentation', - execute: async ({ args, info }) => { - await new Command(info.usage) - .option( - '--package ', - 'Only include the schema that applies to the given package', - ) - .description(info.description) - .action(lazy(() => import('./commands/docs'), 'default')) - .parseAsync(args, { from: 'user' }); - }, - }); - reg.addCommand({ - path: ['config:print'], - description: 'Print the app configuration for the current package', - execute: async ({ args, info }) => { - const argv = await yargs - .options({ - package: { type: 'string' }, - lax: { type: 'boolean' }, - frontend: { type: 'boolean' }, - 'with-secrets': { type: 'boolean' }, - format: { type: 'string' }, - config: { type: 'string', array: true, default: [] }, - }) - .usage('$0', info.description) - .help() - .parse(args); - const m = - (await require('./commands/print')) as typeof import('./commands/print'); - await m.default(argv); - }, - }); - reg.addCommand({ - path: ['config:check'], - description: - 'Validate that the given configuration loads and matches schema', - execute: async ({ args }) => { - const argv = await yargs - .options({ - package: { type: 'string' }, - lax: { type: 'boolean' }, - frontend: { type: 'boolean' }, - deprecated: { type: 'boolean' }, - strict: { type: 'boolean', required: true }, - config: { - type: 'string', - array: true, - default: [], - }, - }) - .help() - .parse(args); - const m = - (await require('./commands/validate')) as typeof import('./commands/validate'); - await m.default(argv); - }, - }); - - reg.addCommand({ - path: ['config:schema'], - description: 'Print the JSON schema for the given configuration', - execute: async ({ args }) => { - const argv = await yargs - .options({ - package: { type: 'string' }, - format: { type: 'string' }, - merge: { type: 'boolean' }, - 'no-merge': { type: 'boolean' }, - }) - .help() - .parse(args); - const m = await import('./commands/schema'); - await m.default(argv); - }, - }); - - reg.addCommand({ - path: ['config', 'schema'], - description: 'Print the JSON schema for the given configuration', - execute: async ({ args }) => { - const argv = await yargs - .options({ - package: { type: 'string' }, - format: { type: 'string' }, - merge: { type: 'boolean' }, - 'no-merge': { type: 'boolean' }, - }) - .help() - .parse(args); - const m = await import('./commands/schema'); - await m.default(argv); - }, - }); - }, -}); diff --git a/packages/cli/src/modules/config/commands/docs.ts b/packages/cli/src/modules/config/commands/docs.ts index 25e3a864f4..e7d3ac13ee 100644 --- a/packages/cli/src/modules/config/commands/docs.ts +++ b/packages/cli/src/modules/config/commands/docs.ts @@ -19,6 +19,7 @@ import { mergeConfigSchemas } from '@backstage/config-loader'; import { OptionValues } from 'commander'; import { JSONSchema7 as JSONSchema } from 'json-schema'; import openBrowser from 'react-dev-utils/openBrowser'; +import chalk from 'chalk'; import { loadCliConfig } from '../lib/config'; const DOCS_URL = 'https://config.backstage.io'; @@ -36,5 +37,23 @@ export default async (opts: OptionValues) => { ), ); - openBrowser(`${DOCS_URL}#schema=${JSON.stringify(schema)}`); + const url = `${DOCS_URL}#schema=${JSON.stringify(schema)}`; + + console.log(); + console.log( + chalk.cyan( + 'Opening configuration reference documentation in your browser...', + ), + ); + console.log(` ${chalk.cyan(url)}`); + console.log(); + + const opened = openBrowser(url); + + if (!opened) { + console.log( + chalk.yellow('⚠️ WARNING: Unable to open browser automatically.'), + ); + console.log(chalk.yellow('Please open the URL manually in your browser.')); + } }; diff --git a/packages/cli/src/modules/config/index.ts b/packages/cli/src/modules/config/index.ts index a1436d0c21..d62dbd4d26 100644 --- a/packages/cli/src/modules/config/index.ts +++ b/packages/cli/src/modules/config/index.ts @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import { createCliPlugin } from '../../wiring/factory'; +import yargs from 'yargs'; import { Command } from 'commander'; import { lazy } from '../../lib/lazy'; @@ -24,64 +25,114 @@ export const configOption = [ Array(), ] as const; -export function registerCommands(program: Command) { - program - .command('config:docs') - .option( - '--package ', - 'Only include the schema that applies to the given package', - ) - .description('Browse the configuration reference documentation') - .action(lazy(() => import('./commands/docs'), 'default')); +export default createCliPlugin({ + pluginId: 'config', + init: async reg => { + reg.addCommand({ + path: ['config:docs'], + description: 'Browse the configuration reference documentation', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command + .option( + '--package ', + 'Only include the schema that applies to the given package', + ) + .description('Browse the configuration reference documentation') + .action(lazy(() => import('./commands/docs'), 'default')); - program - .command('config:print') - .option( - '--package ', - 'Only load config schema that applies to the given package', - ) - .option('--lax', 'Do not require environment variables to be set') - .option('--frontend', 'Print only the frontend configuration') - .option('--with-secrets', 'Include secrets in the printed configuration') - .option( - '--format ', - 'Format to print the configuration in, either json or yaml [yaml]', - ) - .option(...configOption) - .description('Print the app configuration for the current package') - .action(lazy(() => import('./commands/print'), 'default')); + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + reg.addCommand({ + path: ['config', 'docs'], + description: 'Browse the configuration reference documentation', + execute: async ({ args, info }) => { + await new Command(info.usage) + .option( + '--package ', + 'Only include the schema that applies to the given package', + ) + .description(info.description) + .action(lazy(() => import('./commands/docs'), 'default')) + .parseAsync(args, { from: 'user' }); + }, + }); + reg.addCommand({ + path: ['config:print'], + description: 'Print the app configuration for the current package', + execute: async ({ args, info }) => { + const argv = await yargs + .options({ + package: { type: 'string' }, + lax: { type: 'boolean' }, + frontend: { type: 'boolean' }, + 'with-secrets': { type: 'boolean' }, + format: { type: 'string' }, + config: { type: 'string', array: true, default: [] }, + }) + .usage('$0', info.description) + .help() + .parse(args); + await lazy(() => import('./commands/print'), 'default')(argv); + }, + }); + reg.addCommand({ + path: ['config:check'], + description: + 'Validate that the given configuration loads and matches schema', + execute: async ({ args }) => { + const argv = await yargs + .options({ + package: { type: 'string' }, + lax: { type: 'boolean' }, + frontend: { type: 'boolean' }, + deprecated: { type: 'boolean' }, + strict: { type: 'boolean' }, + config: { + type: 'string', + array: true, + default: [], + }, + }) + .help() + .parse(args); + await lazy(() => import('./commands/validate'), 'default')(argv); + }, + }); - program - .command('config:check') - .option( - '--package ', - 'Only load config schema that applies to the given package', - ) - .option('--lax', 'Do not require environment variables to be set') - .option('--frontend', 'Only validate the frontend configuration') - .option('--deprecated', 'Output deprecated configuration settings') - .option( - '--strict', - 'Enable strict config validation, forbidding errors and unknown keys', - ) - .option(...configOption) - .description( - 'Validate that the given configuration loads and matches schema', - ) - .action(lazy(() => import('./commands/validate'), 'default')); + reg.addCommand({ + path: ['config:schema'], + description: 'Print the JSON schema for the given configuration', + execute: async ({ args }) => { + const argv = await yargs + .options({ + package: { type: 'string' }, + format: { type: 'string' }, + merge: { type: 'boolean' }, + 'no-merge': { type: 'boolean' }, + }) + .help() + .parse(args); + await lazy(() => import('./commands/schema'), 'default')(argv); + }, + }); - program - .command('config:schema') - .option( - '--package ', - 'Only output config schema that applies to the given package', - ) - .option( - '--format ', - 'Format to print the schema in, either json or yaml [yaml]', - ) - .option('--merge', 'Print the config schemas merged', true) - .option('--no-merge', 'Print the config schemas not merged') - .description('Print configuration schema') - .action(lazy(() => import('./commands/schema'), 'default')); -} + reg.addCommand({ + path: ['config', 'schema'], + description: 'Print the JSON schema for the given configuration', + execute: async ({ args }) => { + const argv = await yargs + .options({ + package: { type: 'string' }, + format: { type: 'string' }, + merge: { type: 'boolean' }, + 'no-merge': { type: 'boolean' }, + }) + .help() + .parse(args); + await lazy(() => import('./commands/schema'), 'default')(argv); + }, + }); + }, +}); diff --git a/packages/cli/src/modules/create-github-app/alpha.ts b/packages/cli/src/modules/create-github-app/alpha.ts deleted file mode 100644 index da1e04aaac..0000000000 --- a/packages/cli/src/modules/create-github-app/alpha.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { createCliPlugin } from '../../wiring/factory'; -import { Command } from 'commander'; -import { lazy } from '../../lib/lazy'; - -export default createCliPlugin({ - pluginId: 'new', - init: async reg => { - reg.addCommand({ - path: ['create-github-app'], - description: 'Create new GitHub App in your organization.', - execute: async ({ args }) => { - const command = new Command(); - const defaultCommand = command - .argument('') - .action( - lazy(() => import('./commands/create-github-app'), 'default'), - ); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - }, -}); diff --git a/packages/cli/src/modules/create-github-app/index.ts b/packages/cli/src/modules/create-github-app/index.ts index f69230cd77..da1e04aaac 100644 --- a/packages/cli/src/modules/create-github-app/index.ts +++ b/packages/cli/src/modules/create-github-app/index.ts @@ -13,17 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { createCliPlugin } from '../../wiring/factory'; import { Command } from 'commander'; import { lazy } from '../../lib/lazy'; -export function registerCommands(program: Command) { - program - .command('create-github-app ') - .description('Create new GitHub App in your organization.') - .action( - lazy( - () => import('../create-github-app/commands/create-github-app'), - 'default', - ), - ); -} +export default createCliPlugin({ + pluginId: 'new', + init: async reg => { + reg.addCommand({ + path: ['create-github-app'], + description: 'Create new GitHub App in your organization.', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command + .argument('') + .action( + lazy(() => import('./commands/create-github-app'), 'default'), + ); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + }, +}); diff --git a/packages/cli/src/modules/info/index.ts b/packages/cli/src/modules/info/index.ts index af43af652c..1e6f761f83 100644 --- a/packages/cli/src/modules/info/index.ts +++ b/packages/cli/src/modules/info/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import yargs from 'yargs'; +import { createCliPlugin } from '../../wiring/factory'; -import { lazy } from '../../lib/lazy'; -import { Command } from 'commander'; - -export function registerCommands(program: Command) { - program - .command('info') - .description('Show helpful information for debugging and reporting bugs') - .action(lazy(() => import('./commands/info'), 'default')); -} +export default createCliPlugin({ + pluginId: 'info', + init: async reg => { + reg.addCommand({ + path: ['info'], + description: 'Show helpful information for debugging and reporting bugs', + execute: async ({ args }) => { + yargs().parse(args); + const { default: command } = + require('./commands/info') as typeof import('./commands/info'); + await command(); + }, + }); + }, +}); diff --git a/packages/cli/src/modules/lint/alpha.ts b/packages/cli/src/modules/lint/alpha.ts deleted file mode 100644 index 617f71d5c9..0000000000 --- a/packages/cli/src/modules/lint/alpha.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { createCliPlugin } from '../../wiring/factory'; -import { Command } from 'commander'; -import { lazy } from '../../lib/lazy'; - -export default createCliPlugin({ - pluginId: 'lint', - init: async reg => { - reg.addCommand({ - path: ['package', 'lint'], - description: 'Lint a package', - execute: async ({ args }) => { - const command = new Command(); - command.arguments('[directories...]'); - command.option('--fix', 'Attempt to automatically fix violations'); - command.option( - '--format ', - 'Lint report output format', - 'eslint-formatter-friendly', - ); - command.option( - '--output-file ', - 'Write the lint report to a file instead of stdout', - ); - command.option( - '--max-warnings ', - 'Fail if more than this number of warnings. -1 allows warnings. (default: -1)', - ); - command.description('Lint a package'); - command.action( - lazy(() => import('./commands/package/lint'), 'default'), - ); - - await command.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['repo', 'lint'], - description: 'Lint a repository', - execute: async ({ args }) => { - const command = new Command(); - command.option('--fix', 'Attempt to automatically fix violations'); - command.option( - '--format ', - 'Lint report output format', - 'eslint-formatter-friendly', - ); - command.option( - '--output-file ', - 'Write the lint report to a file instead of stdout', - ); - command.option( - '--successCache', - 'Enable success caching, which skips running tests for unchanged packages that were successful in the previous run', - ); - command.option( - '--successCacheDir ', - 'Set the success cache location, (default: node_modules/.cache/backstage-cli)', - ); - command.option( - '--since ', - 'Only lint packages that changed since the specified ref', - ); - command.option( - '--max-warnings ', - 'Fail if more than this number of warnings. -1 allows warnings. (default: -1)', - ); - command.description('Lint a repository'); - command.action(lazy(() => import('./commands/repo/lint'), 'command')); - - await command.parseAsync(args, { from: 'user' }); - }, - }); - }, -}); diff --git a/packages/cli/src/modules/lint/commands/repo/lint.ts b/packages/cli/src/modules/lint/commands/repo/lint.ts index 72bb02a693..f433740886 100644 --- a/packages/cli/src/modules/lint/commands/repo/lint.ts +++ b/packages/cli/src/modules/lint/commands/repo/lint.ts @@ -220,6 +220,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { }); const outputSuccessCache = []; + const jsonResults = []; let errorOutput = ''; @@ -238,7 +239,11 @@ export async function command(opts: OptionValues, cmd: Command): Promise { // dump of all warnings that might be irrelevant if (resultText) { if (opts.outputFile) { - errorOutput += `${resultText}\n`; + if (opts.format === 'json') { + jsonResults.push(resultText); + } else { + errorOutput += `${resultText}\n`; + } } else { console.log(); console.log(resultText.trimStart()); @@ -249,6 +254,14 @@ export async function command(opts: OptionValues, cmd: Command): Promise { } } + if (opts.format === 'json') { + let mergedJsonResults: any[] = []; + for (const jsonResult of jsonResults) { + mergedJsonResults = mergedJsonResults.concat(JSON.parse(jsonResult)); + } + errorOutput = JSON.stringify(mergedJsonResults, null, 2); + } + if (opts.outputFile && errorOutput) { await fs.writeFile(paths.resolveTargetRoot(opts.outputFile), errorOutput); } diff --git a/packages/cli/src/modules/lint/index.ts b/packages/cli/src/modules/lint/index.ts index dfa0916efb..6cba232da9 100644 --- a/packages/cli/src/modules/lint/index.ts +++ b/packages/cli/src/modules/lint/index.ts @@ -13,59 +13,83 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { createCliPlugin } from '../../wiring/factory'; import { Command } from 'commander'; import { lazy } from '../../lib/lazy'; -export function registerPackageCommands(command: Command) { - command - .command('lint [directories...]') - .option( - '--format ', - 'Lint report output format', - 'eslint-formatter-friendly', - ) - .option( - '--output-file ', - 'Write the lint report to a file instead of stdout', - ) - .option('--fix', 'Attempt to automatically fix violations') - .option( - '--max-warnings ', - 'Fail if more than this number of warnings. -1 allows warnings. (default: -1)', - ) - .description('Lint a package') - .action(lazy(() => import('./commands/package/lint'), 'default')); +export function registerPackageLintCommand(command: Command) { + command.arguments('[directories...]'); + command.option('--fix', 'Attempt to automatically fix violations'); + command.option( + '--format ', + 'Lint report output format', + 'eslint-formatter-friendly', + ); + command.option( + '--output-file ', + 'Write the lint report to a file instead of stdout', + ); + command.option( + '--max-warnings ', + 'Fail if more than this number of warnings. -1 allows warnings. (default: -1)', + ); + command.description('Lint a package'); + command.action(lazy(() => import('./commands/package/lint'), 'default')); } -export function registerRepoCommands(command: Command) { - command - .command('lint') - .description('Lint all packages in the project') - .option( - '--format ', - 'Lint report output format', - 'eslint-formatter-friendly', - ) - .option( - '--output-file ', - 'Write the lint report to a file instead of stdout', - ) - .option( - '--since ', - 'Only lint packages that changed since the specified ref', - ) - .option( - '--successCache', - 'Enable success caching, which skips running tests for unchanged packages that were successful in the previous run', - ) - .option( - '--successCacheDir ', - 'Set the success cache location, (default: node_modules/.cache/backstage-cli)', - ) - .option( - '--max-warnings ', - 'Fail if more than this number of warnings. -1 allows warnings. (default: -1)', - ) - .option('--fix', 'Attempt to automatically fix violations') - .action(lazy(() => import('./commands/repo/lint'), 'command')); -} +export default createCliPlugin({ + pluginId: 'lint', + init: async reg => { + reg.addCommand({ + path: ['package', 'lint'], + description: 'Lint a package', + execute: async ({ args }) => { + const command = new Command(); + registerPackageLintCommand(command); + + await command.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['repo', 'lint'], + description: 'Lint a repository', + execute: async ({ args }) => { + const command = new Command(); + + registerPackageLintCommand(command.command('package').command('lint')); + + command.option('--fix', 'Attempt to automatically fix violations'); + command.option( + '--format ', + 'Lint report output format', + 'eslint-formatter-friendly', + ); + command.option( + '--output-file ', + 'Write the lint report to a file instead of stdout', + ); + command.option( + '--successCache', + 'Enable success caching, which skips running tests for unchanged packages that were successful in the previous run', + ); + command.option( + '--successCacheDir ', + 'Set the success cache location, (default: node_modules/.cache/backstage-cli)', + ); + command.option( + '--since ', + 'Only lint packages that changed since the specified ref', + ); + command.option( + '--max-warnings ', + 'Fail if more than this number of warnings. -1 allows warnings. (default: -1)', + ); + command.description('Lint a repository'); + command.action(lazy(() => import('./commands/repo/lint'), 'command')); + + await command.parseAsync(args, { from: 'user' }); + }, + }); + }, +}); diff --git a/packages/cli/src/modules/maintenance/alpha.ts b/packages/cli/src/modules/maintenance/alpha.ts deleted file mode 100644 index 41192afdc4..0000000000 --- a/packages/cli/src/modules/maintenance/alpha.ts +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { Command } from 'commander'; -import { createCliPlugin } from '../../wiring/factory'; -import { lazy } from '../../lib/lazy'; - -export default createCliPlugin({ - pluginId: 'maintenance', - init: async reg => { - reg.addCommand({ - path: ['package', 'clean'], - description: 'Delete cache directories', - execute: async ({ args }) => { - const command = new Command(); - const defaultCommand = command.action( - lazy(() => import('./commands/package/clean'), 'default'), - ); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['package', 'prepack'], - description: 'Prepares a package for packaging before publishing', - execute: async ({ args }) => { - const command = new Command(); - const defaultCommand = command.action( - lazy(() => import('./commands/package/pack'), 'pre'), - ); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['package', 'postpack'], - description: 'Restores the changes made by the prepack command', - execute: async ({ args }) => { - const command = new Command(); - const defaultCommand = command.action( - lazy(() => import('./commands/package/pack'), 'post'), - ); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['repo', 'fix'], - description: 'Automatically fix packages in the project', - execute: async ({ args }) => { - const command = new Command(); - const defaultCommand = command - .option( - '--publish', - 'Enable additional fixes that only apply when publishing packages', - ) - .option( - '--check', - 'Fail if any packages would have been changed by the command', - ) - .action(lazy(() => import('./commands/repo/fix'), 'command')); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['repo', 'clean'], - description: 'Delete cache and output directories', - execute: async ({ args }) => { - const command = new Command(); - const defaultCommand = command.action( - lazy(() => import('./commands/repo/clean'), 'command'), - ); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['repo', 'list-deprecations'], - description: 'List deprecations', - execute: async ({ args }) => { - const command = new Command(); - const defaultCommand = command - .option('--json', 'Output as JSON') - .action( - lazy(() => import('./commands/repo/list-deprecations'), 'command'), - ); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - }, -}); diff --git a/packages/cli/src/modules/maintenance/index.ts b/packages/cli/src/modules/maintenance/index.ts index 48669e9c34..41192afdc4 100644 --- a/packages/cli/src/modules/maintenance/index.ts +++ b/packages/cli/src/modules/maintenance/index.ts @@ -13,49 +13,98 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { Command } from 'commander'; +import { createCliPlugin } from '../../wiring/factory'; import { lazy } from '../../lib/lazy'; -export function registerPackageCommands(command: Command) { - command - .command('clean') - .description('Delete cache directories') - .action(lazy(() => import('./commands/package/clean'), 'default')); +export default createCliPlugin({ + pluginId: 'maintenance', + init: async reg => { + reg.addCommand({ + path: ['package', 'clean'], + description: 'Delete cache directories', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command.action( + lazy(() => import('./commands/package/clean'), 'default'), + ); - command - .command('prepack') - .description('Prepares a package for packaging before publishing') - .action(lazy(() => import('./commands/package/pack'), 'pre')); + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); - command - .command('postpack') - .description('Restores the changes made by the prepack command') - .action(lazy(() => import('./commands/package/pack'), 'post')); -} + reg.addCommand({ + path: ['package', 'prepack'], + description: 'Prepares a package for packaging before publishing', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command.action( + lazy(() => import('./commands/package/pack'), 'pre'), + ); -export function registerRepoCommands(command: Command) { - command - .command('fix') - .description('Automatically fix packages in the project') - .option( - '--publish', - 'Enable additional fixes that only apply when publishing packages', - ) - .option( - '--check', - 'Fail if any packages would have been changed by the command', - ) - .action(lazy(() => import('./commands/repo/fix'), 'command')); + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); - command - .command('clean') - .description('Delete cache and output directories') - .action(lazy(() => import('./commands/repo/clean'), 'command')); + reg.addCommand({ + path: ['package', 'postpack'], + description: 'Restores the changes made by the prepack command', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command.action( + lazy(() => import('./commands/package/pack'), 'post'), + ); - command - .command('list-deprecations') - .description('List deprecations') - .option('--json', 'Output as JSON') - .action(lazy(() => import('./commands/repo/list-deprecations'), 'command')); -} + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['repo', 'fix'], + description: 'Automatically fix packages in the project', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command + .option( + '--publish', + 'Enable additional fixes that only apply when publishing packages', + ) + .option( + '--check', + 'Fail if any packages would have been changed by the command', + ) + .action(lazy(() => import('./commands/repo/fix'), 'command')); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['repo', 'clean'], + description: 'Delete cache and output directories', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command.action( + lazy(() => import('./commands/repo/clean'), 'command'), + ); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['repo', 'list-deprecations'], + description: 'List deprecations', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command + .option('--json', 'Output as JSON') + .action( + lazy(() => import('./commands/repo/list-deprecations'), 'command'), + ); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + }, +}); diff --git a/packages/cli/src/modules/migrate/alpha.ts b/packages/cli/src/modules/migrate/alpha.ts deleted file mode 100644 index ac56c06b7a..0000000000 --- a/packages/cli/src/modules/migrate/alpha.ts +++ /dev/null @@ -1,135 +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 { createCliPlugin } from '../../wiring/factory'; -import { Command } from 'commander'; -import { lazy } from '../../lib/lazy'; - -export default createCliPlugin({ - pluginId: 'migrate', - init: async reg => { - reg.addCommand({ - path: ['versions:migrate'], - description: - 'Migrate any plugins that have been moved to the @backstage-community namespace automatically', - execute: async ({ args }) => { - const command = new Command(); - const defaultCommand = command - .option( - '--pattern ', - 'Override glob for matching packages to upgrade', - ) - .option( - '--skip-code-changes', - 'Skip code changes and only update package.json files', - ) - .action(lazy(() => import('./commands/versions/migrate'), 'default')); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['versions:bump'], - description: 'Bump Backstage packages to the latest versions', - execute: async ({ args }) => { - const command = new Command(); - - const defaultCommand = command - .option( - '--pattern ', - 'Override glob for matching packages to upgrade', - ) - .option( - '--release ', - 'Bump to a specific Backstage release line or version', - 'main', - ) - .option('--skip-install', 'Skips yarn install step') - .option('--skip-migrate', 'Skips migration of any moved packages') - .action(lazy(() => import('./commands/versions/bump'), 'default')); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['migrate', 'package-roles'], - description: `Add package role field to packages that don't have it`, - execute: async ({ args }) => { - const command = new Command(); - const defaultCommand = command.action( - lazy(() => import('./commands/packageRole'), 'default'), - ); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['migrate', 'package-scripts'], - description: 'Set package scripts according to each package role', - execute: async ({ args }) => { - const command = new Command(); - const defaultCommand = command.action( - lazy(() => import('./commands/packageScripts'), 'command'), - ); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['migrate', 'package-exports'], - description: 'Synchronize package subpath export definitions', - execute: async ({ args }) => { - const command = new Command(); - const defaultCommand = command.action( - lazy(() => import('./commands/packageExports'), 'command'), - ); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['migrate', 'package-lint-configs'], - description: - 'Migrates all packages to use @backstage/cli/config/eslint-factory', - execute: async ({ args }) => { - const command = new Command(); - const defaultCommand = command.action( - lazy(() => import('./commands/packageLintConfigs'), 'command'), - ); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['migrate', 'react-router-deps'], - description: - 'Migrates the react-router dependencies for all packages to be peer dependencies', - execute: async ({ args }) => { - const command = new Command(); - const defaultCommand = command.action( - lazy(() => import('./commands/reactRouterDeps'), 'command'), - ); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - }, -}); diff --git a/packages/cli/src/modules/migrate/index.ts b/packages/cli/src/modules/migrate/index.ts index 10e96ad173..ac56c06b7a 100644 --- a/packages/cli/src/modules/migrate/index.ts +++ b/packages/cli/src/modules/migrate/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,72 +13,123 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { lazy } from '../../lib/lazy'; +import { createCliPlugin } from '../../wiring/factory'; import { Command } from 'commander'; +import { lazy } from '../../lib/lazy'; -export function registerCommands(program: Command) { - program - .command('versions:bump') - .option( - '--pattern ', - 'Override glob for matching packages to upgrade', - ) - .option( - '--release ', - 'Bump to a specific Backstage release line or version', - 'main', - ) - .option('--skip-install', 'Skips yarn install step') - .option('--skip-migrate', 'Skips migration of any moved packages') - .description('Bump Backstage packages to the latest versions') - .action(lazy(() => import('./commands/versions/bump'), 'default')); +export default createCliPlugin({ + pluginId: 'migrate', + init: async reg => { + reg.addCommand({ + path: ['versions:migrate'], + description: + 'Migrate any plugins that have been moved to the @backstage-community namespace automatically', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command + .option( + '--pattern ', + 'Override glob for matching packages to upgrade', + ) + .option( + '--skip-code-changes', + 'Skip code changes and only update package.json files', + ) + .action(lazy(() => import('./commands/versions/migrate'), 'default')); - program - .command('versions:migrate') - .option( - '--pattern ', - 'Override glob for matching packages to upgrade', - ) - .option( - '--skip-code-changes', - 'Skip code changes and only update package.json files', - ) - .description( - 'Migrate any plugins that have been moved to the @backstage-community namespace automatically', - ) - .action(lazy(() => import('./commands/versions/migrate'), 'default')); + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); - const command = program - .command('migrate [command]') - .description('Migration utilities'); + reg.addCommand({ + path: ['versions:bump'], + description: 'Bump Backstage packages to the latest versions', + execute: async ({ args }) => { + const command = new Command(); - command - .command('package-roles') - .description(`Add package role field to packages that don't have it`) - .action(lazy(() => import('./commands/packageRole'), 'default')); + const defaultCommand = command + .option( + '--pattern ', + 'Override glob for matching packages to upgrade', + ) + .option( + '--release ', + 'Bump to a specific Backstage release line or version', + 'main', + ) + .option('--skip-install', 'Skips yarn install step') + .option('--skip-migrate', 'Skips migration of any moved packages') + .action(lazy(() => import('./commands/versions/bump'), 'default')); - command - .command('package-scripts') - .description('Set package scripts according to each package role') - .action(lazy(() => import('./commands/packageScripts'), 'command')); + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); - command - .command('package-exports') - .description('Synchronize package subpath export definitions') - .action(lazy(() => import('./commands/packageExports'), 'command')); + reg.addCommand({ + path: ['migrate', 'package-roles'], + description: `Add package role field to packages that don't have it`, + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command.action( + lazy(() => import('./commands/packageRole'), 'default'), + ); - command - .command('package-lint-configs') - .description( - 'Migrates all packages to use @backstage/cli/config/eslint-factory', - ) - .action(lazy(() => import('./commands/packageLintConfigs'), 'command')); + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); - command - .command('react-router-deps') - .description( - 'Migrates the react-router dependencies for all packages to be peer dependencies', - ) - .action(lazy(() => import('./commands/reactRouterDeps'), 'command')); -} + reg.addCommand({ + path: ['migrate', 'package-scripts'], + description: 'Set package scripts according to each package role', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command.action( + lazy(() => import('./commands/packageScripts'), 'command'), + ); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['migrate', 'package-exports'], + description: 'Synchronize package subpath export definitions', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command.action( + lazy(() => import('./commands/packageExports'), 'command'), + ); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['migrate', 'package-lint-configs'], + description: + 'Migrates all packages to use @backstage/cli/config/eslint-factory', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command.action( + lazy(() => import('./commands/packageLintConfigs'), 'command'), + ); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['migrate', 'react-router-deps'], + description: + 'Migrates the react-router dependencies for all packages to be peer dependencies', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command.action( + lazy(() => import('./commands/reactRouterDeps'), 'command'), + ); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + }, +}); diff --git a/packages/cli/src/modules/new/alpha.ts b/packages/cli/src/modules/new/alpha.ts deleted file mode 100644 index 6802077507..0000000000 --- a/packages/cli/src/modules/new/alpha.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { createCliPlugin } from '../../wiring/factory'; -import { Command } from 'commander'; -import { lazy } from '../../lib/lazy'; -import { removed } from '../../lib/removed'; - -export default createCliPlugin({ - pluginId: 'new', - init: async reg => { - reg.addCommand({ - path: ['new'], - description: - 'Open up an interactive guide to creating new things in your app', - execute: async ({ args }) => { - const command = new Command(); - const defaultCommand = command - .storeOptionsAsProperties(false) - .description( - 'Open up an interactive guide to creating new things in your app', - ) - .option( - '--select ', - 'Select the thing you want to be creating upfront', - ) - .option( - '--option =', - 'Pre-fill options for the creation process', - (opt, arr: string[]) => [...arr, opt], - [], - ) - .option( - '--skip-install', - `Skips running 'yarn install' and 'yarn lint --fix'`, - ) - .option('--scope ', 'The scope to use for new packages') - .option( - '--npm-registry ', - 'The package registry to use for new packages', - ) - .option( - '--baseVersion ', - 'The version to use for any new packages (default: 0.1.0)', - ) - .option( - '--license ', - 'The license to use for any new packages (default: Apache-2.0)', - ) - .option('--no-private', 'Do not mark new packages as private') - .action(lazy(() => import('./commands/new'), 'default')); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['create'], - description: 'Create a new Backstage app', - deprecated: true, - execute: async () => { - removed("use 'backstage-cli new' instead")(); - }, - }); - reg.addCommand({ - path: ['create-plugin'], - description: 'Create a new Backstage plugin', - deprecated: true, - execute: async () => { - removed("use 'backstage-cli new' instead")(); - }, - }); - }, -}); diff --git a/packages/cli/src/modules/new/index.ts b/packages/cli/src/modules/new/index.ts index fa288f5f46..6802077507 100644 --- a/packages/cli/src/modules/new/index.ts +++ b/packages/cli/src/modules/new/index.ts @@ -13,53 +13,74 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { createCliPlugin } from '../../wiring/factory'; import { Command } from 'commander'; import { lazy } from '../../lib/lazy'; import { removed } from '../../lib/removed'; -export function registerCommands(program: Command) { - program - .command('new') - .storeOptionsAsProperties(false) - .description( - 'Open up an interactive guide to creating new things in your app', - ) - .option( - '--select ', - 'Select the thing you want to be creating upfront', - ) - .option( - '--option =', - 'Pre-fill options for the creation process', - (opt, arr: string[]) => [...arr, opt], - [], - ) - .option( - '--skip-install', - `Skips running 'yarn install' and 'yarn lint --fix'`, - ) - .option('--scope ', 'The scope to use for new packages') - .option( - '--npm-registry ', - 'The package registry to use for new packages', - ) - .option( - '--baseVersion ', - 'The version to use for any new packages (default: 0.1.0)', - ) - .option( - '--license ', - 'The license to use for any new packages (default: Apache-2.0)', - ) - .option('--no-private', 'Do not mark new packages as private') - .action(lazy(() => import('./commands/new'), 'default')); +export default createCliPlugin({ + pluginId: 'new', + init: async reg => { + reg.addCommand({ + path: ['new'], + description: + 'Open up an interactive guide to creating new things in your app', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command + .storeOptionsAsProperties(false) + .description( + 'Open up an interactive guide to creating new things in your app', + ) + .option( + '--select ', + 'Select the thing you want to be creating upfront', + ) + .option( + '--option =', + 'Pre-fill options for the creation process', + (opt, arr: string[]) => [...arr, opt], + [], + ) + .option( + '--skip-install', + `Skips running 'yarn install' and 'yarn lint --fix'`, + ) + .option('--scope ', 'The scope to use for new packages') + .option( + '--npm-registry ', + 'The package registry to use for new packages', + ) + .option( + '--baseVersion ', + 'The version to use for any new packages (default: 0.1.0)', + ) + .option( + '--license ', + 'The license to use for any new packages (default: Apache-2.0)', + ) + .option('--no-private', 'Do not mark new packages as private') + .action(lazy(() => import('./commands/new'), 'default')); - program - .command('create') - .allowUnknownOption(true) - .action(removed("use 'backstage-cli new' instead")); - program - .command('create-plugin') - .allowUnknownOption(true) - .action(removed("use 'backstage-cli new' instead")); -} + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['create'], + description: 'Create a new Backstage app', + deprecated: true, + execute: async () => { + removed("use 'backstage-cli new' instead")(); + }, + }); + reg.addCommand({ + path: ['create-plugin'], + description: 'Create a new Backstage plugin', + deprecated: true, + execute: async () => { + removed("use 'backstage-cli new' instead")(); + }, + }); + }, +}); diff --git a/packages/cli/src/modules/start/alpha.ts b/packages/cli/src/modules/start/alpha.ts deleted file mode 100644 index a5875df335..0000000000 --- a/packages/cli/src/modules/start/alpha.ts +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Command } from 'commander'; -import { createCliPlugin } from '../../wiring/factory'; -import { lazy } from '../../lib/lazy'; -import { configOption } from '../config'; - -export const startPlugin = createCliPlugin({ - pluginId: 'start', - init: async reg => { - reg.addCommand({ - path: ['package', 'start'], - description: 'Start a package for local development', - execute: async ({ args }) => { - const command = new Command(); - - const defaultCommand = command - .option(...configOption) - .option( - '--role ', - 'Run the command with an explicit package role', - ) - .option('--check', 'Enable type checking and linting if available') - .option('--inspect [host]', 'Enable debugger in Node.js environments') - .option( - '--inspect-brk [host]', - 'Enable debugger in Node.js environments, breaking before code starts', - ) - .option( - '--require ', - 'Add a --require argument to the node process', - ) - .option( - '--link ', - 'Link an external workspace for module resolution', - ) - .action(lazy(() => import('./commands/package/start'), 'command')); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['repo', 'start'], - description: 'Starts packages in the repo for local development', - execute: async ({ args }) => { - const command = new Command(); - - const defaultCommand = command - .argument( - '[...packageNameOrPath]', - 'Run the specified package instead of the defaults.', - ) - .option( - '--plugin ', - 'Start the dev entry-point for any matching plugin package in the repo', - (opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]), - Array(), - ) - .option(...configOption) - .option( - '--inspect [host]', - 'Enable debugger in Node.js environments. Applies to backend package only', - ) - .option( - '--inspect-brk [host]', - 'Enable debugger in Node.js environments, breaking before code starts. Applies to backend package only', - ) - .option( - '--require ', - 'Add a --require argument to the node process. Applies to backend package only', - ) - .option( - '--link ', - 'Link an external workspace for module resolution', - ) - .action(lazy(() => import('./commands/repo/start'), 'command')); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - }, -}); - -export default startPlugin; diff --git a/packages/cli/src/modules/start/index.ts b/packages/cli/src/modules/start/index.ts deleted file mode 100644 index 17225820f7..0000000000 --- a/packages/cli/src/modules/start/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { Command } from 'commander'; -import { lazy } from '../../lib/lazy'; -import { configOption } from '../config'; - -export function registerRepoCommands(command: Command) { - command - .command('start') - .description('Starts packages in the repo for local development') - .argument( - '[packageNameOrPath...]', - 'Run the specified package instead of the defaults.', - ) - .option( - '--plugin ', - 'Start the dev entry-point for any matching plugin package in the repo', - (opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]), - Array(), - ) - .option(...configOption) - .option( - '--inspect [host]', - 'Enable debugger in Node.js environments. Applies to backend package only', - ) - .option( - '--inspect-brk [host]', - 'Enable debugger in Node.js environments, breaking before code starts. Applies to backend package only', - ) - .option( - '--require ', - 'Add a --require argument to the node process. Applies to backend package only', - ) - .option('--link ', 'Link an external workspace for module resolution') - .action(lazy(() => import('./commands/repo/start'), 'command')); -} - -export function registerPackageCommands(command: Command) { - command - .command('start') - .description('Start a package for local development') - .option(...configOption) - .option('--role ', 'Run the command with an explicit package role') - .option('--check', 'Enable type checking and linting if available') - .option('--inspect [host]', 'Enable debugger in Node.js environments') - .option( - '--inspect-brk [host]', - 'Enable debugger in Node.js environments, breaking before code starts', - ) - .option( - '--require ', - 'Add a --require argument to the node process', - ) - .option('--link ', 'Link an external workspace for module resolution') - .action(lazy(() => import('./commands/package/start'), 'command')); -} diff --git a/packages/cli/src/modules/test/alpha.ts b/packages/cli/src/modules/test/alpha.ts deleted file mode 100644 index 1583c74997..0000000000 --- a/packages/cli/src/modules/test/alpha.ts +++ /dev/null @@ -1,64 +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 { createCliPlugin } from '../../wiring/factory'; -import { Command } from 'commander'; -import { lazy } from '../../lib/lazy'; - -export default createCliPlugin({ - pluginId: 'test', - init: async reg => { - reg.addCommand({ - path: ['repo', 'test'], - description: - 'Run tests, forwarding args to Jest, defaulting to watch mode', - execute: async ({ args }) => { - const command = new Command(); - command.allowUnknownOption(true); - command.option( - '--since ', - 'Only test packages that changed since the specified ref', - ); - command.option('--successCache', 'Enable success caching'); - command.option( - '--successCacheDir ', - 'Set the success cache location, (default: node_modules/.cache/backstage-cli)', - ); - command.option( - '--jest-help', - 'Show help for Jest CLI options, which are passed through', - ); - command.action(lazy(() => import('./commands/repo/test'), 'command')); - await command.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['package', 'test'], - description: - 'Run tests, forwarding args to Jest, defaulting to watch mode', - execute: async ({ args }) => { - const command = new Command(); - - command.allowUnknownOption(true); - command.helpOption(', --backstage-cli-help'); - command.action( - lazy(() => import('./commands/package/test'), 'default'), - ); - await command.parseAsync(args, { from: 'user' }); - }, - }); - }, -}); diff --git a/packages/cli/src/modules/test/index.ts b/packages/cli/src/modules/test/index.ts index 8e4720109f..1583c74997 100644 --- a/packages/cli/src/modules/test/index.ts +++ b/packages/cli/src/modules/test/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,39 +13,52 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import { createCliPlugin } from '../../wiring/factory'; import { Command } from 'commander'; import { lazy } from '../../lib/lazy'; -export function registerRepoCommands(command: Command) { - command - .command('test') - .allowUnknownOption(true) // Allows the command to run, but we still need to parse raw args - .option( - '--since ', - 'Only test packages that changed since the specified ref', - ) - .option( - '--successCache', - 'Enable success caching, which skips running tests for unchanged packages that were successful in the previous run', - ) - .option( - '--successCacheDir ', - 'Set the success cache location, (default: node_modules/.cache/backstage-cli)', - ) - .option( - '--jest-help', - 'Show help for Jest CLI options, which are passed through', - ) - .description('Run tests, forwarding args to Jest, defaulting to watch mode') - .action(lazy(() => import('./commands/repo/test'), 'command')); -} +export default createCliPlugin({ + pluginId: 'test', + init: async reg => { + reg.addCommand({ + path: ['repo', 'test'], + description: + 'Run tests, forwarding args to Jest, defaulting to watch mode', + execute: async ({ args }) => { + const command = new Command(); + command.allowUnknownOption(true); + command.option( + '--since ', + 'Only test packages that changed since the specified ref', + ); + command.option('--successCache', 'Enable success caching'); + command.option( + '--successCacheDir ', + 'Set the success cache location, (default: node_modules/.cache/backstage-cli)', + ); + command.option( + '--jest-help', + 'Show help for Jest CLI options, which are passed through', + ); + command.action(lazy(() => import('./commands/repo/test'), 'command')); + await command.parseAsync(args, { from: 'user' }); + }, + }); -export function registerPackageCommands(command: Command) { - command - .command('test') - .allowUnknownOption(true) // Allows the command to run, but we still need to parse raw args - .helpOption(', --backstage-cli-help') // Let Jest handle help - .description('Run tests, forwarding args to Jest, defaulting to watch mode') - .action(lazy(() => import('./commands/package/test'), 'default')); -} + reg.addCommand({ + path: ['package', 'test'], + description: + 'Run tests, forwarding args to Jest, defaulting to watch mode', + execute: async ({ args }) => { + const command = new Command(); + + command.allowUnknownOption(true); + command.helpOption(', --backstage-cli-help'); + command.action( + lazy(() => import('./commands/package/test'), 'default'), + ); + await command.parseAsync(args, { from: 'user' }); + }, + }); + }, +}); diff --git a/packages/cli/templates/backend-plugin-module/package.json.hbs b/packages/cli/templates/backend-plugin-module/package.json.hbs index 20d78380ca..c34f7e0646 100644 --- a/packages/cli/templates/backend-plugin-module/package.json.hbs +++ b/packages/cli/templates/backend-plugin-module/package.json.hbs @@ -9,7 +9,8 @@ "types": "dist/index.d.ts" }, "backstage": { - "role": "backend-plugin-module" + "role": "backend-plugin-module", + "pluginId": "{{pluginId}}" }, "scripts": { "start": "backstage-cli package start", diff --git a/packages/cli/templates/backend-plugin/package.json.hbs b/packages/cli/templates/backend-plugin/package.json.hbs index ad3810dff3..d8fcb9a9d6 100644 --- a/packages/cli/templates/backend-plugin/package.json.hbs +++ b/packages/cli/templates/backend-plugin/package.json.hbs @@ -8,7 +8,8 @@ "types": "dist/index.d.ts" }, "backstage": { - "role": "backend-plugin" + "role": "backend-plugin", + "pluginId": "{{pluginId}}" }, "scripts": { "start": "backstage-cli package start", diff --git a/packages/cli/templates/frontend-plugin/package.json.hbs b/packages/cli/templates/frontend-plugin/package.json.hbs index 64888c2d37..1ee8231228 100644 --- a/packages/cli/templates/frontend-plugin/package.json.hbs +++ b/packages/cli/templates/frontend-plugin/package.json.hbs @@ -8,7 +8,8 @@ "types": "dist/index.d.ts" }, "backstage": { - "role": "frontend-plugin" + "role": "frontend-plugin", + "pluginId": "{{pluginId}}" }, "sideEffects": false, "scripts": { diff --git a/packages/cli/templates/plugin-common-library/package.json.hbs b/packages/cli/templates/plugin-common-library/package.json.hbs index 085ccb40dd..117679abc1 100644 --- a/packages/cli/templates/plugin-common-library/package.json.hbs +++ b/packages/cli/templates/plugin-common-library/package.json.hbs @@ -10,7 +10,8 @@ "types": "dist/index.d.ts" }, "backstage": { - "role": "common-library" + "role": "common-library", + "pluginId": "{{pluginId}}" }, "sideEffects": false, "scripts": { diff --git a/packages/cli/templates/plugin-node-library/package.json.hbs b/packages/cli/templates/plugin-node-library/package.json.hbs index 182ef1c93f..876d6414ac 100644 --- a/packages/cli/templates/plugin-node-library/package.json.hbs +++ b/packages/cli/templates/plugin-node-library/package.json.hbs @@ -9,7 +9,8 @@ "types": "dist/index.d.ts" }, "backstage": { - "role": "node-library" + "role": "node-library", + "pluginId": "{{pluginId}}" }, "scripts": { "build": "backstage-cli package build", diff --git a/packages/cli/templates/plugin-web-library/package.json.hbs b/packages/cli/templates/plugin-web-library/package.json.hbs index d104a29305..6924600989 100644 --- a/packages/cli/templates/plugin-web-library/package.json.hbs +++ b/packages/cli/templates/plugin-web-library/package.json.hbs @@ -9,7 +9,8 @@ "types": "dist/index.d.ts" }, "backstage": { - "role": "web-library" + "role": "web-library", + "pluginId": "{{pluginId}}" }, "sideEffects": false, "scripts": { diff --git a/packages/cli/templates/scaffolder-backend-module/package.json.hbs b/packages/cli/templates/scaffolder-backend-module/package.json.hbs index 3866a83672..36c99c7687 100644 --- a/packages/cli/templates/scaffolder-backend-module/package.json.hbs +++ b/packages/cli/templates/scaffolder-backend-module/package.json.hbs @@ -9,7 +9,8 @@ "types": "dist/index.d.ts" }, "backstage": { - "role": "backend-plugin-module" + "role": "backend-plugin-module", + "pluginId": "scaffolder" }, "scripts": { "start": "backstage-cli package start", diff --git a/packages/cli/templates/scaffolder-backend-module/src/actions/example.ts b/packages/cli/templates/scaffolder-backend-module/src/actions/example.ts index 7e47a89bcc..9ce24f92bc 100644 --- a/packages/cli/templates/scaffolder-backend-module/src/actions/example.ts +++ b/packages/cli/templates/scaffolder-backend-module/src/actions/example.ts @@ -12,22 +12,16 @@ import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; export function createExampleAction() { // For more information on how to define custom actions, see // https://backstage.io/docs/features/software-templates/writing-custom-actions - return createTemplateAction<{ - myParameter: string; - }>({ + return createTemplateAction({ id: 'acme:example', description: 'Runs an example action', schema: { input: { - type: 'object', - required: ['myParameter'], - properties: { - myParameter: { - title: 'An example parameter', - description: "This is an example parameter, don't set it to foo", - type: 'string', - }, - }, + myParameter: z => + z.string({ + description: + "This is an example parameter, don't set it to foo", + }), }, }, async handler(ctx) { diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index 5305433cb9..3b933aeeea 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 + ## 1.10.1-next.0 ### Patch Changes diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 6f846fddbd..364d1a2bc8 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/config-loader", - "version": "1.10.1-next.0", + "version": "1.10.1", "description": "Config loading functionality used by Backstage backend, and CLI", "backstage": { "role": "node-library" diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 7e874a1774..6b5a5727e8 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,33 @@ # @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 + ## 1.17.0-next.1 ### Minor Changes diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 2eb53d7d87..73fef45b06 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-app-api", - "version": "1.17.0-next.1", + "version": "1.17.0", "description": "Core app API used by Backstage apps", "backstage": { "role": "web-library" diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index 430117e719..98eff832bb 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,47 @@ # @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 + +## 0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/version-bridge@1.0.11 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + +## 0.4.2 + +### Patch Changes + +- 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 + ## 0.4.2-next.3 ### Patch Changes diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 77650e4196..858b01d955 100644 --- a/packages/core-compat-api/package.json +++ b/packages/core-compat-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-compat-api", - "version": "0.4.2-next.3", + "version": "0.4.3-next.2", "backstage": { "role": "web-library" }, diff --git a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx index 151fb21067..0a5dd68aa3 100644 --- a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx +++ b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx @@ -119,7 +119,7 @@ function LegacyAppContextProvider(props: { children: ReactNode }) { const pluginSet = new Set(); for (const node of tree.nodes.values()) { - const plugin = node.spec.source; + const plugin = node.spec.plugin; if (plugin) { pluginSet.add(toLegacyPlugin(plugin)); } diff --git a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx index 68bb7cac3f..3f85e07ca5 100644 --- a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx +++ b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx @@ -41,6 +41,8 @@ describe('convertLegacyPlugin', () => { "featureFlags": [], "getExtension": [Function], "id": "test", + "info": [Function], + "infoOptions": undefined, "routes": {}, "toString": [Function], "version": "v1", diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index 55608d11bf..214b187e68 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,31 @@ # @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 + +## 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 + ## 0.17.2-next.1 ### Patch Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 2fe5f03f28..e5941498bc 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-components", - "version": "0.17.2-next.1", + "version": "0.17.3-next.0", "description": "Core components used by Backstage plugins and apps", "backstage": { "role": "web-library" diff --git a/packages/core-components/report-alpha.api.md b/packages/core-components/report-alpha.api.md index 73b4194266..67c4226322 100644 --- a/packages/core-components/report-alpha.api.md +++ b/packages/core-components/report-alpha.api.md @@ -13,6 +13,7 @@ export const coreComponentsTranslationRef: TranslationRef< readonly 'table.filter.placeholder': 'All results'; readonly 'table.filter.clearAll': 'Clear all'; readonly 'table.body.emptyDataSourceMessage': 'No records to display'; + readonly 'table.header.actions': 'Actions'; readonly 'table.toolbar.search': 'Filter'; readonly 'table.pagination.firstTooltip': 'First Page'; readonly 'table.pagination.labelDisplayedRows': '{from}-{to} of {count}'; diff --git a/packages/core-components/src/components/Table/Table.tsx b/packages/core-components/src/components/Table/Table.tsx index 08fc746ca4..0287cfdf40 100644 --- a/packages/core-components/src/components/Table/Table.tsx +++ b/packages/core-components/src/components/Table/Table.tsx @@ -509,6 +509,10 @@ export function Table(props: TableProps) { previousTooltip: t('table.pagination.previousTooltip'), ...localization?.pagination, }, + header: { + actions: t('table.header.actions'), + ...localization?.header, + }, toolbar: { searchPlaceholder: t('table.toolbar.search'), searchTooltip: t('table.toolbar.search'), diff --git a/packages/core-components/src/translation.ts b/packages/core-components/src/translation.ts index 40b6e94798..8313b75a40 100644 --- a/packages/core-components/src/translation.ts +++ b/packages/core-components/src/translation.ts @@ -104,6 +104,9 @@ export const coreComponentsTranslationRef = createTranslationRef({ toolbar: { search: 'Filter', }, + header: { + actions: 'Actions', + }, }, alertDisplay: { message_one: '({{ count }} newer message)', diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index 90dee1e81b..6aee788421 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 + ## 1.10.7-next.0 ### Patch Changes diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 7c9eaa3943..3671d16609 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-plugin-api", - "version": "1.10.7-next.0", + "version": "1.10.7", "description": "Core API used by Backstage plugins", "backstage": { "role": "web-library" diff --git a/packages/core-plugin-api/src/analytics/Tracker.ts b/packages/core-plugin-api/src/analytics/Tracker.ts index 130410e2a2..4ae31b43fd 100644 --- a/packages/core-plugin-api/src/analytics/Tracker.ts +++ b/packages/core-plugin-api/src/analytics/Tracker.ts @@ -20,7 +20,7 @@ import { AnalyticsEventAttributes, AnalyticsTracker, } from '../apis'; -import { AnalyticsContextValue } from './'; +import { AnalyticsContextValue } from './types'; type TempGlobalEvents = { /** diff --git a/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts b/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts index 7c3c3f8e83..5cfb14abb2 100644 --- a/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts @@ -16,7 +16,7 @@ import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; import { Expand, ExpandRecursive, Observable } from '@backstage/types'; -import { TranslationRef } from '../../translation'; +import { TranslationRef } from '../../translation/TranslationRef'; import { JSX } from 'react'; /** diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 14c327f183..4c2af3b914 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/create-app +## 0.7.0-next.3 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.15 + +## 0.7.0-next.2 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.15 + +## 0.7.0-next.1 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.15 + +## 0.7.0-next.0 + +### Minor Changes + +- 30474c4: Add .cache directory to shipped gitignore. + +## 0.6.2 + +### Patch Changes + +- 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 + ## 0.6.2-next.3 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 20981dc781..75831789cb 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/create-app", - "version": "0.6.2-next.3", + "version": "0.7.0-next.3", "description": "A CLI that helps you create your own Backstage app", "backstage": { "role": "cli" diff --git a/packages/create-app/templates/default-app/.gitignore.hbs b/packages/create-app/templates/default-app/.gitignore.hbs index e506e00f92..2164e72f7d 100644 --- a/packages/create-app/templates/default-app/.gitignore.hbs +++ b/packages/create-app/templates/default-app/.gitignore.hbs @@ -51,3 +51,6 @@ site # E2E test reports e2e-test-report/ + +# Cache +.cache/ diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index d1c08c394e..165ce2feef 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,55 @@ # @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 + +## 1.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/app-defaults@1.6.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + +## 1.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/integration-react@1.2.7 + +## 1.1.10 + +### Patch Changes + +- 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 + ## 1.1.10-next.3 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index e71af05c9a..070be8ae97 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/dev-utils", - "version": "1.1.10-next.3", + "version": "1.1.11-next.2", "description": "Utilities for developing Backstage plugins.", "backstage": { "role": "web-library" diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md index b760a4d651..1120008ca9 100644 --- a/packages/e2e-test/CHANGELOG.md +++ b/packages/e2e-test/CHANGELOG.md @@ -1,5 +1,48 @@ # 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 + +## 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 + +## 0.2.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.0-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## 0.2.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.0-next.0 + +## 0.2.28 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.6.2 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + ## 0.2.28-next.3 ### Patch Changes diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index 7e37bb477d..31088f64ff 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,6 +1,6 @@ { "name": "e2e-test", - "version": "0.2.28-next.3", + "version": "0.2.29-next.3", "description": "E2E test for verifying Backstage packages", "backstage": { "role": "cli" diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 12aed8093f..9dcf7841e5 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/eslint-plugin +## 0.1.11-next.0 + +### Patch Changes + +- 098ef95: Fix custom rules package scanning performance. + ## 0.1.10 ### Patch Changes diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 3a67ce8cee..69a8eff08f 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/eslint-plugin", - "version": "0.1.10", + "version": "0.1.11-next.0", "description": "Backstage ESLint plugin", "publishConfig": { "access": "public" diff --git a/packages/eslint-plugin/rules/no-undeclared-imports.js b/packages/eslint-plugin/rules/no-undeclared-imports.js index b770ded365..17331d2fce 100644 --- a/packages/eslint-plugin/rules/no-undeclared-imports.js +++ b/packages/eslint-plugin/rules/no-undeclared-imports.js @@ -319,6 +319,7 @@ module.exports = { if (importsToAdd.length > 0) { addMissingImports(importsToAdd, packages, localPkg); + packages.clearCache(); // This switches all import directives back to the original import. for (const added of importsToAdd) { context.report({ @@ -336,6 +337,7 @@ module.exports = { removeInlineImports(importsToInline, localPkg); addForwardedInlineImports(importsToInline, localPkg); + packages.clearCache(); for (const inlined of importsToInline) { context.report({ node: inlined.node, @@ -350,8 +352,6 @@ module.exports = { } importsToInline.length = 0; } - - packages.clearCache(); }, ...visitImports(context, (node, imp) => { // We leave checking of type imports to the repo-tools check, diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index 84fb2dbaa1..4c34740a75 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,45 @@ # @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 + +## 0.11.3-next.0 + +### Patch Changes + +- c38c9e8: Implemented support for the `plugin.info()` method in specialized apps with a default resolved for `package.json` and `catalog-info.yaml`. The default resolution logic can be overridden via the `pluginInfoResolver` option to `createSpecializedApp`, and plugin-specific overrides can be applied via the new `app.pluginOverrides` key in static configuration. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/frontend-defaults@0.2.3-next.0 + +## 0.11.2 + +### Patch Changes + +- 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 + ## 0.11.2-next.3 ### Patch Changes diff --git a/packages/frontend-app-api/config.d.ts b/packages/frontend-app-api/config.d.ts index fc265e8833..3b86b5c1d4 100644 --- a/packages/frontend-app-api/config.d.ts +++ b/packages/frontend-app-api/config.d.ts @@ -51,5 +51,71 @@ export interface Config { }; } >; + + /** + * This section enables you to override certain properties of specific or + * groups of plugins. + * + * @remarks + * All matching entries will be applied to each plugin, with the later + * entries taking precedence. + * + * This configuration is intended to be used primarily to apply overrides + * for third-party plugins. + * + * @deepVisibility frontend + */ + pluginOverrides?: Array<{ + /** + * The criteria for matching plugins to override. + * + * @remarks + * If no match criteria are provided, the override will be applied to + * all plugins. + */ + match?: { + /** + * A pattern that is matched against the plugin ID. + * + * @remarks + * By default the string is interpreted as a glob pattern, but if the + * string is surrounded by '/' it is interpreted as a regex. + */ + pluginId?: string; + + /** + * A pattern that is matched against the package name. + * + * @remarks + * By default the string is interpreted as a glob pattern, but if the + * string is surrounded by '/' it is interpreted as a regex. + * + * Note that this will only work for plugins that provide a + * `package.json` info loader. + */ + packageName?: string; + }; + /** + * Overrides individual top-level fields of the plugin info. + */ + info: { + /** + * Override the description of the plugin. + */ + description?: string; + /** + * Override the owner entity references of the plugin. + * + * @remarks + * The provided values are interpreted as entity references defaulting + * to Group entities in the default namespace. + */ + ownerEntityRefs?: string[]; + /** + * Override the links of the plugin. + */ + links?: Array<{ title: string; url: string }>; + }; + }>; }; } diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index 6cfbccf639..ccddf635d2 100644 --- a/packages/frontend-app-api/package.json +++ b/packages/frontend-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-app-api", - "version": "0.11.2-next.3", + "version": "0.11.3-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-app-api/report.api.md b/packages/frontend-app-api/report.api.md index d2a3ac4d3b..7228777561 100644 --- a/packages/frontend-app-api/report.api.md +++ b/packages/frontend-app-api/report.api.md @@ -9,6 +9,8 @@ import { ConfigApi } from '@backstage/core-plugin-api'; import { ExtensionFactoryMiddleware } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { FrontendFeature as FrontendFeature_2 } from '@backstage/frontend-plugin-api'; +import { FrontendPluginInfo } from '@backstage/frontend-plugin-api'; +import { JsonObject } from '@backstage/types'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { SubRouteRef } from '@backstage/frontend-plugin-api'; @@ -27,7 +29,7 @@ export type CreateAppRouteBinder = < // @public export function createSpecializedApp(options?: { - features?: FrontendFeature[]; + features?: FrontendFeature_2[]; config?: ConfigApi; bindRoutes?(context: { bind: CreateAppRouteBinder }): void; apis?: ApiHolder; @@ -37,6 +39,7 @@ export function createSpecializedApp(options?: { flags?: { allowUnknownExtensionConfig?: boolean; }; + pluginInfoResolver?: FrontendPluginInfoResolver; }): { apis: ApiHolder; tree: AppTree; @@ -44,4 +47,18 @@ export function createSpecializedApp(options?: { // @public @deprecated (undocumented) export type FrontendFeature = FrontendFeature_2; + +// @public +export type FrontendPluginInfoResolver = (ctx: { + packageJson(): Promise; + manifest(): Promise; + defaultResolver(sources: { + packageJson: JsonObject | undefined; + manifest: JsonObject | undefined; + }): Promise<{ + info: FrontendPluginInfo; + }>; +}) => Promise<{ + info: FrontendPluginInfo; +}>; ``` diff --git a/packages/frontend-app-api/src/routing/RouteTracker.test.tsx b/packages/frontend-app-api/src/routing/RouteTracker.test.tsx index 08a9673afc..5cee727075 100644 --- a/packages/frontend-app-api/src/routing/RouteTracker.test.tsx +++ b/packages/frontend-app-api/src/routing/RouteTracker.test.tsx @@ -49,7 +49,7 @@ describe('RouteTracker', () => { appNode: { spec: { extension: { id: 'home.page.index' }, - source: { id: 'home' }, + plugin: { id: 'home' }, }, } as AppNode, }, @@ -63,7 +63,7 @@ describe('RouteTracker', () => { appNode: { spec: { extension: { id: 'plugin1.page.index' }, - source: { id: 'plugin1' }, + plugin: { id: 'plugin1' }, }, } as AppNode, }, @@ -77,7 +77,7 @@ describe('RouteTracker', () => { appNode: { spec: { extension: { id: 'plugin2.page.index' }, - source: { id: 'plugin2' }, + plugin: { id: 'plugin2' }, }, } as AppNode, }, diff --git a/packages/frontend-app-api/src/routing/RouteTracker.tsx b/packages/frontend-app-api/src/routing/RouteTracker.tsx index 4a218a0900..0068f87d4d 100644 --- a/packages/frontend-app-api/src/routing/RouteTracker.tsx +++ b/packages/frontend-app-api/src/routing/RouteTracker.tsx @@ -63,7 +63,7 @@ const getExtensionContext = ( return acc; }, {}); - const plugin = routeObject.appNode?.spec.source; + const plugin = routeObject.appNode?.spec.plugin; const extension = routeObject.appNode?.spec.extension; return { diff --git a/packages/frontend-app-api/src/routing/collectRouteIds.ts b/packages/frontend-app-api/src/routing/collectRouteIds.ts index 8b23e9e80b..6b0527f670 100644 --- a/packages/frontend-app-api/src/routing/collectRouteIds.ts +++ b/packages/frontend-app-api/src/routing/collectRouteIds.ts @@ -29,7 +29,7 @@ import { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/rou // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalSubRouteRef } from '../../../frontend-plugin-api/src/routing/SubRouteRef'; import { OpaqueFrontendPlugin } from '@internal/frontend'; -import { FrontendFeature } from '../wiring'; +import { FrontendFeature } from '../wiring/types'; /** @internal */ export interface RouteRefsById { diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.ts index 17afbeb4c9..a1abaf1599 100644 --- a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.ts +++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.ts @@ -124,8 +124,8 @@ export function extractRouteInfoFromAppNode(node: AppNode): { routeParents.set(routeRef, newParentRef); currentObj?.routeRefs.add(routeRef); - if (current.spec.source) { - currentObj?.plugins.add(toLegacyPlugin(current.spec.source)); + if (current.spec.plugin) { + currentObj?.plugins.add(toLegacyPlugin(current.spec.plugin)); } } diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts index a000e016be..8801e2070e 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts @@ -57,6 +57,7 @@ function makeSpec( disabled: extension.disabled, extension: extension as Extension, source: undefined, + plugin: undefined, ...spec, }; } diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts index c486370563..979c851a47 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts @@ -126,6 +126,7 @@ describe('resolveAppNodeSpecs', () => { extension: makeExt('test/a'), attachTo: { id: 'root', input: 'default' }, source: pluginA, + plugin: pluginA, disabled: false, }, ]); @@ -162,6 +163,7 @@ describe('resolveAppNodeSpecs', () => { id: 'test/a', extension: a, attachTo: { id: 'root', input: 'default' }, + plugin, source: plugin, config: { foo: { bar: 1 } }, disabled: false, @@ -170,6 +172,7 @@ describe('resolveAppNodeSpecs', () => { id: 'test/b', extension: b, attachTo: { id: 'root', input: 'default' }, + plugin, source: plugin, config: { foo: { qux: 3 } }, disabled: false, @@ -305,6 +308,7 @@ describe('resolveAppNodeSpecs', () => { id: 'test/a', extension: expect.objectContaining(aOverride), attachTo: { id: 'other', input: 'default' }, + plugin, source: plugin, disabled: false, }, @@ -312,6 +316,7 @@ describe('resolveAppNodeSpecs', () => { id: 'test/b', extension: expect.objectContaining(bOverride), attachTo: { id: 'other', input: 'default' }, + plugin, source: plugin, disabled: true, }, @@ -319,6 +324,7 @@ describe('resolveAppNodeSpecs', () => { id: 'test/c', extension: expect.objectContaining(cOverride), attachTo: { id: 'root', input: 'default' }, + plugin, source: plugin, disabled: false, }, diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts index 3da68dcb50..399e6ccb7b 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts @@ -46,23 +46,23 @@ export function resolveAppNodeSpecs(options: { const plugins = features.filter(OpaqueFrontendPlugin.isType); const modules = features.filter(isInternalFrontendModule); - const pluginExtensions = plugins.flatMap(source => { - return OpaqueFrontendPlugin.toInternal(source).extensions.map( + const pluginExtensions = plugins.flatMap(plugin => { + return OpaqueFrontendPlugin.toInternal(plugin).extensions.map( extension => ({ ...extension, - source, + plugin, }), ); }); const moduleExtensions = modules.flatMap(mod => toInternalFrontendModule(mod).extensions.flatMap(extension => { // Modules for plugins that are not installed are ignored - const source = plugins.find(p => p.id === mod.pluginId); - if (!source) { + const plugin = plugins.find(p => p.id === mod.pluginId); + if (!plugin) { return []; } - return [{ ...extension, source }]; + return [{ ...extension, plugin }]; }), ); @@ -70,7 +70,7 @@ export function resolveAppNodeSpecs(options: { if (pluginExtensions.some(({ id }) => forbidden.has(id))) { const pluginsStr = pluginExtensions .filter(({ id }) => forbidden.has(id)) - .map(({ source }) => `'${source.id}'`) + .map(({ plugin }) => `'${plugin.id}'`) .join(', '); const forbiddenStr = [...forbidden].map(id => `'${id}'`).join(', '); throw new Error( @@ -80,7 +80,7 @@ export function resolveAppNodeSpecs(options: { if (moduleExtensions.some(({ id }) => forbidden.has(id))) { const pluginsStr = moduleExtensions .filter(({ id }) => forbidden.has(id)) - .map(({ source }) => `'${source.id}'`) + .map(({ plugin }) => `'${plugin.id}'`) .join(', '); const forbiddenStr = [...forbidden].map(id => `'${id}'`).join(', '); throw new Error( @@ -89,12 +89,13 @@ export function resolveAppNodeSpecs(options: { } const configuredExtensions = [ - ...pluginExtensions.map(({ source, ...extension }) => { + ...pluginExtensions.map(({ plugin, ...extension }) => { const internalExtension = toInternalExtension(extension); return { extension: internalExtension, params: { - source, + plugin, + source: plugin, attachTo: internalExtension.attachTo, disabled: internalExtension.disabled, config: undefined as unknown, @@ -107,6 +108,7 @@ export function resolveAppNodeSpecs(options: { extension: internalExtension, params: { source: undefined, + plugin: undefined, attachTo: internalExtension.attachTo, disabled: internalExtension.disabled, config: undefined as unknown, @@ -133,7 +135,8 @@ export function resolveAppNodeSpecs(options: { configuredExtensions.push({ extension: internalExtension, params: { - source: extension.source, + plugin: extension.plugin, + source: extension.plugin, attachTo: internalExtension.attachTo, disabled: internalExtension.disabled, config: undefined, @@ -219,6 +222,7 @@ export function resolveAppNodeSpecs(options: { attachTo: param.params.attachTo, extension: param.extension, disabled: param.params.disabled, + plugin: param.params.plugin, source: param.params.source, config: param.params.config, })); diff --git a/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.test.ts b/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.test.ts new file mode 100644 index 0000000000..6c053f8aed --- /dev/null +++ b/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.test.ts @@ -0,0 +1,284 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { mockApis } from '@backstage/test-utils'; +import { createPluginInfoAttacher } from './createPluginInfoAttacher'; +import { OpaqueFrontendPlugin } from '@internal/frontend'; +import { + createFrontendPlugin, + FrontendFeature, +} from '@backstage/frontend-plugin-api'; + +function getInfo(plugin: FrontendFeature) { + return OpaqueFrontendPlugin.toInternal(plugin).info(); +} + +describe('createPluginInfoAttacher', () => { + const mockConfig = mockApis.config({ + data: { + app: { + pluginOverrides: [ + { + match: { + pluginId: '/^.*-tester$/', + }, + info: { + description: 'Overridden description', + }, + }, + { + match: { + pluginId: '/^not-.*-tester$/', + }, + info: { + ownerEntityRefs: ['test-group'], + }, + }, + { + match: { + packageName: '@test/package', + }, + info: { + description: 'Package name matched', + }, + }, + { + match: { + pluginId: 'info-tester', + }, + info: { + links: [{ title: 'Custom Link', url: 'https://example.com' }], + }, + }, + ], + }, + }, + }); + + describe('with default resolver', () => { + const attacher = createPluginInfoAttacher(mockConfig); + + it('should return a new plugin instance', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + }); + + const newPlugin = attacher(plugin); + expect(newPlugin).not.toBe(plugin); + await expect(getInfo(newPlugin)).resolves.toEqual({}); + }); + + it('should return non-plugin features unchanged', () => { + const nonPluginFeature = { + type: 'not-a-plugin', + } as unknown as FrontendFeature; + + expect(attacher(nonPluginFeature)).toBe(nonPluginFeature); + }); + + it('should resolve plugin info from package.json and config overrides', async () => { + await expect( + getInfo( + attacher( + createFrontendPlugin({ + pluginId: 'other-tester', + info: { + packageJson: async () => ({ + name: '@test/package', + version: '1.0.0', + description: 'Original description', + homepage: 'https://homepage.com', + repository: { + url: 'https://github.com/test/project', + directory: 'packages/test', + }, + }), + }, + }), + ), + ), + ).resolves.toEqual({ + packageName: '@test/package', + version: '1.0.0', + description: 'Package name matched', + links: [ + { + title: 'Homepage', + url: 'https://homepage.com', + }, + { + title: 'Repository', + url: 'https://github.com/test/project/tree/-/packages/test', + }, + ], + }); + + await expect( + getInfo( + attacher( + createFrontendPlugin({ + pluginId: 'info-tester', + info: { + packageJson: async () => ({ + name: '@other/package', + description: 'Original description', + homepage: 'https://homepage.com', + }), + }, + }), + ), + ), + ).resolves.toEqual({ + packageName: '@other/package', + description: 'Overridden description', + links: [ + { + title: 'Custom Link', + url: 'https://example.com', + }, + ], + }); + + await expect( + getInfo( + attacher( + createFrontendPlugin({ + pluginId: 'not-info-tester', + info: { + packageJson: async () => ({ + name: '@other/package', + description: 'Original description', + repository: { + url: 'http://example.com', + directory: 'packages/test', + }, + }), + }, + }), + ), + ), + ).resolves.toEqual({ + packageName: '@other/package', + description: 'Overridden description', + ownerEntityRefs: ['group:default/test-group'], + links: [ + { + title: 'Repository', + url: 'http://example.com/', + }, + ], + }); + }); + }); + + describe('with custom resolver', () => { + const plugin = createFrontendPlugin({ + pluginId: 'custom-resolver', + info: { + packageJson: async () => ({ + name: '@test/resolver', + version: '1.0.0', + }), + manifest: async () => ({ + metadata: { + links: [{ title: 'Metadata link', url: 'https://example.com' }], + }, + }), + }, + }); + + it('should use the default resolver', async () => { + const attacher = createPluginInfoAttacher(mockConfig, async ctx => + ctx.defaultResolver({ + packageJson: await ctx.packageJson(), + manifest: await ctx.manifest(), + }), + ); + + await expect(getInfo(attacher(plugin))).resolves.toEqual({ + packageName: '@test/resolver', + version: '1.0.0', + links: [ + { + title: 'Metadata link', + url: 'https://example.com', + }, + ], + }); + }); + + it('should override info sources passed to default resolver', async () => { + const attacher = createPluginInfoAttacher(mockConfig, ctx => + ctx.defaultResolver({ + packageJson: { + name: '@test/resolver-other', + version: '2.0.0', + }, + manifest: { + metadata: { + links: [{ title: 'Other link', url: 'https://example.com' }], + }, + spec: { + owner: 'test-group', + }, + }, + }), + ); + + await expect(getInfo(attacher(plugin))).resolves.toEqual({ + packageName: '@test/resolver-other', + version: '2.0.0', + links: [ + { + title: 'Other link', + url: 'https://example.com', + }, + ], + ownerEntityRefs: ['group:default/test-group'], + }); + }); + + it('should use a completely custom resolver', async () => { + const attacher = createPluginInfoAttacher(mockConfig, async () => ({ + info: { version: '0.1.0' }, + })); + + await expect(getInfo(attacher(plugin))).resolves.toEqual({ + version: '0.1.0', + }); + }); + + it('should handle unexpected input from the default resolver', async () => { + const attacher = createPluginInfoAttacher(mockConfig, ctx => + ctx.defaultResolver({ + packageJson: { + name: null, + version: {}, + }, + manifest: { + metadata: { + links: 'not an array', + }, + spec: [], + }, + }), + ); + await expect(getInfo(attacher(plugin))).resolves.toEqual({ + version: '[object Object]', + }); + }); + }); +}); diff --git a/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.ts b/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.ts new file mode 100644 index 0000000000..dc2b54f534 --- /dev/null +++ b/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.ts @@ -0,0 +1,247 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigApi } from '@backstage/core-plugin-api'; +import { + FrontendFeature, + FrontendPluginInfo, +} from '@backstage/frontend-plugin-api'; +import { OpaqueFrontendPlugin } from '@internal/frontend'; +import { JsonObject, JsonValue } from '@backstage/types'; +import once from 'lodash/once'; +// Avoid full dependency on catalog-model +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { + parseEntityRef, + stringifyEntityRef, +} from '../../../catalog-model/src/entity/ref'; + +/** + * A function that resolves plugin info from a plugin manifest and package.json. + * + * @public + */ +export type FrontendPluginInfoResolver = (ctx: { + packageJson(): Promise; + manifest(): Promise; + defaultResolver(sources: { + packageJson: JsonObject | undefined; + manifest: JsonObject | undefined; + }): Promise<{ info: FrontendPluginInfo }>; +}) => Promise<{ info: FrontendPluginInfo }>; + +export function createPluginInfoAttacher( + config: ConfigApi, + infoResolver: FrontendPluginInfoResolver = async ctx => + ctx.defaultResolver({ + packageJson: await ctx.packageJson(), + manifest: await ctx.manifest(), + }), +): (feature: FrontendFeature) => FrontendFeature { + const applyInfoOverrides = createPluginInfoOverrider(config); + + return (feature: FrontendFeature) => { + if (!OpaqueFrontendPlugin.isType(feature)) { + return feature; + } + + const plugin = OpaqueFrontendPlugin.toInternal(feature); + + return { + ...plugin, + info: once(async () => { + const manifestLoader = plugin.infoOptions?.manifest; + const packageJsonLoader = plugin.infoOptions?.packageJson; + + const { info: resolvedInfo } = await infoResolver({ + manifest: async () => manifestLoader?.(), + packageJson: async () => packageJsonLoader?.(), + defaultResolver: async sources => ({ + info: { + ...resolvePackageInfo(sources.packageJson), + ...resolveManifestInfo(sources.manifest), + }, + }), + }); + + const infoWithOverrides = applyInfoOverrides(plugin.id, resolvedInfo); + return normalizePluginInfo(infoWithOverrides); + }), + }; + }; +} + +function normalizePluginInfo(info: FrontendPluginInfo) { + return { + ...info, + ownerEntityRefs: info.ownerEntityRefs?.map(ref => + stringifyEntityRef( + parseEntityRef(ref, { + defaultKind: 'Group', + }), + ), + ), + }; +} + +function createPluginInfoOverrider(config: ConfigApi) { + const overrideConfigs = + config.getOptionalConfigArray('app.pluginOverrides') ?? []; + + const overrideMatchers = overrideConfigs.map(overrideConfig => { + const pluginIdMatcher = makeStringMatcher( + overrideConfig.getOptionalString('match.pluginId'), + ); + const packageNameMatcher = makeStringMatcher( + overrideConfig.getOptionalString('match.packageName'), + ); + const description = overrideConfig.getOptionalString('info.description'); + const ownerEntityRefs = overrideConfig.getOptionalStringArray( + 'info.ownerEntityRefs', + ); + const links = overrideConfig + .getOptionalConfigArray('info.links') + ?.map(linkConfig => ({ + title: linkConfig.getString('title'), + url: linkConfig.getString('url'), + })); + + return { + test(pluginId: string, packageName?: string) { + return packageNameMatcher(packageName) && pluginIdMatcher(pluginId); + }, + info: { + description, + ownerEntityRefs, + links, + }, + }; + }); + + return (pluginId: string, info: FrontendPluginInfo) => { + const { packageName } = info; + for (const matcher of overrideMatchers) { + if (matcher.test(pluginId, packageName)) { + if (matcher.info.description) { + info.description = matcher.info.description; + } + if (matcher.info.ownerEntityRefs) { + info.ownerEntityRefs = matcher.info.ownerEntityRefs; + } + if (matcher.info.links) { + info.links = matcher.info.links; + } + } + } + return info; + }; +} + +function resolveManifestInfo(manifest?: JsonValue) { + if (!isJsonObject(manifest) || !isJsonObject(manifest.metadata)) { + return undefined; + } + + const info: FrontendPluginInfo = {}; + + if (isJsonObject(manifest.spec) && typeof manifest.spec.owner === 'string') { + info.ownerEntityRefs = [ + stringifyEntityRef( + parseEntityRef(manifest.spec.owner, { + defaultKind: 'Group', + defaultNamespace: manifest.metadata.namespace?.toString(), + }), + ), + ]; + } + + if (Array.isArray(manifest.metadata.links)) { + info.links = manifest.metadata.links.filter(isJsonObject).map(link => ({ + title: String(link.title), + url: String(link.url), + })); + } + + return info; +} + +function resolvePackageInfo(packageJson?: JsonObject) { + if (!packageJson) { + return undefined; + } + + const info: FrontendPluginInfo = { + packageName: packageJson?.name?.toString(), + version: packageJson?.version?.toString(), + description: packageJson?.description?.toString(), + }; + + const links: { title: string; url: string }[] = []; + + if (typeof packageJson.homepage === 'string') { + links.push({ + title: 'Homepage', + url: packageJson.homepage, + }); + } + + if ( + isJsonObject(packageJson.repository) && + typeof packageJson.repository?.url === 'string' + ) { + try { + const url = new URL(packageJson.repository?.url); + if (url.protocol === 'http:' || url.protocol === 'https:') { + // TODO(Rugvip): Support more variants + if ( + url.hostname === 'github.com' && + typeof packageJson.repository.directory === 'string' + ) { + const path = `${url.pathname}/tree/-/${packageJson.repository.directory}`; + url.pathname = path.replaceAll('//', '/'); + } + + links.push({ + title: 'Repository', + url: url.toString(), + }); + } + } catch { + /* ignored */ + } + } + + if (links.length > 0) { + info.links = links; + } + return info; +} + +function makeStringMatcher(pattern: string | undefined) { + if (!pattern) { + return () => true; + } + if (pattern.startsWith('/') && pattern.endsWith('/') && pattern.length > 2) { + const regex = new RegExp(pattern.slice(1, -1)); + return (str?: string) => (str ? regex.test(str) : false); + } + + return (str?: string) => str === pattern; +} + +function isJsonObject(value?: JsonValue): value is JsonObject { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx index 00551e16e3..7c6bb7958b 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx @@ -671,4 +671,123 @@ describe('createSpecializedApp', () => { expect(render(root).container.textContent).toBe('1-2-test-1-2'); }); + + describe('plugin info', () => { + const testExtension = createExtension({ + attachTo: { id: 'root', input: 'app' }, + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement(
Test
)], + }); + + it('should throw unless accessed via an app', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + extensions: [testExtension], + }); + + const errorMsg = + "Attempted to load plugin info for plugin 'test', but the plugin instance is not installed in an app"; + await expect(plugin.info()).rejects.toThrow(errorMsg); + + const app = createSpecializedApp({ features: [plugin] }); + + await expect(plugin.info()).rejects.toThrow(errorMsg); + + const installedPlugin = app.tree.nodes.get('test')?.spec.source; + expect(installedPlugin).toBeDefined(); + const info = await installedPlugin?.info(); + expect(info).toEqual({}); + }); + + it('should forward plugin info', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + info: { + packageJson: () => import('../../package.json'), + }, + extensions: [testExtension], + }); + + const app = createSpecializedApp({ features: [plugin] }); + const info = await app.tree.nodes.get('test')?.spec.source?.info(); + expect(info).toMatchObject({ + packageName: '@backstage/frontend-app-api', + }); + }); + + it('should allow overriding plugin info per plugin', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + info: { + packageJson: () => import('../../package.json'), + }, + extensions: [testExtension], + }); + + const overriddenPlugin = plugin.withOverrides({ + extensions: [], + info: { + packageJson: () => Promise.resolve({ name: 'test-override' }), + }, + }); + + const app = createSpecializedApp({ features: [overriddenPlugin] }); + const info = await app.tree.nodes.get('test')?.spec.source?.info(); + expect(info).toMatchObject({ + packageName: 'test-override', + }); + }); + + it('should merge with plugin info from manifest', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + info: { + packageJson: () => import('../../package.json'), + manifest: async () => ({ + metadata: { + links: [{ title: 'Example', url: 'https://example.com' }], + }, + spec: { + owner: 'cubic-belugas', + }, + }), + }, + extensions: [testExtension], + }); + + const app = createSpecializedApp({ features: [plugin] }); + const info = await app.tree.nodes.get('test')?.spec.source?.info(); + expect(info).toEqual({ + packageName: '@backstage/frontend-app-api', + version: expect.any(String), + links: [{ title: 'Example', url: 'https://example.com' }], + ownerEntityRefs: ['group:default/cubic-belugas'], + }); + }); + + it('should allow overriding of the plugin info resolver', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + info: { + packageJson: () => import('../../package.json'), + }, + extensions: [testExtension], + }); + + const app = createSpecializedApp({ + features: [plugin], + async pluginInfoResolver(ctx) { + const { info } = await ctx.defaultResolver({ + packageJson: await ctx.packageJson(), + manifest: await ctx.manifest(), + }); + return { info: { packageName: `decorated:${info.packageName}` } }; + }, + }); + const info = await app.tree.nodes.get('test')?.spec.source?.info(); + expect(info).toEqual({ + packageName: 'decorated:@backstage/frontend-app-api', + }); + }); + }); }); diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx index 5f6692d600..51b0f14250 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx @@ -31,6 +31,7 @@ import { routeResolutionApiRef, AppNode, ExtensionFactoryMiddleware, + FrontendFeature, } from '@backstage/frontend-plugin-api'; import { AnyApiFactory, @@ -74,8 +75,12 @@ import { ApiRegistry } from '../../../core-app-api/src/apis/system/ApiRegistry'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy'; import { BackstageRouteObject } from '../routing/types'; -import { FrontendFeature, RouteInfo } from './types'; +import { RouteInfo } from './types'; import { matchRoutes } from 'react-router-dom'; +import { + createPluginInfoAttacher, + FrontendPluginInfoResolver, +} from './createPluginInfoAttacher'; function deduplicateFeatures( allFeatures: FrontendFeature[], @@ -209,9 +214,12 @@ export function createSpecializedApp(options?: { | ExtensionFactoryMiddleware | ExtensionFactoryMiddleware[]; flags?: { allowUnknownExtensionConfig?: boolean }; + pluginInfoResolver?: FrontendPluginInfoResolver; }): { apis: ApiHolder; tree: AppTree } { const config = options?.config ?? new ConfigReader({}, 'empty-config'); - const features = deduplicateFeatures(options?.features ?? []); + const features = deduplicateFeatures(options?.features ?? []).map( + createPluginInfoAttacher(config, options?.pluginInfoResolver), + ); const tree = resolveAppTree( 'root', diff --git a/packages/frontend-app-api/src/wiring/index.ts b/packages/frontend-app-api/src/wiring/index.ts index 4ecce3f184..fc52dcb7a1 100644 --- a/packages/frontend-app-api/src/wiring/index.ts +++ b/packages/frontend-app-api/src/wiring/index.ts @@ -15,4 +15,5 @@ */ export { createSpecializedApp } from './createSpecializedApp'; +export { type FrontendPluginInfoResolver } from './createPluginInfoAttacher'; export * from './types'; diff --git a/packages/frontend-defaults/CHANGELOG.md b/packages/frontend-defaults/CHANGELOG.md index e9f64904c5..1455739ece 100644 --- a/packages/frontend-defaults/CHANGELOG.md +++ b/packages/frontend-defaults/CHANGELOG.md @@ -1,5 +1,37 @@ # @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 + +## 0.2.3-next.0 + +### Patch Changes + +- fa5650c: Forwarded the new `pluginInfoResolver` option for `createApp`. +- Updated dependencies + - @backstage/plugin-app@0.1.10-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/frontend-app-api@0.11.3-next.0 + +## 0.2.2 + +### Patch Changes + +- 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 + ## 0.2.2-next.3 ### Patch Changes diff --git a/packages/frontend-defaults/package.json b/packages/frontend-defaults/package.json index 5525daa3bb..c95591b88b 100644 --- a/packages/frontend-defaults/package.json +++ b/packages/frontend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-defaults", - "version": "0.2.2-next.3", + "version": "0.2.3-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-defaults/report.api.md b/packages/frontend-defaults/report.api.md index e9bcd99920..72f212b3b1 100644 --- a/packages/frontend-defaults/report.api.md +++ b/packages/frontend-defaults/report.api.md @@ -9,6 +9,7 @@ import { CreateAppRouteBinder } from '@backstage/frontend-app-api'; import { ExtensionFactoryMiddleware } from '@backstage/frontend-plugin-api'; import { FrontendFeature } from '@backstage/frontend-plugin-api'; import { FrontendFeatureLoader } from '@backstage/frontend-plugin-api'; +import { FrontendPluginInfoResolver } from '@backstage/frontend-app-api'; import { JSX as JSX_2 } from 'react'; import { ReactNode } from 'react'; @@ -44,6 +45,8 @@ export interface CreateAppOptions { | CreateAppFeatureLoader )[]; loadingComponent?: ReactNode; + // (undocumented) + pluginInfoResolver?: FrontendPluginInfoResolver; } // @public diff --git a/packages/frontend-defaults/src/createApp.test.tsx b/packages/frontend-defaults/src/createApp.test.tsx index 4fd1ba4c5b..4f627f1839 100644 --- a/packages/frontend-defaults/src/createApp.test.tsx +++ b/packages/frontend-defaults/src/createApp.test.tsx @@ -23,12 +23,15 @@ import { createFrontendPlugin, ThemeBlueprint, createFrontendModule, + useAppNode, + FrontendPluginInfo, } from '@backstage/frontend-plugin-api'; import { screen, waitFor } from '@testing-library/react'; import { CreateAppFeatureLoader, createApp } from './createApp'; import { mockApis, renderWithEffects } from '@backstage/test-utils'; import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api'; import { default as appPluginOriginal } from '@backstage/plugin-app'; +import { useState, useEffect } from 'react'; describe('createApp', () => { const appPlugin = appPluginOriginal.withOverrides({ @@ -119,6 +122,52 @@ describe('createApp', () => { ); }); + it('should allow overriding the plugin info resolver', async () => { + function TestComponent() { + const appNode = useAppNode(); + const [info, setInfo] = useState( + undefined, + ); + + useEffect(() => { + appNode?.spec.source?.info().then(setInfo); + }, [appNode]); + + return
Package name: {info?.packageName}
; + } + + const app = createApp({ + configLoader: async () => ({ config: mockApis.config() }), + features: [ + appPlugin, + createFrontendPlugin({ + pluginId: 'test', + extensions: [ + PageBlueprint.make({ + params: { + defaultPath: '/', + loader: async () => , + }, + }), + ], + }), + ], + pluginInfoResolver: async () => { + return { + info: { + packageName: '@test/test', + }, + }; + }, + }); + + await renderWithEffects(app.createRoot()); + + await expect( + screen.findByText('Package name: @test/test'), + ).resolves.toBeInTheDocument(); + }); + it('should support feature loaders', async () => { const loader: CreateAppFeatureLoader = { getLoaderName() { diff --git a/packages/frontend-defaults/src/createApp.tsx b/packages/frontend-defaults/src/createApp.tsx index f25b71ce18..e8baa3a764 100644 --- a/packages/frontend-defaults/src/createApp.tsx +++ b/packages/frontend-defaults/src/createApp.tsx @@ -30,6 +30,7 @@ import { ConfigReader } from '@backstage/config'; import { CreateAppRouteBinder, createSpecializedApp, + FrontendPluginInfoResolver, } from '@backstage/frontend-app-api'; import appPlugin from '@backstage/plugin-app'; import { discoverAvailableFeatures } from './discovery'; @@ -78,6 +79,7 @@ export interface CreateAppOptions { extensionFactoryMiddleware?: | ExtensionFactoryMiddleware | ExtensionFactoryMiddleware[]; + pluginInfoResolver?: FrontendPluginInfoResolver; } /** @@ -112,6 +114,7 @@ export function createApp(options?: CreateAppOptions): { features: [appPlugin, ...loadedFeatures], bindRoutes: options?.bindRoutes, extensionFactoryMiddleware: options?.extensionFactoryMiddleware, + pluginInfoResolver: options?.pluginInfoResolver, }); const rootEl = app.tree.root.instance!.getData( diff --git a/packages/frontend-dynamic-feature-loader/CHANGELOG.md b/packages/frontend-dynamic-feature-loader/CHANGELOG.md index 1b377ed777..d50b65c30a 100644 --- a/packages/frontend-dynamic-feature-loader/CHANGELOG.md +++ b/packages/frontend-dynamic-feature-loader/CHANGELOG.md @@ -1,5 +1,28 @@ # @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 + +## 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/package.json b/packages/frontend-dynamic-feature-loader/package.json index d3b05fdc12..681c60c498 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.2-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-internal/CHANGELOG.md b/packages/frontend-internal/CHANGELOG.md index 65ffbd826f..eee56d04b0 100644 --- a/packages/frontend-internal/CHANGELOG.md +++ b/packages/frontend-internal/CHANGELOG.md @@ -1,5 +1,30 @@ # @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 + +## 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/package.json b/packages/frontend-internal/package.json index 7f1615c5f5..1eb25f11b0 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.10-next.1", "backstage": { "role": "web-library", "inline": true diff --git a/packages/frontend-internal/src/wiring/InternalFrontendPlugin.ts b/packages/frontend-internal/src/wiring/InternalFrontendPlugin.ts index a7be9614b6..af69370f76 100644 --- a/packages/frontend-internal/src/wiring/InternalFrontendPlugin.ts +++ b/packages/frontend-internal/src/wiring/InternalFrontendPlugin.ts @@ -19,6 +19,7 @@ import { FeatureFlagConfig, FrontendPlugin, } from '@backstage/frontend-plugin-api'; +import { JsonObject } from '@backstage/types'; import { OpaqueType } from '@internal/opaque'; export const OpaqueFrontendPlugin = OpaqueType.create<{ @@ -27,6 +28,10 @@ export const OpaqueFrontendPlugin = OpaqueType.create<{ readonly version: 'v1'; readonly extensions: Extension[]; readonly featureFlags: FeatureFlagConfig[]; + readonly infoOptions?: { + packageJson?: () => Promise; + manifest?: () => Promise; + }; }; }>({ type: '@backstage/FrontendPlugin', diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index ce9e73745f..52912ea0db 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,63 @@ # @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 + +## 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/package.json b/packages/frontend-plugin-api/package.json index 1e3161489c..3da223818c 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.10.3-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index cd5d89401d..6101f3e7c9 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'; @@ -235,6 +236,8 @@ export interface AppNodeSpec { // (undocumented) readonly id: string; // (undocumented) + readonly plugin?: FrontendPlugin; + // @deprecated (undocumented) readonly source?: FrontendPlugin; } @@ -390,7 +393,7 @@ export const coreExtensionData: { >; routePath: ConfigurableExtensionDataRef; routeRef: ConfigurableExtensionDataRef< - RouteRef, + RouteRef, 'core.routing.ref', {} >; @@ -1322,14 +1325,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 }; @@ -1514,6 +1541,8 @@ export interface PluginOptions< // (undocumented) featureFlags?: FeatureFlagConfig[]; // (undocumented) + info?: FrontendPluginInfoOptions; + // (undocumented) pluginId: TId; // (undocumented) routes?: TRoutes; @@ -1804,6 +1833,9 @@ export { useApi }; export { useApiHolder }; +// @public +export function useAppNode(): AppNode | undefined; + // @public export function useComponentRef( ref: ComponentRef, diff --git a/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts index c9d9896600..0c6dde3cc8 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts @@ -37,6 +37,10 @@ export interface AppNodeSpec { readonly extension: Extension; readonly disabled: boolean; readonly config?: unknown; + readonly plugin?: FrontendPlugin; + /** + * @deprecated Use {@link AppNodeSpec.plugin} instead. + */ readonly source?: FrontendPlugin; } diff --git a/packages/frontend-plugin-api/src/components/AppNodeProvider.test.tsx b/packages/frontend-plugin-api/src/components/AppNodeProvider.test.tsx new file mode 100644 index 0000000000..fcc3b74c59 --- /dev/null +++ b/packages/frontend-plugin-api/src/components/AppNodeProvider.test.tsx @@ -0,0 +1,84 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createVersionedContext, + createVersionedValueMap, +} from '@backstage/version-bridge'; +import { AppNode } from '../apis'; +import { renderHook } from '@testing-library/react'; +import { AppNodeProvider, useAppNode } from './AppNodeProvider'; +import { withLogCollector } from '@backstage/test-utils'; + +describe('AppNodeProvider', () => { + it('should provide app node context to children', () => { + const node = { id: 'test' } as unknown as AppNode; + const { result } = renderHook(() => useAppNode(), { + wrapper: ({ children }) => ( + {children} + ), + }); + + expect(result.current).toBe(node); + }); + + it('should return undefined when used outside provider', () => { + const { result } = renderHook(() => useAppNode()); + expect(result.current).toBeUndefined(); + }); + + it('should return the closest app node', () => { + const node1 = { id: 'test1' } as unknown as AppNode; + const node2 = { id: 'test2' } as unknown as AppNode; + + const { result } = renderHook(() => useAppNode(), { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + expect(result.current).toBe(node2); + }); + + it('should throw error for invalid context version', () => { + const node = { id: 'test' } as unknown as AppNode; + const Context = createVersionedContext('app-node-context'); + const value = createVersionedValueMap({ 2: { node } }); + + const { error } = withLogCollector(() => { + expect(() => + renderHook(() => useAppNode(), { + wrapper: ({ children }) => ( + {children} + ), + }), + ).toThrow('AppNodeContext v1 not available'); + }); + expect(error).toEqual([ + expect.objectContaining({ + detail: new Error('AppNodeContext v1 not available'), + }), + expect.objectContaining({ + detail: new Error('AppNodeContext v1 not available'), + }), + expect.stringContaining( + 'The above error occurred in the component:', + ), + ]); + }); +}); diff --git a/packages/frontend-plugin-api/src/components/AppNodeProvider.tsx b/packages/frontend-plugin-api/src/components/AppNodeProvider.tsx new file mode 100644 index 0000000000..2bc9715c10 --- /dev/null +++ b/packages/frontend-plugin-api/src/components/AppNodeProvider.tsx @@ -0,0 +1,74 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createVersionedContext, + createVersionedValueMap, + useVersionedContext, +} from '@backstage/version-bridge'; +import { AppNode } from '../apis'; +import { ReactNode } from 'react'; + +const CONTEXT_KEY = 'app-node-context'; + +type AppNodeContextV1 = { + node?: AppNode; +}; + +type AppNodeContextMap = { + 1: AppNodeContextV1; +}; + +const AppNodeContext = createVersionedContext(CONTEXT_KEY); + +/** @internal */ +export function AppNodeProvider({ + node, + children, +}: { + node: AppNode; + children: ReactNode; +}) { + const versionedValue = createVersionedValueMap({ 1: { node } }); + + return ; +} + +/** + * React hook providing access to the current {@link AppNode}. + * + * @public + * @remarks + * + * This hook will return the {@link AppNode} for the closest extension. This + * relies on the extension using the {@link (ExtensionBoundary:function)} component in its + * implementation, which is included by default for all common blueprints. + * + * If the current component is not inside an {@link (ExtensionBoundary:function)}, it will + * return `undefined`. + */ +export function useAppNode(): AppNode | undefined { + const versionedContext = useVersionedContext(CONTEXT_KEY); + if (!versionedContext) { + return undefined; + } + + const context = versionedContext.atVersion(1); + if (!context) { + throw new Error('AppNodeContext v1 not available'); + } + return context.node; +} diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx index 46b3956e0e..7f14b992de 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx @@ -28,6 +28,7 @@ import { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/ana import { AppNode, useComponentRef } from '../apis'; import { coreComponentRefs } from './coreComponentRefs'; import { coreExtensionData } from '../wiring'; +import { AppNodeProvider } from './AppNodeProvider'; type RouteTrackerProps = PropsWithChildren<{ disableTracking?: boolean; @@ -69,26 +70,28 @@ export function ExtensionBoundary(props: ExtensionBoundaryProps) { node.instance?.getData(coreExtensionData.routePath), ); - const plugin = node.spec.source; + const plugin = node.spec.plugin; const Progress = useComponentRef(coreComponentRefs.progress); const fallback = useComponentRef(coreComponentRefs.errorBoundaryFallback); // 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/index.ts b/packages/frontend-plugin-api/src/components/index.ts index 5066144000..31a53be24c 100644 --- a/packages/frontend-plugin-api/src/components/index.ts +++ b/packages/frontend-plugin-api/src/components/index.ts @@ -20,3 +20,4 @@ export { } from './ExtensionBoundary'; export { coreComponentRefs } from './coreComponentRefs'; export { createComponentRef, type ComponentRef } from './createComponentRef'; +export { useAppNode } from './AppNodeProvider'; diff --git a/packages/frontend-plugin-api/src/wiring/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/createFrontendPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts index 359f9da417..c97c60e89e 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts @@ -281,6 +281,60 @@ describe('createFrontendPlugin', () => { ).toThrow("Plugin 'test' provided duplicate extensions: test/2, test/3"); }); + describe('info', () => { + it('should support reading info from package.json', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + info: { packageJson: () => Promise.resolve({ name: '@test/test' }) }, + }); + + await expect((plugin as any).infoOptions?.packageJson()).resolves.toEqual( + { name: '@test/test' }, + ); + }); + + it('should support reading info from actual package.json', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + info: { packageJson: () => import('../../package.json') }, + }); + + await expect( + (plugin as any).infoOptions?.packageJson(), + ).resolves.toMatchObject({ name: '@backstage/frontend-plugin-api' }); + }); + + it('should support reading info from opaque manifest', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + info: { manifest: () => Promise.resolve({ owner: 'me' }) }, + }); + + await expect((plugin as any).infoOptions?.manifest()).resolves.toEqual({ + owner: 'me', + }); + }); + + it('should throw when trying to load info without installing in an app', async () => { + await expect( + createFrontendPlugin({ + pluginId: 'test', + }).info(), + ).rejects.toThrow( + "Attempted to load plugin info for plugin 'test', but the plugin instance is not installed in an app", + ); + + await expect( + createFrontendPlugin({ + pluginId: 'test', + info: { packageJson: () => Promise.resolve({ name: '@test/test' }) }, + }).info(), + ).rejects.toThrow( + "Attempted to load plugin info for plugin 'test', but the plugin instance is not installed in an app", + ); + }); + }); + describe('overrides', () => { it('should return a plugin instance with the correct namespace', () => { const plugin = createFrontendPlugin({ diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts index b7c530a0c0..c16ac329cc 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts @@ -25,6 +25,67 @@ import { } from './resolveExtensionDefinition'; import { AnyExternalRoutes, AnyRoutes, FeatureFlagConfig } from './types'; import { MakeSortedExtensionsMap } from './MakeSortedExtensionsMap'; +import { JsonObject } from '@backstage/types'; + +/** + * Information about the plugin. + * + * @public + * @remarks + * + * This interface is intended to be extended via [module + * augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) + * in order to add fields that are specific to each project. + * + * For example, one might add a `slackChannel` field that is read from the + * opaque manifest file. + * + * See the options for `createApp` for more information about how to + * customize the parsing of manifest files. + */ +export interface FrontendPluginInfo { + /** + * The name of the package that implements the plugin. + */ + packageName?: string; + + /** + * The version of the plugin, typically the version of the package.json file. + */ + version?: string; + + /** + * As short description of the plugin, typically the description field in + * package.json. + */ + description?: string; + + /** + * The owner entity references of the plugin. + */ + ownerEntityRefs?: string[]; + + /** + * Links related to the plugin. + */ + links?: Array<{ title: string; url: string }>; +} + +/** + * Options for providing information for a plugin. + * + * @public + */ +export type FrontendPluginInfoOptions = { + /** + * A loader function for the package.json file for the plugin. + */ + packageJson?: () => Promise<{ name: string } & JsonObject>; + /** + * A loader function for an opaque manifest file for the plugin. + */ + manifest?: () => Promise; +}; /** @public */ export interface FrontendPlugin< @@ -38,9 +99,19 @@ export interface FrontendPlugin< readonly id: string; readonly routes: TRoutes; readonly externalRoutes: TExternalRoutes; + + /** + * Loads the plugin info. + */ + info(): Promise; getExtension(id: TId): TExtensionMap[TId]; withOverrides(options: { extensions: Array; + + /** + * Overrides the original info loaders of the plugin one by one. + */ + info?: FrontendPluginInfoOptions; }): FrontendPlugin; } @@ -56,6 +127,7 @@ export interface PluginOptions< externalRoutes?: TExternalRoutes; extensions?: TExtensions; featureFlags?: FeatureFlagConfig[]; + info?: FrontendPluginInfoOptions; } /** @public */ @@ -150,6 +222,14 @@ export function createFrontendPlugin< externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes), featureFlags: options.featureFlags ?? [], extensions: extensions, + infoOptions: options.info, + + // This method is overridden when the plugin instance is installed in an app + async info() { + throw new Error( + `Attempted to load plugin info for plugin '${pluginId}', but the plugin instance is not installed in an app`, + ); + }, getExtension(id) { const ext = extensionDefinitionsById.get(id); if (!ext) { @@ -178,6 +258,10 @@ export function createFrontendPlugin< ...options, pluginId, extensions: [...nonOverriddenExtensions, ...overrides.extensions], + info: { + ...options.info, + ...overrides.info, + }, }); }, }); diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index b584fc9769..24a4e1653f 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -40,6 +40,8 @@ export { createFrontendPlugin, type FrontendPlugin, type PluginOptions, + type FrontendPluginInfo, + type FrontendPluginInfoOptions, } from './createFrontendPlugin'; export { createFrontendModule, diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index fba6e9afb5..aae6d27e75 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,41 @@ # @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 + +## 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 diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index 833030129f..59fecd025c 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.3", + "version": "0.3.3-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/integration-aws-node/CHANGELOG.md b/packages/integration-aws-node/CHANGELOG.md index 41d4eecb08..75d79f45d3 100644 --- a/packages/integration-aws-node/CHANGELOG.md +++ b/packages/integration-aws-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 + ## 0.1.16-next.0 ### Patch Changes diff --git a/packages/integration-aws-node/package.json b/packages/integration-aws-node/package.json index 65403727b6..695f74012a 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.16", "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 fb44067948..06f8807c4c 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 + ## 1.2.7-next.3 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index 529f5011db..6d0e76aa86 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.3", + "version": "1.2.7", "description": "Frontend package for managing integrations towards external systems", "backstage": { "role": "web-library" diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index 832f45e230..56c1f6722f 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,44 @@ # @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 + ## 1.17.0-next.3 ### Minor Changes diff --git a/packages/integration/package.json b/packages/integration/package.json index 142f17c74a..43d8f87e89 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "1.17.0-next.3", + "version": "1.17.0", "description": "Helpers for managing integrations towards external systems", "backstage": { "role": "common-library" diff --git a/packages/release-manifests/CHANGELOG.md b/packages/release-manifests/CHANGELOG.md index a7f41cbdf8..db9a60d0f4 100644 --- a/packages/release-manifests/CHANGELOG.md +++ b/packages/release-manifests/CHANGELOG.md @@ -1,5 +1,15 @@ # @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 diff --git a/packages/release-manifests/package.json b/packages/release-manifests/package.json index ec67298ec2..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.13-next.0", + "version": "0.0.13", "description": "Helper library for receiving release manifests", "backstage": { "role": "common-library" diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index ed05f6dc0e..9af1c80662 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,60 @@ # @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 + +## 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 diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 2aa4e1d80d..dbf7c50198 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.3", + "version": "0.14.0-next.2", "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:^", diff --git a/packages/repo-tools/src/commands/package-docs/Cache.test.ts b/packages/repo-tools/src/commands/package-docs/Cache.test.ts new file mode 100644 index 0000000000..3fe52dcabd --- /dev/null +++ b/packages/repo-tools/src/commands/package-docs/Cache.test.ts @@ -0,0 +1,178 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Lockfile } from '@backstage/cli-node'; +import { PackageDocsCache } from './Cache'; +import { + createMockDirectory, + MockDirectory, +} from '@backstage/backend-test-utils'; +import { readFile } from 'fs/promises'; +import { join as joinPath } from 'path'; + +jest.mock('crypto', () => { + const hash = { + update: jest.fn(), + digest: jest.fn().mockReturnValue('test'), + }; + return { + createHash: jest.fn().mockReturnValue(hash), + }; +}); + +describe('PackageDocsCache', () => { + let testDir: MockDirectory; + beforeAll(async () => { + testDir = createMockDirectory(); + }); + afterEach(async () => { + testDir.clear(); + }); + it('should be able to parse cache files', async () => { + testDir.addContent({ + '.cache': { + 'package-docs': { + test: { + 'cache.json': JSON.stringify({ + hash: 'test', + packageName: 'test', + restoreTo: 'test', + version: '1', + }), + }, + }, + }, + test: { + 'package.json': JSON.stringify({ + name: '@test/test', + }), + }, + }); + const lockfile = { + getDependencyTreeHash: () => 'test', + } as any as Lockfile; + const cache = await PackageDocsCache.loadAsync(testDir.path, lockfile); + expect(await cache.has('test')).toBe(true); + }); + + it('should be able to restore cache', async () => { + testDir.addContent({ + '.cache': { + 'package-docs': { + test: { + 'cache.json': JSON.stringify({ + hash: 'test', + packageName: 'test', + restoreTo: 'test', + version: '1', + }), + contents: { + 'src/index.ts': 'export const test = "test";', + }, + }, + }, + }, + test: { + 'package.json': JSON.stringify({ + name: '@test/test', + }), + }, + }); + const lockfile = { + getDependencyTreeHash: () => 'test', + } as any as Lockfile; + const cache = await PackageDocsCache.loadAsync(testDir.path, lockfile); + await cache.restore('test'); + expect( + await readFile(joinPath(testDir.path, 'test', 'src/index.ts'), 'utf-8'), + ).toBe('export const test = "test";'); + }); + + it('should be able to write cache', async () => { + testDir.addContent({ + '.cache': {}, + test: { + 'package.json': JSON.stringify({ + name: '@test/test', + }), + 'src/index.ts': 'export const test = "test";', + }, + }); + const lockfile = { + getDependencyTreeHash: () => 'test', + } as any as Lockfile; + const cache = await PackageDocsCache.loadAsync(testDir.path, lockfile); + await cache.write('test', joinPath(testDir.path, 'test')); + expect( + await readFile( + joinPath(testDir.path, '.cache', 'package-docs', 'test', 'cache.json'), + 'utf-8', + ), + ).toBe( + JSON.stringify({ + hash: 'test', + packageName: '@test/test', + restoreTo: 'test', + version: '1', + }), + ); + expect( + await readFile( + joinPath( + testDir.path, + '.cache', + 'package-docs', + 'test', + 'contents', + 'src/index.ts', + ), + 'utf-8', + ), + ).toBe('export const test = "test";'); + }); + + it.each([ + { + content: JSON.stringify({ + hash: 'test', + packageName: 'test', + restoreTo: 'test', + version: '2', + }), + }, + { + content: JSON.stringify({ + hash: 'test', + packageName: 1, + }), + }, + ])('should skip invalid cache files', async content => { + testDir.addContent({ + '.cache': {}, + test: { + 'package.json': JSON.stringify({ + name: '@test/test', + }), + }, + 'cache.json': content, + }); + const lockfile = { + getDependencyTreeHash: () => 'test', + } as any as Lockfile; + const cache = await PackageDocsCache.loadAsync(testDir.path, lockfile); + expect(await cache.has('test')).toBe(false); + }); +}); diff --git a/packages/repo-tools/src/commands/package-docs/Cache.ts b/packages/repo-tools/src/commands/package-docs/Cache.ts new file mode 100644 index 0000000000..487db81347 --- /dev/null +++ b/packages/repo-tools/src/commands/package-docs/Cache.ts @@ -0,0 +1,158 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { readFile, writeFile, cp } from 'fs/promises'; +import globby from 'globby'; +import { dirname, join as joinPath, relative } from 'path'; +import crypto from 'crypto'; +import { Lockfile } from '@backstage/cli-node'; +import { exists, rm, mkdirp } from 'fs-extra'; +import { z } from 'zod'; +import { CACHE_DIR, CACHE_FILE } from './constants'; + +const version = '1'; + +interface CacheEntry { + hash: string; + packageName: string; + restoreTo: string; + version: string; +} + +const cacheEntrySchema = z.object({ + hash: z.string(), + packageName: z.string(), + restoreTo: z.string(), + version: z.string(), +}); + +export class PackageDocsCache { + // A map of package directory to package hash. + private keyCache: Map; + constructor( + private readonly lockfile: Lockfile, + // A map of package directory to cache entry. + private readonly cache: Map, + private readonly baseDirectory: string, + ) { + this.keyCache = new Map(); + } + static async loadAsync(baseDirectory: string, lockfile: Lockfile) { + const cacheDir = joinPath(baseDirectory, CACHE_DIR); + await mkdirp(cacheDir); + const cacheFiles = await globby(`**/${CACHE_FILE}`, { + cwd: cacheDir, + }); + const map = new Map(); + for (const file of cacheFiles) { + const pkg = dirname(file); + const cache = await readFile(joinPath(cacheDir, file), 'utf-8'); + try { + const cacheJson = JSON.parse(cache); + const parsed = cacheEntrySchema.parse(cacheJson); + if (parsed.version !== version) { + console.warn( + `Skipping cache file ${file} due to version mismatch: ${parsed.version} !== ${version}`, + ); + continue; + } + map.set(pkg, parsed); + } catch (e) { + console.error(`Skipping unparseable cache file ${file}: ${e}`); + } + } + return new PackageDocsCache(lockfile, map, baseDirectory); + } + + async directoryToName(directory: string) { + const packageJson = await readFile( + joinPath(this.baseDirectory, directory, 'package.json'), + 'utf-8', + ); + return JSON.parse(packageJson).name; + } + + private async toKey(pkg: string) { + if (this.keyCache.has(pkg)) { + return this.keyCache.get(pkg)!; + } + const name = await this.directoryToName(pkg); + const result = await globby('src/**', { + gitignore: true, + onlyFiles: true, + cwd: pkg, + }); + + const hash = crypto.createHash('sha1'); + hash.update(version); + hash.update('\0'); + + for (const path of result.sort()) { + const absPath = joinPath(this.baseDirectory, pkg, path); + const pathInPackage = joinPath(absPath, path); + hash.update(pathInPackage); + hash.update('\0'); + hash.update(await readFile(absPath)); + hash.update('\0'); + } + hash.update(this.lockfile.getDependencyTreeHash(name)); + hash.update('\0'); + const hashString = hash.digest('hex'); + this.keyCache.set(pkg, hashString); + return hashString; + } + + async has(pkg: string) { + const cache = this.cache.get(pkg); + if (!cache) { + return false; + } + const hashString = await this.toKey(pkg); + return cache.hash === hashString; + } + + async restore(pkg: string) { + if (!this.has(pkg)) { + throw new Error(`Cache entry for ${pkg} not found`); + } + const cacheEntry = this.cache.get(pkg); + const restoreTo = cacheEntry!.restoreTo; + const cacheDir = joinPath(this.baseDirectory, CACHE_DIR, pkg); + const contentsDir = joinPath(cacheDir, 'contents'); + + const targetDir = joinPath(this.baseDirectory, restoreTo); + await mkdirp(targetDir); + await cp(contentsDir, targetDir, { recursive: true }); + } + + async write(pkg: string, contentDirectory: string) { + const cacheDir = joinPath(this.baseDirectory, CACHE_DIR, pkg); + const contentsDir = joinPath(cacheDir, 'contents'); + if (await exists(contentsDir)) { + await rm(contentsDir, { recursive: true }); + } else { + await mkdirp(contentsDir); + } + const hashString = await this.toKey(pkg); + await cp(contentDirectory, contentsDir, { recursive: true }); + const cacheEntry: CacheEntry = { + hash: hashString, + packageName: await this.directoryToName(pkg), + restoreTo: relative(this.baseDirectory, contentDirectory), + version, + }; + await writeFile(joinPath(cacheDir, CACHE_FILE), JSON.stringify(cacheEntry)); + } +} diff --git a/packages/repo-tools/src/commands/package-docs/command.ts b/packages/repo-tools/src/commands/package-docs/command.ts index 8c30b06145..aa5edc7651 100644 --- a/packages/repo-tools/src/commands/package-docs/command.ts +++ b/packages/repo-tools/src/commands/package-docs/command.ts @@ -17,8 +17,11 @@ import { exec } from 'child_process'; import { promisify } from 'util'; import { paths as cliPaths, resolvePackagePaths } from '../../lib/paths'; import { createTemporaryTsConfig } from './utils'; -import { mkdir, readFile, writeFile } from 'fs/promises'; +import { readFile, writeFile } from 'fs/promises'; import pLimit from 'p-limit'; +import { mkdirp } from 'fs-extra'; +import { PackageDocsCache } from './Cache'; +import { Lockfile } from '@backstage/cli-node'; const limit = pLimit(8); @@ -38,6 +41,7 @@ const EXCLUDE = [ 'packages/techdocs-cli-embedded-app', 'packages/yarn-plugin', 'packages/backend', + 'packages/backend-legacy', ]; const HIGHLIGHT_LANGUAGES = [ @@ -52,6 +56,7 @@ const HIGHLIGHT_LANGUAGES = [ 'diff', 'js', 'json', + 'docker', ]; function getExports(packageJson: any) { @@ -75,41 +80,35 @@ async function generateDocJson(pkg: string) { !exports.length || !exports.some(e => e.startsWith('src') || e.startsWith('./src')) ) { - return; + return false; } - try { - await mkdir(cliPaths.resolveTargetRoot(`dist-types`, pkg), { - recursive: true, - }); + await mkdirp(cliPaths.resolveTargetRoot(`dist-types`, pkg)); - const { stdout, stderr } = await execAsync( - [ - cliPaths.resolveTargetRoot('node_modules/.bin/typedoc'), - '--json', - cliPaths.resolveTargetRoot(`dist-types`, pkg, 'docs.json'), - '--tsconfig', - temporaryTsConfigPath, - '--basePath', - cliPaths.targetRoot, - '--skipErrorChecking', - ...(getExports(packageJson).flatMap(e => [ - '--entryPoints', - e, - ]) as string[]), - ].join(' '), - { - cwd: pkg, - env: { ...process.env, NODE_OPTIONS: '--max-old-space-size=12288' }, - }, - ); - console.log(`### Processed ${pkg}`); - console.log(stdout); - console.error(stderr); - } catch (e) { - console.error('Failed to generate docs for', pkg); - console.error(e); - } + const { stdout, stderr } = await execAsync( + [ + cliPaths.resolveTargetRoot('node_modules/.bin/typedoc'), + '--json', + cliPaths.resolveTargetRoot(`dist-types`, pkg, 'docs.json'), + '--tsconfig', + temporaryTsConfigPath, + '--basePath', + cliPaths.targetRoot, + '--skipErrorChecking', + ...(getExports(packageJson).flatMap(e => [ + '--entryPoints', + e, + ]) as string[]), + ].join(' '), + { + cwd: pkg, + env: { ...process.env, NODE_OPTIONS: '--max-old-space-size=12288' }, + }, + ); + console.log(`### Processed ${pkg}`); + console.log(stdout); + console.error(stderr); + return true; } export default async function packageDocs(paths: string[] = [], opts: any) { @@ -120,6 +119,11 @@ export default async function packageDocs(paths: string[] = [], opts: any) { exclude: opts.exclude, }); + const cache = await PackageDocsCache.loadAsync( + cliPaths.resolveTargetRoot(), + await Lockfile.load(cliPaths.resolveTargetRoot('yarn.lock')), + ); + console.log(`### Generating docs.`); await Promise.all( selectedPackageDirs.map(pkg => @@ -127,8 +131,29 @@ export default async function packageDocs(paths: string[] = [], opts: any) { if (EXCLUDE.includes(pkg)) { return; } - console.log(`### Processing ${pkg}`); - await generateDocJson(pkg); + if (await cache.has(pkg)) { + console.log(`### Skipping ${pkg} due to cache hit`); + try { + await cache.restore(pkg); + return; + } catch (e) { + console.error('Failed to restore cache for', pkg); + console.error(e); + } + } + try { + console.log(`### Processing ${pkg}`); + const success = await generateDocJson(pkg); + if (success) { + await cache.write( + pkg, + cliPaths.resolveTargetRoot(`dist-types`, pkg), + ); + } + } catch (e) { + console.error('Failed to generate docs for', pkg); + console.error(e); + } }), ), ); diff --git a/packages/repo-tools/src/commands/package-docs/constants.ts b/packages/repo-tools/src/commands/package-docs/constants.ts new file mode 100644 index 0000000000..a8372afad8 --- /dev/null +++ b/packages/repo-tools/src/commands/package-docs/constants.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 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..2461d8c549 100644 --- a/packages/repo-tools/templates/typescript-backstage-client/modelEnum.mustache +++ b/packages/repo-tools/templates/typescript-backstage-client/modelEnum.mustache @@ -18,14 +18,3 @@ export enum {{classname}} { */ export type {{classname}} = {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}} | {{/-last}}{{/enumVars}}{{/allowableValues}}; -/** - * @public - */ -export const {{classname}} = { -{{#allowableValues}} -{{#enumVars}} - {{name}}: {{{value}}} as {{classname}}{{^-last}},{{/-last}} -{{/enumVars}} -{{/allowableValues}} -}; -{{/stringEnums}} diff --git a/packages/repo-tools/templates/typescript-backstage-server/modelEnum.mustache b/packages/repo-tools/templates/typescript-backstage-server/modelEnum.mustache index 3a1b5ce86d..2461d8c549 100644 --- a/packages/repo-tools/templates/typescript-backstage-server/modelEnum.mustache +++ b/packages/repo-tools/templates/typescript-backstage-server/modelEnum.mustache @@ -18,14 +18,3 @@ export enum {{classname}} { */ export type {{classname}} = {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}} | {{/-last}}{{/enumVars}}{{/allowableValues}}; -/** - * @public - */ -export const {{classname}} = { -{{#allowableValues}} -{{#enumVars}} - {{name}}: {{{value}}} as {{classname}}{{^-last}},{{/-last}} -{{/enumVars}} -{{/allowableValues}} -}; -{{/stringEnums}} diff --git a/packages/scaffolder-internal/CHANGELOG.md b/packages/scaffolder-internal/CHANGELOG.md index 01824c4df6..d7c85cab14 100644 --- a/packages/scaffolder-internal/CHANGELOG.md +++ b/packages/scaffolder-internal/CHANGELOG.md @@ -1,5 +1,37 @@ # @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 + +## 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 diff --git a/packages/scaffolder-internal/package.json b/packages/scaffolder-internal/package.json index dc727b99d3..ca7e3fa63f 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.3", + "version": "0.0.10-next.2", "backstage": { "role": "web-library", "inline": true diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 4e504b8f60..c8a0016968 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,92 @@ # 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 + +## 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 diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 0683378df9..5a300a9748 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.3", + "version": "0.2.109-next.3", "backstage": { "role": "frontend" }, diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index 3ce409bbc4..a418856ddd 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,46 @@ # @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 + +## 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 diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 3034be2715..89712ac7d5 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.3", + "version": "1.9.4-next.2", "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 e8acf8edd1..ebbf6a99ba 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,19 @@ # @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 + ## 1.7.8-next.2 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 0824422425..c35b80ae46 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.2", + "version": "1.7.8", "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..e4cbb4870b 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/theme +## 0.6.6 + +### Patch Changes + +- 1b14572: Show arrow when MuiTableSortLabel receives focus + ## 0.6.6-next.0 ### Patch Changes diff --git a/packages/theme/package.json b/packages/theme/package.json index acf9b891d4..c483ed5034 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.6", "description": "material-ui theme for use with Backstage.", "backstage": { "role": "web-library" diff --git a/packages/yarn-plugin/CHANGELOG.md b/packages/yarn-plugin/CHANGELOG.md index 7a05151bb0..9b8ab91819 100644 --- a/packages/yarn-plugin/CHANGELOG.md +++ b/packages/yarn-plugin/CHANGELOG.md @@ -1,5 +1,14 @@ # 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 + ## 0.0.5-next.1 ### Patch Changes diff --git a/packages/yarn-plugin/package.json b/packages/yarn-plugin/package.json index c764da4fa6..5195bf5fdc 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.1", + "version": "0.0.5", "description": "Yarn plugin for working with Backstage monorepos", "backstage": { "role": "node-library" diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index e126781b9c..6f76ee2984 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,63 @@ # @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 + +## 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 diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index db9b774cc0..e6be2f9a83 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.3", + "version": "0.12.8-next.2", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin", diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx index cb55f620a7..356328a61d 100644 --- a/plugins/api-docs/src/alpha.tsx +++ b/plugins/api-docs/src/alpha.tsx @@ -228,6 +228,7 @@ const apiDocsApisEntityContent = EntityContentBlueprint.make({ export default createFrontendPlugin({ pluginId: 'api-docs', + info: { packageJson: () => import('../package.json') }, routes: { root: convertLegacyRouteRef(rootRoute), }, diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 2230b0ed43..42beb0377f 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-app-backend +## 0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.34-next.1 + +## 0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/config-loader@1.10.1 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-app-node@0.1.34-next.0 + +## 0.5.2 + +### Patch Changes + +- 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 diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 82cbcf2e94..6a2a2ea09b 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-backend", - "version": "0.5.2-next.2", + "version": "0.5.3-next.1", "description": "A Backstage backend plugin that serves the Backstage frontend app", "backstage": { "role": "backend-plugin", diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md index 9b85efb09a..4052664edc 100644 --- a/plugins/app-node/CHANGELOG.md +++ b/plugins/app-node/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-app-node +## 0.1.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config-loader@1.10.1 + +## 0.1.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/config-loader@1.10.1 + +## 0.1.33 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + - @backstage/config-loader@1.10.1 + ## 0.1.33-next.2 ### Patch Changes diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json index aec12abb23..83d0009c1b 100644 --- a/plugins/app-node/package.json +++ b/plugins/app-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-node", - "version": "0.1.33-next.2", + "version": "0.1.34-next.1", "description": "Node.js library for the app plugin", "backstage": { "role": "node-library", diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md index 9d0513b41a..5de9ee2573 100644 --- a/plugins/app-visualizer/CHANGELOG.md +++ b/plugins/app-visualizer/CHANGELOG.md @@ -1,5 +1,32 @@ # @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 + +## 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/package.json b/plugins/app-visualizer/package.json index d481e89225..607230b237 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.20-next.1", "description": "Visualizes the Backstage app structure", "backstage": { "role": "frontend-plugin", diff --git a/plugins/app-visualizer/src/plugin.tsx b/plugins/app-visualizer/src/plugin.tsx index 92ccd00159..faaf2a5788 100644 --- a/plugins/app-visualizer/src/plugin.tsx +++ b/plugins/app-visualizer/src/plugin.tsx @@ -46,5 +46,6 @@ export const appVisualizerNavItem = NavItemBlueprint.make({ /** @public */ export const visualizerPlugin = createFrontendPlugin({ pluginId: 'app-visualizer', + info: { packageJson: () => import('../package.json') }, extensions: [appVisualizerPage, appVisualizerNavItem], }); diff --git a/plugins/app/CHANGELOG.md b/plugins/app/CHANGELOG.md index 4c96752e76..b41a92bc14 100644 --- a/plugins/app/CHANGELOG.md +++ b/plugins/app/CHANGELOG.md @@ -1,5 +1,41 @@ # @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 + +## 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 diff --git a/plugins/app/package.json b/plugins/app/package.json index c1759b138b..c1a5f80a22 100644 --- a/plugins/app/package.json +++ b/plugins/app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app", - "version": "0.1.9-next.3", + "version": "0.1.10-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "app", diff --git a/plugins/app/src/plugin.ts b/plugins/app/src/plugin.ts index b166423fa1..a1e6602410 100644 --- a/plugins/app/src/plugin.ts +++ b/plugins/app/src/plugin.ts @@ -42,6 +42,7 @@ import { apis } from './defaultApis'; /** @public */ export const appPlugin = createFrontendPlugin({ pluginId: 'app', + info: { packageJson: () => import('../package.json') }, extensions: [ ...apis, App, diff --git a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md index fa0658554a..3ff30201af 100644 --- a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-auth-backend-module-atlassian-provider +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.4.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-atlassian-provider/package.json b/plugins/auth-backend-module-atlassian-provider/package.json index fe708925bd..c146a3a551 100644 --- a/plugins/auth-backend-module-atlassian-provider/package.json +++ b/plugins/auth-backend-module-atlassian-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-atlassian-provider", - "version": "0.4.3-next.2", + "version": "0.4.4-next.1", "description": "The atlassian-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-auth0-provider/CHANGELOG.md b/plugins/auth-backend-module-auth0-provider/CHANGELOG.md index c9ccfe7d41..22cec4e289 100644 --- a/plugins/auth-backend-module-auth0-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-auth0-provider/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-auth-backend-module-auth0-provider +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.3 + - @backstage/backend-plugin-api@1.3.1 + ## 0.2.3-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-auth0-provider/package.json b/plugins/auth-backend-module-auth0-provider/package.json index ce5d1b6cb1..3e18951293 100644 --- a/plugins/auth-backend-module-auth0-provider/package.json +++ b/plugins/auth-backend-module-auth0-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-auth0-provider", - "version": "0.2.3-next.2", + "version": "0.2.4-next.1", "description": "The auth0-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md index cb47375143..a681992dc8 100644 --- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-auth-backend-module-aws-alb-provider +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-backend@0.25.1-next.1 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-backend@0.25.1-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.4.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-aws-alb-provider/package.json b/plugins/auth-backend-module-aws-alb-provider/package.json index b418c74bc1..4382931db3 100644 --- a/plugins/auth-backend-module-aws-alb-provider/package.json +++ b/plugins/auth-backend-module-aws-alb-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-aws-alb-provider", - "version": "0.4.3-next.2", + "version": "0.4.4-next.1", "description": "The aws-alb provider module for the Backstage auth backend.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-aws-alb-provider/report.api.md b/plugins/auth-backend-module-aws-alb-provider/report.api.md index ebb3e8e24a..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'; 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 4c41c3f427..f0359b1e55 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-auth-backend-module-azure-easyauth-provider +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.2.8 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-azure-easyauth-provider/package.json b/plugins/auth-backend-module-azure-easyauth-provider/package.json index 51ded2f229..39ebf38dd6 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/package.json +++ b/plugins/auth-backend-module-azure-easyauth-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-azure-easyauth-provider", - "version": "0.2.8-next.2", + "version": "0.2.9-next.1", "description": "The azure-easyauth-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md index b8fb09bac0..a9c90e8a05 100644 --- a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-auth-backend-module-bitbucket-provider +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.3.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-bitbucket-provider/package.json b/plugins/auth-backend-module-bitbucket-provider/package.json index 2a4630e623..edd1d811da 100644 --- a/plugins/auth-backend-module-bitbucket-provider/package.json +++ b/plugins/auth-backend-module-bitbucket-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-bitbucket-provider", - "version": "0.3.3-next.2", + "version": "0.3.4-next.1", "description": "The bitbucket-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md index 486e011126..7f2c2eac5c 100644 --- a/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-auth-backend-module-bitbucket-server-provider +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.2.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-bitbucket-server-provider/package.json b/plugins/auth-backend-module-bitbucket-server-provider/package.json index c61880fbe9..7c2060a6df 100644 --- a/plugins/auth-backend-module-bitbucket-server-provider/package.json +++ b/plugins/auth-backend-module-bitbucket-server-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-bitbucket-server-provider", - "version": "0.2.3-next.2", + "version": "0.2.4-next.1", "description": "The bitbucket-server-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md index 168292d2f8..1e206cdbaa 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-auth-backend-module-cloudflare-access-provider +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.4.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-cloudflare-access-provider/package.json b/plugins/auth-backend-module-cloudflare-access-provider/package.json index cd4b800bfc..fc9334f958 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/package.json +++ b/plugins/auth-backend-module-cloudflare-access-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-cloudflare-access-provider", - "version": "0.4.3-next.2", + "version": "0.4.4-next.1", "description": "The cloudflare-access-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md index a280efab1a..63b30dba1f 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-auth-backend-module-gcp-iap-provider +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.4.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-gcp-iap-provider/package.json b/plugins/auth-backend-module-gcp-iap-provider/package.json index 6366642ec8..bbdeca08a3 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/package.json +++ b/plugins/auth-backend-module-gcp-iap-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-gcp-iap-provider", - "version": "0.4.3-next.2", + "version": "0.4.4-next.1", "description": "A GCP IAP auth provider module for the Backstage auth backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-github-provider/CHANGELOG.md b/plugins/auth-backend-module-github-provider/CHANGELOG.md index 93a0865d63..ec3305ddca 100644 --- a/plugins/auth-backend-module-github-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-github-provider/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-auth-backend-module-github-provider +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.3.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index bf6529a971..92352dd8aa 100644 --- a/plugins/auth-backend-module-github-provider/package.json +++ b/plugins/auth-backend-module-github-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-github-provider", - "version": "0.3.3-next.2", + "version": "0.3.4-next.1", "description": "The github-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md index 4001ca4ac0..94c6a8a63c 100644 --- a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-auth-backend-module-gitlab-provider +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.3.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-gitlab-provider/package.json b/plugins/auth-backend-module-gitlab-provider/package.json index 677bac514e..8db0c5f881 100644 --- a/plugins/auth-backend-module-gitlab-provider/package.json +++ b/plugins/auth-backend-module-gitlab-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-gitlab-provider", - "version": "0.3.3-next.2", + "version": "0.3.4-next.1", "description": "The gitlab-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-google-provider/CHANGELOG.md b/plugins/auth-backend-module-google-provider/CHANGELOG.md index 08ba55f539..451746eb0a 100644 --- a/plugins/auth-backend-module-google-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-google-provider/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-auth-backend-module-google-provider +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.3.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-google-provider/package.json b/plugins/auth-backend-module-google-provider/package.json index 69f95614f6..d0c77c3bde 100644 --- a/plugins/auth-backend-module-google-provider/package.json +++ b/plugins/auth-backend-module-google-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-google-provider", - "version": "0.3.3-next.2", + "version": "0.3.4-next.1", "description": "A Google auth provider module for the Backstage auth backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-guest-provider/CHANGELOG.md b/plugins/auth-backend-module-guest-provider/CHANGELOG.md index dd906358c1..c9117fb1e2 100644 --- a/plugins/auth-backend-module-guest-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-guest-provider/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-auth-backend-module-guest-provider +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.2.8 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json index 1562ea78fb..92368c3364 100644 --- a/plugins/auth-backend-module-guest-provider/package.json +++ b/plugins/auth-backend-module-guest-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-guest-provider", - "version": "0.2.8-next.2", + "version": "0.2.9-next.1", "description": "The guest-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md index a84516bb02..3ddacc4be9 100644 --- a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-auth-backend-module-microsoft-provider +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.3.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-microsoft-provider/package.json b/plugins/auth-backend-module-microsoft-provider/package.json index 2e04eb41e5..d3ecdcbc4d 100644 --- a/plugins/auth-backend-module-microsoft-provider/package.json +++ b/plugins/auth-backend-module-microsoft-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-microsoft-provider", - "version": "0.3.3-next.2", + "version": "0.3.4-next.1", "description": "The microsoft-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md index 34a3ea522b..67fcf5686d 100644 --- a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-auth-backend-module-oauth2-provider +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.4.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-oauth2-provider/package.json b/plugins/auth-backend-module-oauth2-provider/package.json index 8061de65b6..33c221ce42 100644 --- a/plugins/auth-backend-module-oauth2-provider/package.json +++ b/plugins/auth-backend-module-oauth2-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-provider", - "version": "0.4.3-next.2", + "version": "0.4.4-next.1", "description": "The oauth2-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md index f64ab78eaa..6da4b2529d 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-auth-backend-module-oauth2-proxy-provider +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.2.8 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/package.json b/plugins/auth-backend-module-oauth2-proxy-provider/package.json index b77acc0055..2df01732ac 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/package.json +++ b/plugins/auth-backend-module-oauth2-proxy-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-proxy-provider", - "version": "0.2.8-next.2", + "version": "0.2.9-next.1", "description": "The oauth2-proxy-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md index f1d17a579d..37f529cbbc 100644 --- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-auth-backend-module-oidc-provider +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-backend@0.25.1-next.1 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-backend@0.25.1-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.4.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json index 33f323c335..621726e6c4 100644 --- a/plugins/auth-backend-module-oidc-provider/package.json +++ b/plugins/auth-backend-module-oidc-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oidc-provider", - "version": "0.4.3-next.2", + "version": "0.4.4-next.1", "description": "The oidc-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-okta-provider/CHANGELOG.md b/plugins/auth-backend-module-okta-provider/CHANGELOG.md index 79660f330f..2c4022fc73 100644 --- a/plugins/auth-backend-module-okta-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-okta-provider/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-auth-backend-module-okta-provider +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.2.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-okta-provider/package.json b/plugins/auth-backend-module-okta-provider/package.json index 70d8defb25..9bc143907a 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.2", + "version": "0.2.4-next.1", "description": "The okta-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -36,7 +36,7 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", - "@davidzemon/passport-okta-oauth": "^0.0.5", + "@davidzemon/passport-okta-oauth": "^0.0.6", "express": "^4.18.2", "passport": "^0.7.0", "zod": "^3.22.4" 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 c6e5d2944a..dce3cd0c6c 100644 --- a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-auth-backend-module-onelogin-provider +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.3.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-onelogin-provider/package.json b/plugins/auth-backend-module-onelogin-provider/package.json index 6867251e54..58f5489bbc 100644 --- a/plugins/auth-backend-module-onelogin-provider/package.json +++ b/plugins/auth-backend-module-onelogin-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-onelogin-provider", - "version": "0.3.3-next.2", + "version": "0.3.4-next.1", "description": "The onelogin-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md index 865f494229..0c31e3d28e 100644 --- a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-auth-backend-module-pinniped-provider +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.3.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-pinniped-provider/package.json b/plugins/auth-backend-module-pinniped-provider/package.json index 8c0569dbb4..4cde024617 100644 --- a/plugins/auth-backend-module-pinniped-provider/package.json +++ b/plugins/auth-backend-module-pinniped-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-pinniped-provider", - "version": "0.3.3-next.2", + "version": "0.3.4-next.1", "description": "The pinniped-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md index 62f2a9373d..e8e5ac6a05 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-auth-backend-module-vmware-cloud-provider +## 0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + +## 0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.5.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-backend-module-vmware-cloud-provider/package.json b/plugins/auth-backend-module-vmware-cloud-provider/package.json index 7f3c77a1e4..2aa4cc56bc 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/package.json +++ b/plugins/auth-backend-module-vmware-cloud-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-vmware-cloud-provider", - "version": "0.5.3-next.2", + "version": "0.5.4-next.1", "description": "The vmware-cloud-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index ebf41fff38..0c97d879ae 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,76 @@ # @backstage/plugin-auth-backend +## 0.25.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.25.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## 0.25.0 + +### Minor Changes + +- 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 diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index ce99f85b08..9ba97ee8ac 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.25.0-next.2", + "version": "0.25.1-next.1", "description": "A Backstage backend plugin that handles authentication", "backstage": { "role": "backend-plugin", diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index 5e60a40cd3..5745219625 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,75 @@ # @backstage/plugin-auth-node +## 0.6.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.6.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## 0.6.3 + +### Patch Changes + +- 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 diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 2c9e8c007d..fe24d798d6 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.6.3-next.2", + "version": "0.6.4-next.1", "backstage": { "role": "node-library", "pluginId": "auth", diff --git a/plugins/auth-node/report.api.md b/plugins/auth-node/report.api.md index 090f981636..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'; @@ -575,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-react/CHANGELOG.md b/plugins/auth-react/CHANGELOG.md index 0a1043daa1..6226e7d1af 100644 --- a/plugins/auth-react/CHANGELOG.md +++ b/plugins/auth-react/CHANGELOG.md @@ -1,5 +1,24 @@ # @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 + +## 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/package.json b/plugins/auth-react/package.json index 3a04afbf51..89c079abe8 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.16-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 4b6241bb7b..6924ab6310 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,29 @@ # @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 + ## 0.3.0-next.3 ### Patch Changes diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index 40006fc8d7..97150d6e8d 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.3", + "version": "0.3.0", "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 d22346d163..f2ec30a82a 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,63 @@ # @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 + +## 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 diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index a3cb716c29..13586e3a90 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.3", + "version": "0.4.12-next.2", "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 3e9a4c6aad..c59dfea5b3 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.3.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 diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 7f1d5453d7..cfc5f2a30d 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", - "version": "0.3.5-next.3", + "version": "0.3.6-next.1", "description": "A Backstage catalog backend module that helps integrate towards Azure", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-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 5160254a3a..f2282c7796 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-openapi-utils@0.5.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/backend-openapi-utils@0.5.4-next.0 + +## 0.5.2 + +### Patch Changes + +- 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 diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index a7f4f1aca2..b3669aa611 100644 --- a/plugins/catalog-backend-module-backstage-openapi/package.json +++ b/plugins/catalog-backend-module-backstage-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-backstage-openapi", - "version": "0.5.2-next.2", + "version": "0.5.3-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index 06202b5624..4bbaf6b350 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,71 @@ # @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 + +## 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 diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index 48ff0b17d4..119352bc8d 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.3", + "version": "0.5.0-next.2", "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 8b6ff29b28..7506f728cf 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## 0.4.1 + +### Patch Changes + +- 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 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/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index ca241c615a..de7fb993e9 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-server", - "version": "0.4.1-next.3", + "version": "0.4.2-next.1", "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 215f5c16bc..99df738894 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/plugin-kubernetes-common@0.9.5 + +## 0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## 0.3.8 + +### Patch Changes + +- 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 diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index 7be8d3f195..dbcd8c8249 100644 --- a/plugins/catalog-backend-module-gcp/package.json +++ b/plugins/catalog-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gcp", - "version": "0.3.8-next.2", + "version": "0.3.9-next.1", "description": "A Backstage catalog backend module that helps integrate towards GCP", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index d74fad602d..104a13390d 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## 0.3.2 + +### Patch Changes + +- 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 diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index a34a769b3c..5568702238 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.3.2-next.3", + "version": "0.3.3-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-gitea/CHANGELOG.md b/plugins/catalog-backend-module-gitea/CHANGELOG.md index 35e32e6169..849f8205b4 100644 --- a/plugins/catalog-backend-module-gitea/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-catalog-backend-module-gitea +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## 0.1.0 + +### Minor Changes + +- 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 diff --git a/plugins/catalog-backend-module-gitea/package.json b/plugins/catalog-backend-module-gitea/package.json index 22f4dc5460..714252d43a 100644 --- a/plugins/catalog-backend-module-gitea/package.json +++ b/plugins/catalog-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitea", - "version": "0.1.0-next.0", + "version": "0.1.1-next.1", "license": "Apache-2.0", "description": "The gitea backend module for the catalog plugin.", "main": "src/index.ts", diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index a3ac67d24d..6c70ea07dc 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,49 @@ # @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 + +## 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 diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index ebe2e4582b..8852264996 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.3", + "version": "0.3.11-next.2", "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 0c3b3e2a63..6ecf3025f3 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,65 @@ # @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 + +## 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 diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index a0b29c1c9e..62c37e8c2b 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.3", + "version": "0.9.1-next.2", "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/providers/GithubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts index b7df19c296..4032e1d368 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,27 @@ 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); + }); }); describe('on repository event', () => { @@ -1189,6 +1210,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 131237c581..5f3c652cb9 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -323,7 +323,13 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { } private async onPush(event: PushEvent) { - if (this.config.organization !== event.organization?.login) { + const configOrganization = this.config.organization; + const eventOrganization = event.organization?.login; + + if ( + configOrganization.toLocaleLowerCase('en-US') !== + eventOrganization?.toLocaleLowerCase('en-US') + ) { this.logger.debug( `skipping push event from organization ${event.organization?.login}`, ); @@ -406,7 +412,13 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { } private async onRepoChange(event: RepositoryEvent) { - if (this.config.organization !== event.organization?.login) { + const configOrganization = this.config.organization; + const eventOrganization = event.organization?.login; + + if ( + configOrganization.toLocaleLowerCase('en-US') !== + eventOrganization?.toLocaleLowerCase('en-US') + ) { this.logger.debug( `skipping repository event from organization ${event.organization?.login}`, ); diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md index 61983705d7..7214eb1122 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,45 @@ # @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 + +## 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 diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index 44480b3cc9..08a1be049e 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.3", + "version": "0.2.10-next.2", "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 b2cc0ee53c..3193372ca1 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,63 @@ # @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 + +## 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 diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 108d05023e..d4acfecabf 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.3", + "version": "0.7.0-next.2", "description": "A Backstage catalog backend module that helps integrate towards GitLab", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts b/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts index 9ffb43a53d..0732bbc4cd 100644 --- a/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts +++ b/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts @@ -30,6 +30,9 @@ import { some_endpoint, unhealthy_endpoint, userID, + all_saas_subgroup_1_members, + all_saas_subgroup_2_members, + group_with_subgroups_response, } from './mocks'; const httpHandlers = [ @@ -71,6 +74,13 @@ const httpHandlers = [ return res(ctx.set('x-next-page', ''), ctx.json(all_groups_response)); }), + rest.get(`${apiBaseUrl}/groups/group-with-subgroup`, (_, res, ctx) => { + return res( + ctx.set('x-next-page', ''), + ctx.json(group_with_subgroups_response), + ); + }), + rest.get(`${apiBaseUrl}/groups/42`, (_, res, ctx) => { return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' })); }), @@ -89,6 +99,24 @@ const httpHandlers = [ }, ), + rest.get(`${apiBaseUrlSaas}/groups/456/members/all`, (_req, res, ctx) => { + return res(ctx.json(all_saas_users_response)); + }), + + rest.get(`${apiBaseUrlSaas}/groups/1/members/all`, (_req, res, ctx) => { + return res(ctx.json(all_saas_users_response)); + }), + + // Subgroup 1 members id=6 + rest.get(`${apiBaseUrlSaas}/groups/6/members/all`, (_req, res, ctx) => { + return res(ctx.json(all_saas_subgroup_1_members)); + }), + + // Subgroup 2 members id=7 + rest.get(`${apiBaseUrlSaas}/groups/7/members/all`, (_req, res, ctx) => { + return res(ctx.json(all_saas_subgroup_2_members)); + }), + /** * Users REST endpoint mocks */ @@ -608,7 +636,47 @@ const graphqlHandlers = [ graphql .link(saasGraphQlBaseUrl) - .query('listDescendantGroups', async (_, res, ctx) => { + .query('listDescendantGroups', async (req, res, ctx) => { + const { group } = req.variables; + + if (group === 'group1') { + return res( + ctx.data({ + group: { + descendantGroups: { + nodes: req.variables.endCursor + ? [ + { + id: 'gid://gitlab/Group/6', + name: 'subgroup1', + description: 'description1', + fullPath: 'path/subgroup1', + parent: { + id: '1', + }, + }, + ] + : [ + { + id: 'gid://gitlab/Group/7', + name: 'subgroup2', + description: 'description2', + fullPath: 'path/subgroup2', + parent: { + id: '1', + }, + }, + ], + pageInfo: { + endCursor: req.variables.endCursor ? 'end' : 'next', + hasNextPage: !req.variables.endCursor, + }, + }, + }, + }), + ); + } + return res( ctx.data({ group: { diff --git a/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts b/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts index d9914c0d0c..2e74be66cc 100644 --- a/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts +++ b/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts @@ -796,6 +796,30 @@ export const config_org_group_restrictUsers_true_selfHosted = { }, }, }; + +export const config_org_group_with_subgroups_sass = { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + apiBaseUrl: 'https://gitlab.com/api/v4', + token: '1234', + }, + ], + }, + catalog: { + providers: { + gitlab: { + 'test-id': { + host: 'gitlab.com', + group: 'group1', + orgEnabled: true, + skipForkedRepos: true, + }, + }, + }, + }, +}; /** * GitLab API responses */ @@ -1020,7 +1044,72 @@ export const all_saas_users_response: MockObject[] = [ access_level: 30, created_at: '2023-07-19T08:58:34.984Z', expires_at: null, - id: 34, + id: 36, + username: 'testusernoseat1', + name: 'Test User No Seat 1', + state: 'active', + avatar_url: 'https://secure.gravatar.com/', + web_url: 'https://gitlab.com/testusernoseat1', + email: 'testusernoseat1@example.com', + group_saml_identity: { + provider: 'group_saml', + extern_uid: '60', + saml_provider_id: 1, + }, + is_using_seat: false, + membership_state: 'active', + }, +]; + +// Only test user 1 +export const all_saas_group_with_subgroups_members: MockObject[] = [ + { + access_level: 30, + created_at: '2023-07-17T08:58:34.984Z', + expires_at: null, + id: 12, + username: 'testuser1', + name: 'Test User 1', + state: 'active', + avatar_url: 'https://secure.gravatar.com/', + web_url: 'https://gitlab.com/testuser1', + email: 'testuser1@example.com', + group_saml_identity: { + provider: 'group_saml', + extern_uid: '50', + saml_provider_id: 1, + }, + is_using_seat: true, + membership_state: 'active', + }, +]; + +// Only test user 1 +export const all_saas_subgroup_1_members: MockObject[] = [ + { + access_level: 30, + created_at: '2023-07-17T08:58:34.984Z', + expires_at: null, + id: 12, + username: 'testuser1', + name: 'Test User 1', + state: 'active', + avatar_url: 'https://secure.gravatar.com/', + web_url: 'https://gitlab.com/testuser1', + email: 'testuser1@example.com', + group_saml_identity: { + provider: 'group_saml', + extern_uid: '51', + saml_provider_id: 1, + }, + is_using_seat: true, + membership_state: 'active', + }, + { + access_level: 30, + created_at: '2023-07-17T08:58:34.984Z', + expires_at: null, + id: 33, username: 'testuser3', name: 'Test User 3', state: 'active', @@ -1032,7 +1121,49 @@ export const all_saas_users_response: MockObject[] = [ extern_uid: '53', saml_provider_id: 1, }, - is_using_seat: false, + is_using_seat: true, + membership_state: 'active', + }, +]; + +// Only test user 2 +export const all_saas_subgroup_2_members: MockObject[] = [ + { + access_level: 30, + created_at: '2023-07-19T08:58:34.984Z', + expires_at: null, + id: 34, + username: 'testuser2', + name: 'Test User 2', + state: 'active', + avatar_url: 'https://secure.gravatar.com/', + web_url: 'https://gitlab.com/testuser2', + email: 'testuser2@example.com', + group_saml_identity: { + provider: 'group_saml', + extern_uid: '52', + saml_provider_id: 1, + }, + is_using_seat: true, + membership_state: 'active', + }, + { + access_level: 30, + created_at: '2023-07-17T08:58:34.984Z', + expires_at: null, + id: 44, + username: 'testuser4', + name: 'Test User 4', + state: 'active', + avatar_url: 'https://secure.gravatar.com/', + web_url: 'https://gitlab.com/testuser4', + email: 'testuser4@example.com', + group_saml_identity: { + provider: 'group_saml', + extern_uid: '54', + saml_provider_id: 1, + }, + is_using_seat: true, membership_state: 'active', }, ]; @@ -1074,6 +1205,21 @@ export const all_groups_response: GitLabGroup[] = [ description: '', full_path: 'group1/subgroup1', }, + { + id: 7, + name: 'subgroup2', + description: '', + full_path: 'group1/subgroup2', + }, +]; + +export const group_with_subgroups_response: GitLabGroup[] = [ + { + id: 1, + name: 'group1', + description: 'description1', + full_path: 'group1', + }, ]; export const group_with_parent: MockObject[] = [ @@ -2183,6 +2329,58 @@ export const expected_full_org_scan_entities_saas: MockObject[] = [ }, locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id', }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://gitlab.com/testuser4', + 'backstage.io/managed-by-origin-location': + 'url:https://gitlab.com/testuser4', + 'gitlab.com/user-login': 'https://gitlab.com/testuser4', + 'gitlab.com/saml-external-uid': '54', + }, + name: 'testuser4', + }, + spec: { + memberOf: [], + profile: { + displayName: 'Test User 4', + email: 'testuser4@example.com', + picture: 'https://secure.gravatar.com/', + }, + }, + }, + locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id', + }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://gitlab.com/testuser3', + 'backstage.io/managed-by-origin-location': + 'url:https://gitlab.com/testuser3', + 'gitlab.com/user-login': 'https://gitlab.com/testuser3', + 'gitlab.com/saml-external-uid': '53', + }, + name: 'testuser3', + }, + spec: { + memberOf: [], + profile: { + displayName: 'Test User 3', + email: 'testuser3@example.com', + picture: 'https://secure.gravatar.com/', + }, + }, + }, + locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id', + }, ]; export const expected_full_org_scan_entities_includeUsersWithoutSeat_saas: MockObject[] = @@ -2239,6 +2437,58 @@ export const expected_full_org_scan_entities_includeUsersWithoutSeat_saas: MockO }, locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id', }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://gitlab.com/testusernoseat1', + 'backstage.io/managed-by-origin-location': + 'url:https://gitlab.com/testusernoseat1', + 'gitlab.com/user-login': 'https://gitlab.com/testusernoseat1', + 'gitlab.com/saml-external-uid': '60', + }, + name: 'testusernoseat1', + }, + spec: { + memberOf: [], + profile: { + displayName: 'Test User No Seat 1', + email: 'testusernoseat1@example.com', + picture: 'https://secure.gravatar.com/', + }, + }, + }, + locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id', + }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://gitlab.com/testuser4', + 'backstage.io/managed-by-origin-location': + 'url:https://gitlab.com/testuser4', + 'gitlab.com/user-login': 'https://gitlab.com/testuser4', + 'gitlab.com/saml-external-uid': '54', + }, + name: 'testuser4', + }, + spec: { + memberOf: [], + profile: { + displayName: 'Test User 4', + email: 'testuser4@example.com', + picture: 'https://secure.gravatar.com/', + }, + }, + }, + locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id', + }, { entity: { apiVersion: 'backstage.io/v1alpha1', diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.ts index 9053d68554..e58227839e 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/client.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/client.ts @@ -190,9 +190,8 @@ export class GitLabClient { let endCursor: string | null = null; do { - const response: GitLabDescendantGroupsResponse = await fetch( - `${this.config.baseUrl}/api/graphql`, - { + const response: GitLabDescendantGroupsResponse = + await this.fetchWithRetry(`${this.config.baseUrl}/api/graphql`, { method: 'POST', headers: { ...getGitLabRequestOptions(this.config).headers, @@ -223,8 +222,7 @@ export class GitLabClient { } `, }), - }, - ).then(r => r.json()); + }).then(r => r.json()); if (response.errors) { throw new Error(`GraphQL errors: ${JSON.stringify(response.errors)}`); } @@ -266,7 +264,7 @@ export class GitLabClient { let hasNextPage: boolean = false; let endCursor: string | null = null; do { - const response: GitLabGroupMembersResponse = await fetch( + const response: GitLabGroupMembersResponse = await this.fetchWithRetry( `${this.config.baseUrl}/api/graphql`, { method: 'POST', @@ -359,7 +357,7 @@ export class GitLabClient { const request = new URL(`${this.config.apiBaseUrl}${endpoint}`); request.searchParams.append('ref', branch); - const response = await fetch(request.toString(), { + const response = await this.fetchWithRetry(request.toString(), { headers: getGitLabRequestOptions(this.config).headers, method: 'HEAD', }); @@ -406,7 +404,7 @@ export class GitLabClient { } this.logger.debug(`Fetching: ${request.toString()}`); - const response = await fetch( + const response = await this.fetchWithRetry( request.toString(), getGitLabRequestOptions(this.config), ); @@ -444,7 +442,7 @@ export class GitLabClient { } } - const response = await fetch( + const response = await this.fetchWithRetry( request.toString(), getGitLabRequestOptions(this.config), ); @@ -459,6 +457,48 @@ export class GitLabClient { return response.json(); } + + /** + * Performs a fetch request with retry logic for rate limiting (429 errors) + * @param url - The URL to fetch + * @param options - Fetch options + * @param retries - Maximum number of retries + * @param initialBackoff - Initial backoff time in ms + */ + async fetchWithRetry( + url: string, + options: fetch.RequestInit, + retries = 5, + initialBackoff = 100, + ): Promise { + let currentRetry = 0; + let backoff = initialBackoff; + + for (;;) { + const response = await fetch(url, options); + + if (response.status !== 429 || currentRetry >= retries) { + return response; + } + + // Get retry-after header if available, or use exponential backoff + const retryAfter = response.headers.get('Retry-After'); + const waitTime = retryAfter ? parseInt(retryAfter, 10) * 1000 : backoff; + + this.logger.warn( + `GitLab API rate limit exceeded, retrying in ${waitTime}ms (retry ${ + currentRetry + 1 + }/${retries})`, + ); + + // Wait before retrying + await new Promise(resolve => setTimeout(resolve, waitTime)); + + // Exponential backoff with jitter + backoff = backoff * 2 * (0.8 + Math.random() * 0.4); + currentRetry++; + } + } } /** diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts index 5f62cedb71..047488166a 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts @@ -303,7 +303,7 @@ describe('GitlabOrgDiscoveryEntityProvider - refresh', () => { }); }); - // This should return all members of the SaaS Root group (group1) -> expected_full_org_scan_entities_saas + // This should return all members of the SaaS Root group (group1) and any subgroups -> expected_full_org_scan_entities_saas it('SaaS: should get all saas root group users when restrictUsersToGroup is not set', async () => { const config = new ConfigReader(mock.config_org_group_saas); const schedule = new PersistingTaskRunner(); @@ -334,7 +334,7 @@ describe('GitlabOrgDiscoveryEntityProvider - refresh', () => { }); }); - // This should return all members of the SaaS Root group (group1) -> expected_full_org_scan_entities_saas + // This should return all members of the SaaS Root group (group1) and any subgroups -> expected_full_org_scan_entities_saas it('SaaS: should get all saas root group users when restrictUsersToGroup is false', async () => { const config = new ConfigReader( mock.config_org_group_restrictUsers_false_saas, diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts index 1fbd2a2a13..3eec377e73 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts @@ -359,7 +359,7 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { } let groups; - let users; + const allUsers = []; // Self-hosted: Fetch the users either from the defined group (restrictUsersToGroup) or fetch all users from the GitLab instance // SaaS: Fetch the users from the defined group (restrictUsersToGroup) or fetch all users from the root group. @@ -367,13 +367,15 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { groups = (await this.gitLabClient.listDescendantGroups(this.config.group)) .items; groups.push(await this.gitLabClient.getGroupByPath(this.config.group)); // adds the parent group for #26554 - users = paginated( - options => - this.gitLabClient.listGroupMembers(this.config.group, options), // calls /groups//members - { - page: 1, - per_page: 100, - }, + allUsers.push( + paginated( + options => + this.gitLabClient.listGroupMembers(this.config.group, options), // calls /groups//members + { + page: 1, + per_page: 100, + }, + ), ); } else if ( this.gitLabClient.isSelfManaged() && @@ -387,35 +389,46 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { all_available: true, }, ); - users = paginated( - options => this.gitLabClient.listUsers(options), // calls /users? - { page: 1, per_page: 100, active: true }, + allUsers.push( + paginated( + options => this.gitLabClient.listUsers(options), // calls /users? + { page: 1, per_page: 100, active: true }, + ), ); } // SaaS: Fetch the users from the defined group (restrictUsersToGroup) or fetch all users from the root group. else { - groups = (await this.gitLabClient.listDescendantGroups(this.config.group)) - .items; + const descendantGroups = ( + await this.gitLabClient.listDescendantGroups(this.config.group) + ).items; + groups = descendantGroups; groups.push(await this.gitLabClient.getGroupByPath(this.config.group)); // adds the parent group for #26554 const rootGroupSplit = this.config.group.split('/'); - const groupPath = this.config.restrictUsersToGroup - ? this.config.group - : rootGroupSplit[0]; - users = paginated( - options => - this.gitLabClient.listSaaSUsers( - groupPath, - options, - this.config.includeUsersWithoutSeat, + const groupPaths = this.config.restrictUsersToGroup + ? [this.config.group] + : [rootGroupSplit[0], ...descendantGroups.map(g => `${g.id}`)]; + + // Fetch users group and descendant groups + for (const group of groupPaths) { + logger.debug(`Fetching users for group: ${group}`); + allUsers.push( + paginated( + options => + this.gitLabClient.listSaaSUsers( + group, + options, + this.config.includeUsersWithoutSeat, + ), + { + page: 1, + per_page: 100, + }, ), - { - page: 1, - per_page: 100, - }, - ); + ); + } } const idMappedUser: { [userId: number]: GitLabUser } = {}; @@ -430,16 +443,23 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { matches: [], }; - for await (const user of users) { - userRes.scanned++; + for (const users of allUsers) { + // Iterate through paginated users + for await (const user of users) { + // Skip if user already processed and do not count it as scanned + if (user.id in idMappedUser) { + continue; // Skip if user already processed + } + userRes.scanned++; - if (!this.shouldProcessUser(user)) { - logger.debug(`Skipped user: ${user.username}`); - continue; + if (!this.shouldProcessUser(user)) { + logger.debug(`Skipped user: ${user.username}`); + continue; + } + + idMappedUser[user.id] = user; + userRes.matches.push(user); } - - idMappedUser[user.id] = user; - userRes.matches.push(user); } for await (const group of groups) { diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index 707a61f0d2..93fc756405 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,68 @@ # @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 + +## 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 diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 074966b7a3..ed4841703c 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.3", + "version": "0.7.1-next.2", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index db8ec2bec7..53039b1260 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.11.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.11.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## 0.11.5 + +### Patch Changes + +- 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 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 79919b8d54..a5d22ba3f5 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", - "version": "0.11.5-next.3", + "version": "0.11.6-next.1", "description": "A Backstage catalog backend module that helps integrate towards LDAP", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-ldap/report.api.md b/plugins/catalog-backend-module-ldap/report.api.md index 16546cb773..8357a9ae38 100644 --- a/plugins/catalog-backend-module-ldap/report.api.md +++ b/plugins/catalog-backend-module-ldap/report.api.md @@ -63,8 +63,8 @@ export type GroupConfig = { displayName: string; email?: string; picture?: string; - memberOf: string; - members: string; + memberOf: string | null; + members: string | null; }; }; @@ -259,7 +259,7 @@ export type UserConfig = { displayName: string; email: string; picture?: string; - memberOf: string; + memberOf: string | null; }; }; 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/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-logs/CHANGELOG.md b/plugins/catalog-backend-module-logs/CHANGELOG.md index d7c498bad0..18811c6598 100644 --- a/plugins/catalog-backend-module-logs/CHANGELOG.md +++ b/plugins/catalog-backend-module-logs/CHANGELOG.md @@ -1,5 +1,42 @@ # @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 + +## 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 diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index 857be5ed67..481ffba3ad 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.3", + "version": "0.1.11-next.2", "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 9d6ca9759d..9e495277c3 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## 0.7.0 + +### Minor Changes + +- 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 diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 606bf9fd51..67285b2897 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", - "version": "0.7.0-next.2", + "version": "0.7.1-next.1", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index c0acf65480..0db664e8c8 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## 0.2.10 + +### Patch Changes + +- 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 diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 13c4e7200a..742cf6e796 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", - "version": "0.2.10-next.3", + "version": "0.2.11-next.1", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md index f650e83545..f1c0618cc8 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.2.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 diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index c354a7c791..0df55cbbe0 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-puppetdb", - "version": "0.2.10-next.2", + "version": "0.2.11-next.1", "description": "A Backstage catalog backend module that helps integrate towards PuppetDB", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md index 4d8d4817d2..1eacdb3f61 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## 0.2.8 + +### Patch Changes + +- 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 diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/package.json b/plugins/catalog-backend-module-scaffolder-entity-model/package.json index 06c35eba9f..2c78f3f07c 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/package.json +++ b/plugins/catalog-backend-module-scaffolder-entity-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-scaffolder-entity-model", - "version": "0.2.8-next.2", + "version": "0.2.9-next.1", "description": "Adds support for the scaffolder specific entity model (e.g. the Template kind) to the catalog backend plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md index 74e023e6a1..87e43914b0 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.6.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.8 + - @backstage/plugin-permission-common@0.9.0 + +## 0.6.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## 0.6.0 + +### Minor Changes + +- 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 diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index c7291b57df..f8a7d529b1 100644 --- a/plugins/catalog-backend-module-unprocessed/package.json +++ b/plugins/catalog-backend-module-unprocessed/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-unprocessed", - "version": "0.6.0-next.2", + "version": "0.6.1-next.1", "description": "Backstage Catalog module to view unprocessed entities", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index f1023af433..a005c33d06 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,148 @@ # @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 + +## 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 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 ad2aa6b6fd..de0be65d06 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.3", + "version": "2.0.1-next.2", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin", diff --git a/plugins/catalog-backend/report-alpha.api.md b/plugins/catalog-backend/report-alpha.api.md index ca521a568d..caa77a0d40 100644 --- a/plugins/catalog-backend/report-alpha.api.md +++ b/plugins/catalog-backend/report-alpha.api.md @@ -30,6 +30,7 @@ export const catalogConditions: Conditions<{ 'catalog-entity', { label: string; + value?: string | undefined; } >; hasMetadata: PermissionRule< @@ -103,6 +104,7 @@ export const permissionRules: { 'catalog-entity', { label: string; + value?: string | undefined; } >; hasMetadata: PermissionRule< diff --git a/plugins/catalog-backend/report.sql.md b/plugins/catalog-backend/report.sql.md index 3402b05063..4f76d2a3f5 100644 --- a/plugins/catalog-backend/report.sql.md +++ b/plugins/catalog-backend/report.sql.md @@ -8,7 +8,7 @@ ## Sequences - `location_update_log_id_seq` (bigint) -- `refresh_state_references_id_seq` (integer) +- `refresh_state_references_id_seq` (bigint) ## Table `final_entities` @@ -93,12 +93,12 @@ ## Table `refresh_state_references` -| Column | Type | Nullable | Max Length | Default | -| ------------------- | --------- | -------- | ---------- | ------------------------------------------------------ | -| `id` | `integer` | false | - | `nextval('refresh_state_references_id_seq'::regclass)` | -| `source_entity_ref` | `text` | true | - | - | -| `source_key` | `text` | true | - | - | -| `target_entity_ref` | `text` | false | - | - | +| Column | Type | Nullable | Max Length | Default | +| ------------------- | -------- | -------- | ---------- | ------------------------------------------------------ | +| `id` | `bigint` | false | - | `nextval('refresh_state_references_id_seq'::regclass)` | +| `source_entity_ref` | `text` | true | - | - | +| `source_key` | `text` | true | - | - | +| `target_entity_ref` | `text` | false | - | - | ### Indices diff --git a/plugins/catalog-backend/src/database/DefaultProviderDatabase.test.ts b/plugins/catalog-backend/src/database/DefaultProviderDatabase.test.ts index 383e06100f..d9465b27a6 100644 --- a/plugins/catalog-backend/src/database/DefaultProviderDatabase.test.ts +++ b/plugins/catalog-backend/src/database/DefaultProviderDatabase.test.ts @@ -610,16 +610,18 @@ describe('DefaultProviderDatabase', () => { ); let references = await knex( 'refresh_state_references', - ).select(); + ) + .select() + .orderBy('id'); expect(references).toEqual([ { - id: 1, + id: expect.anything(), source_key: 'lols', source_entity_ref: null, target_entity_ref: 'component:default/a', }, { - id: 2, + id: expect.anything(), source_key: 'lols', source_entity_ref: null, target_entity_ref: 'component:default/b', @@ -670,16 +672,18 @@ describe('DefaultProviderDatabase', () => { ); references = await knex( 'refresh_state_references', - ).select(); + ) + .select() + .orderBy('id'); expect(references).toEqual([ { - id: 2, + id: expect.anything(), source_key: 'lols', source_entity_ref: null, target_entity_ref: 'component:default/b', }, { - id: 3, + id: expect.anything(), source_key: 'lols', source_entity_ref: null, target_entity_ref: 'component:default/a', diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts index 8aa43402c5..7743120eaa 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts @@ -73,6 +73,50 @@ describe('hasLabel permission rule', () => { ), ).toEqual(true); }); + + it('returns false when specified label has different than expected value', () => { + expect( + hasLabel.apply( + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + labels: { + someLabel: 'foo', + 'backstage.io/testLabel': 'bar', + }, + }, + }, + { + label: 'backstage.io/testLabel', + value: 'baz', + }, + ), + ).toEqual(false); + }); + + it('returns true when specified label has expected value', () => { + expect( + hasLabel.apply( + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + labels: { + someLabel: 'foo', + 'backstage.io/testLabel': 'bar', + }, + }, + }, + { + label: 'backstage.io/testLabel', + value: 'bar', + }, + ), + ).toEqual(true); + }); }); describe('toQuery', () => { @@ -85,5 +129,17 @@ describe('hasLabel permission rule', () => { key: 'metadata.labels.backstage.io/testlabel', }); }); + + it('returns an appropriate catalog-backend filter with values', () => { + expect( + hasLabel.toQuery({ + label: 'backstage.io/testLabel', + value: 'foo', + }), + ).toEqual({ + key: 'metadata.labels.backstage.io/testLabel', + values: ['foo'], + }); + }); }); }); diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts index e825b05b04..40a96dbf49 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts @@ -29,10 +29,18 @@ export const hasLabel = createPermissionRule({ resourceRef: catalogEntityPermissionResourceRef, paramsSchema: z.object({ label: z.string().describe('Name of the label to match on'), + value: z.string().optional().describe('Value of the label to match on'), }), - apply: (resource, { label }) => - !!resource.metadata.labels?.hasOwnProperty(label), - toQuery: ({ label }) => ({ - key: `metadata.labels.${label}`, - }), + apply: (resource, { label, value }) => + !!resource.metadata.labels?.hasOwnProperty(label) && + (value === undefined ? true : resource.metadata.labels?.[label] === value), + toQuery: ({ label, value }) => + value === undefined + ? { + key: `metadata.labels.${label}`, + } + : { + key: `metadata.labels.${label}`, + values: [value], + }, }); diff --git a/plugins/catalog-backend/src/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/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index b0dddbdef0..193d5b5b52 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 = { @@ -799,12 +799,6 @@ describe('createRouter readonly disabled', () => { metadata: { name: 'n' }, }; - permissionsService.authorize.mockResolvedValueOnce([ - { - result: AuthorizeResult.ALLOW, - }, - ]); - orchestrator.process.mockResolvedValueOnce({ ok: true, state: {}, @@ -845,12 +839,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 +876,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 +922,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 = { @@ -1144,7 +1126,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/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..e0b901855d 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 + ## 1.1.4-next.0 ### Patch Changes diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index 3cf4c885a9..17e631a547 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.4", "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 6edba7a4fd..b592b92029 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,60 @@ # @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 + +## 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 diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 8319e40ec9..fa506eabda 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.3", + "version": "0.4.20-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-graph", diff --git a/plugins/catalog-graph/src/alpha.tsx b/plugins/catalog-graph/src/alpha.tsx index d70232e92a..aa1f6a05cf 100644 --- a/plugins/catalog-graph/src/alpha.tsx +++ b/plugins/catalog-graph/src/alpha.tsx @@ -87,6 +87,7 @@ const CatalogGraphPage = PageBlueprint.makeWithOverrides({ export default createFrontendPlugin({ pluginId: 'catalog-graph', + info: { packageJson: () => import('../package.json') }, routes: { catalogGraph: convertLegacyRouteRef(catalogGraphRouteRef), }, diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts index 0a5e43bf37..08b718d789 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts @@ -34,6 +34,17 @@ const useEntityRelationGraph = useEntityRelationGraphMocked as jest.Mock< ReturnType >; +/* + 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-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 42bdd25b75..def4991947 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,77 @@ # @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 + +## 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 diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index a9f5c79f05..78d35330a7 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.3", + "version": "0.13.1-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..229c2ff10c 100644 --- a/plugins/catalog-import/report-alpha.api.md +++ b/plugins/catalog-import/report-alpha.api.md @@ -26,6 +26,8 @@ export const catalogImportTranslationRef: TranslationRef< readonly 'importInfoCard.fileLinkTitle': 'Link to an existing entity file'; readonly 'importInfoCard.examplePrefix': 'Example: '; readonly 'importInfoCard.fileLinkDescription': 'The wizard analyzes the file, previews the entities, and adds them to the {{appTitle}} catalog.'; + readonly 'importInfoCard.exampleDescription': 'The wizard discovers all {{catalogFilename}} files in the repository, previews the entities, and adds them to the {{appTitle}} catalog.'; + readonly 'importInfoCard.preparePullRequestDescription': 'If no entities are found, the wizard will prepare a Pull Request that adds an example {{catalogFilename}} and prepares the {{appTitle}} catalog to load all entities as soon as the Pull Request is merged.'; readonly 'importInfoCard.githubIntegration.label': 'GitHub only'; readonly 'importInfoCard.githubIntegration.title': 'Link to a repository'; readonly 'importStepper.finish.title': 'Finish'; @@ -65,6 +67,7 @@ export const catalogImportTranslationRef: TranslationRef< readonly 'stepInitAnalyzeUrl.error.locations': 'There are no entities at this location'; readonly 'stepInitAnalyzeUrl.urlHelperText': 'Enter the full path to your entity file to start tracking your component'; readonly 'stepInitAnalyzeUrl.nextButtonText': 'Analyze'; + readonly 'stepPrepareCreatePullRequest.description': 'You entered a link to a {{integrationType}} repository but a {{catalogFilename}} could not be found. Use this form to open a Pull Request that creates one.'; readonly 'stepPrepareCreatePullRequest.nextButtonText': 'Create PR'; readonly 'stepPrepareCreatePullRequest.previewPr.title': 'Preview Pull Request'; readonly 'stepPrepareCreatePullRequest.previewPr.subheader': 'Create a new Pull Request'; diff --git a/plugins/catalog-import/src/alpha.tsx b/plugins/catalog-import/src/alpha.tsx index 3fade2d92b..17260acc14 100644 --- a/plugins/catalog-import/src/alpha.tsx +++ b/plugins/catalog-import/src/alpha.tsx @@ -87,6 +87,7 @@ const catalogImportApi = ApiBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'catalog-import', + info: { packageJson: () => import('../package.json') }, extensions: [catalogImportApi, catalogImportPage], routes: { importPage: convertLegacyRouteRef(rootRouteRef), diff --git a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx index 1777c0786b..f9103a00cf 100644 --- a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx +++ b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx @@ -92,16 +92,17 @@ export const ImportInfoCard = (props: ImportInfoCardProps) => { {exampleRepositoryUrl} - The wizard discovers all {catalogFilename} files in the - repository, previews the entities, and adds them to the {appTitle}{' '} - catalog. + {t('importInfoCard.exampleDescription', { + catalogFilename: {catalogFilename}, + appTitle, + })} {catalogImportApi.preparePullRequest && ( - If no entities are found, the wizard will prepare a Pull Request - that adds an example {catalogFilename} and prepares - the {appTitle} catalog to load all entities as soon as the Pull - Request is merged. + {t('importInfoCard.preparePullRequestDescription', { + catalogFilename: {catalogFilename}, + appTitle, + })} )} diff --git a/plugins/catalog-import/src/components/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/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 c70deebfc0..b42bb83c6f 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-catalog-node +## 1.17.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## 1.17.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + +## 1.17.0 + +### Minor Changes + +- 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 diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 70ec27bfc1..bfe1bb218f 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-node", - "version": "1.17.0-next.2", + "version": "1.17.1-next.1", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", "backstage": { "role": "node-library", diff --git a/plugins/catalog-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/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-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index b598d13f5f..b33fba13b4 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,152 @@ # @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 + +## 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 diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 4ab0eb0b37..b676aaf559 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.3", + "version": "1.19.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..36a93a8a5e 100644 --- a/plugins/catalog-react/report-alpha.api.md +++ b/plugins/catalog-react/report-alpha.api.md @@ -9,6 +9,7 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { IconLinkVerticalProps } from '@backstage/core-components'; import { JsonValue } from '@backstage/types'; import { JSX as JSX_2 } from 'react'; import { ReactNode } from 'react'; @@ -332,17 +333,36 @@ export const EntityContextMenuItemBlueprint: ExtensionBlueprint<{ kind: 'entity-context-menu-item'; name: undefined; params: EntityContextMenuItemParams; - output: ConfigurableExtensionDataRef; + output: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (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) @@ -371,6 +391,64 @@ export const EntityHeaderBlueprint: ExtensionBlueprint<{ }; }>; +// @alpha (undocumented) +export const EntityIconLinkBlueprint: ExtensionBlueprint<{ + kind: 'entity-icon-link'; + name: undefined; + params: { + useProps: () => Omit; + filter?: EntityPredicate | ((entity: Entity) => boolean); + }; + output: + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + () => IconLinkVerticalProps, + 'entity-icon-link-props', + {} + >; + inputs: {}; + config: { + label: string | undefined; + title: string | undefined; + filter: EntityPredicate | undefined; + }; + configInput: { + filter?: EntityPredicate | undefined; + label?: string | undefined; + title?: string | undefined; + }; + dataRefs: { + useProps: ConfigurableExtensionDataRef< + () => IconLinkVerticalProps, + 'entity-icon-link-props', + {} + >; + filterFunction: ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + {} + >; + filterExpression: ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + {} + >; + }; +}>; + // @alpha (undocumented) export type EntityPredicate = | EntityPredicateExpression diff --git a/plugins/catalog-react/src/alpha/blueprints/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..470aa13646 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.tsx @@ -24,7 +24,13 @@ 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, + entityPredicateToFilterFunction, +} from '../predicates'; +import type { Entity } from '@backstage/catalog-model'; +import { entityFilterFunctionDataRef } from './extensionData'; +import { createEntityPredicateSchema } from '../predicates/createEntityPredicateSchema'; /** @alpha */ export type UseProps = () => | { @@ -42,14 +48,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 +97,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/EntityIconLinkBlueprint.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityIconLinkBlueprint.tsx new file mode 100644 index 0000000000..11c9a2b805 --- /dev/null +++ b/plugins/catalog-react/src/alpha/blueprints/EntityIconLinkBlueprint.tsx @@ -0,0 +1,83 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { IconLinkVerticalProps } from '@backstage/core-components'; +import { + createExtensionBlueprint, + createExtensionDataRef, +} from '@backstage/frontend-plugin-api'; + +import { EntityPredicate } from '../predicates'; +import { createEntityPredicateSchema } from '../predicates/createEntityPredicateSchema'; + +import { + entityFilterExpressionDataRef, + entityFilterFunctionDataRef, +} from './extensionData'; +import { Entity } from '@backstage/catalog-model'; +import { resolveEntityFilterData } from './resolveEntityFilterData'; + +const entityIconLinkPropsDataRef = createExtensionDataRef< + () => IconLinkVerticalProps +>().with({ + id: 'entity-icon-link-props', +}); + +/** @alpha */ +export const EntityIconLinkBlueprint = createExtensionBlueprint({ + kind: 'entity-icon-link', + attachTo: { id: 'entity-card:catalog/about', input: 'iconLinks' }, + output: [ + entityFilterFunctionDataRef.optional(), + entityFilterExpressionDataRef.optional(), + entityIconLinkPropsDataRef, + ], + dataRefs: { + useProps: entityIconLinkPropsDataRef, + filterFunction: entityFilterFunctionDataRef, + filterExpression: entityFilterExpressionDataRef, + }, + config: { + schema: { + label: z => z.string().optional(), + title: z => z.string().optional(), + filter: z => createEntityPredicateSchema(z).optional(), + }, + }, + *factory( + params: { + useProps: () => Omit; + filter?: EntityPredicate | ((entity: Entity) => boolean); + }, + { config, node }, + ) { + const { filter, useProps } = params; + yield* resolveEntityFilterData(filter, config, node); + // Only include properties that are defined in the config + // to avoid overriding defaults with undefined values + const configProps = Object.entries(config).reduce( + (rest, [key, value]) => + value !== undefined + ? { + ...rest, + [key]: value, + } + : rest, + {}, + ); + yield entityIconLinkPropsDataRef(() => ({ ...useProps(), ...configProps })); + }, +}); diff --git a/plugins/catalog-react/src/alpha/blueprints/index.ts b/plugins/catalog-react/src/alpha/blueprints/index.ts index b21522331d..640ebb881c 100644 --- a/plugins/catalog-react/src/alpha/blueprints/index.ts +++ b/plugins/catalog-react/src/alpha/blueprints/index.ts @@ -28,3 +28,4 @@ export { type EntityContextMenuItemParams, type UseProps, } from './EntityContextMenuItemBlueprint'; +export { EntityIconLinkBlueprint } from './EntityIconLinkBlueprint'; diff --git a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md index 63bf77a4af..0bed34b2e0 100644 --- a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-catalog-unprocessed-entities-common +## 0.0.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..5bbe80a78a 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.8", "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 6f974e361b..9266e5eb93 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,51 @@ # @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 + +## 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 diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index 8b810df81a..040cd25b1e 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.3", + "version": "0.2.18-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-unprocessed-entities", diff --git a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx index 8ff83605c7..34a40b23d7 100644 --- a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx +++ b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx @@ -74,6 +74,7 @@ export const catalogUnprocessedEntitiesNavItem = NavItemBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'catalog-unprocessed-entities', + info: { packageJson: () => import('../../package.json') }, routes: { root: convertLegacyRouteRef(rootRouteRef), }, diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 57e4467030..574151b68f 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,108 @@ # @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 + +## 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 diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 816f3fffd2..65151dc0f2 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.30.0-next.3", + "version": "1.31.0-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..4be914a69d 100644 --- a/plugins/catalog/report-alpha.api.md +++ b/plugins/catalog/report-alpha.api.md @@ -18,6 +18,7 @@ import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; +import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; @@ -336,8 +337,6 @@ const _default: FrontendPlugin< }; }>; 'entity-card:catalog/about': ExtensionDefinition<{ - kind: 'entity-card'; - name: 'about'; config: { filter: EntityPredicate | undefined; type: 'content' | 'summary' | 'info' | undefined; @@ -369,7 +368,35 @@ const _default: FrontendPlugin< optional: true; } >; - inputs: {}; + inputs: { + iconLinks: ExtensionInput< + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + () => IconLinkVerticalProps, + 'entity-icon-link-props', + {} + >, + { + singleton: false; + optional: false; + } + >; + }; + kind: 'entity-card'; + name: 'about'; params: { loader: () => Promise; filter?: string | EntityPredicate | ((entity: Entity) => boolean); @@ -863,42 +890,105 @@ 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: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (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: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (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: + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (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: + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + () => IconLinkVerticalProps, + 'entity-icon-link-props', + {} + >; + inputs: {}; + params: { + useProps: () => Omit; + filter?: EntityPredicate | ((entity: Entity) => boolean); + }; + }>; 'nav-item:catalog': ExtensionDefinition<{ kind: 'nav-item'; name: undefined; @@ -1045,7 +1135,14 @@ const _default: FrontendPlugin< } >; contextMenuItems: ExtensionInput< - ConfigurableExtensionDataRef, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + >, { singleton: false; optional: false; diff --git a/plugins/catalog/report.api.md b/plugins/catalog/report.api.md index 10e120d208..52b13e1c43 100644 --- a/plugins/catalog/report.api.md +++ b/plugins/catalog/report.api.md @@ -41,10 +41,9 @@ import { TabProps } from '@material-ui/core/Tab'; import { UserListFilterKind } from '@backstage/plugin-catalog-react'; // @public -export interface AboutCardProps { - // (undocumented) +export type AboutCardProps = { variant?: InfoCardVariants; -} +}; // @public (undocumented) export function AboutContent(props: AboutContentProps): JSX_2.Element; diff --git a/plugins/catalog/src/alpha/entityCards.tsx b/plugins/catalog/src/alpha/entityCards.tsx index 8662518d30..3913f8b2b7 100644 --- a/plugins/catalog/src/alpha/entityCards.tsx +++ b/plugins/catalog/src/alpha/entityCards.tsx @@ -14,17 +14,57 @@ * limitations under the License. */ -import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; +import { + EntityIconLinkBlueprint, + EntityCardBlueprint, +} from '@backstage/plugin-catalog-react/alpha'; import { compatWrapper } from '@backstage/core-compat-api'; +import { createExtensionInput } from '@backstage/frontend-plugin-api'; +import { + HeaderIconLinkRow, + IconLinkVerticalProps, +} from '@backstage/core-components'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { buildFilterFn } from './filter/FilterWrapper'; -export const catalogAboutEntityCard = EntityCardBlueprint.make({ +export const catalogAboutEntityCard = EntityCardBlueprint.makeWithOverrides({ name: 'about', - params: { - type: 'info', - loader: async () => - import('../components/AboutCard').then(m => - compatWrapper(), - ), + inputs: { + iconLinks: createExtensionInput([ + EntityIconLinkBlueprint.dataRefs.filterFunction.optional(), + EntityIconLinkBlueprint.dataRefs.filterExpression.optional(), + EntityIconLinkBlueprint.dataRefs.useProps, + ]), + }, + factory(originalFactory, { inputs }) { + function Subheader() { + const { entity } = useEntity(); + // The "useProps" functions may be calling other hooks, so we need to + // call them in a component function to avoid breaking the rules of hooks. + const links = inputs.iconLinks.reduce((rest, iconLink) => { + const props = iconLink.get(EntityIconLinkBlueprint.dataRefs.useProps)(); + const filter = buildFilterFn( + iconLink.get(EntityIconLinkBlueprint.dataRefs.filterFunction), + iconLink.get(EntityIconLinkBlueprint.dataRefs.filterExpression), + ); + if (filter(entity)) { + return [...rest, props]; + } + return rest; + }, new Array()); + return links.length ? : null; + } + return originalFactory({ + type: 'info', + async loader() { + const { InternalAboutCard } = await import( + '../components/AboutCard/AboutCard' + ); + return compatWrapper( + } />, + ); + }, + }); }, }); diff --git a/plugins/catalog/src/alpha/entityIconLinks.tsx b/plugins/catalog/src/alpha/entityIconLinks.tsx new file mode 100644 index 0000000000..b13b13477c --- /dev/null +++ b/plugins/catalog/src/alpha/entityIconLinks.tsx @@ -0,0 +1,27 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { EntityIconLinkBlueprint } from '@backstage/plugin-catalog-react/alpha'; +import { useCatalogSourceIconLinkProps } from '../components/AboutCard/AboutCard'; + +const catalogViewSourceEntityIconLink = EntityIconLinkBlueprint.make({ + name: 'view-source', + params: { + useProps: useCatalogSourceIconLinkProps, + }, +}); + +export default [catalogViewSourceEntityIconLink]; diff --git a/plugins/catalog/src/alpha/pages.test.tsx b/plugins/catalog/src/alpha/pages.test.tsx index f5aeb7d196..47b3efbd0a 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 = { @@ -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..c1f5b55341 100644 --- a/plugins/catalog/src/alpha/pages.tsx +++ b/plugins/catalog/src/alpha/pages.tsx @@ -31,11 +31,11 @@ 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: { @@ -72,7 +72,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: { @@ -89,9 +92,12 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({ 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, @@ -100,7 +106,7 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({ const header = inputs.header?.get( EntityHeaderBlueprint.dataRefs.element, - ) ?? ; + ); let groups = Object.entries(defaultEntityContentGroups).reduce( (rest, group) => { @@ -137,9 +143,18 @@ 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) + : []; + 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/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index ff5cd9d89f..fe995a9046 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,25 @@ # @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 + +## 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..b78f7d40db 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.69-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 f4365f0fbc..8d2965b668 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,63 @@ # @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 + +## 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 diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index 61e23e8cfe..bb2dcc591d 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.3", + "version": "0.5.6-next.2", "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..11007945ce 100644 --- a/plugins/devtools-common/CHANGELOG.md +++ b/plugins/devtools-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-devtools-common +## 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..52d7bb27d0 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.16", "description": "Common functionalities for the devtools plugin", "backstage": { "role": "common-library", diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index 987bdf393d..4f8f7b6eed 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,54 @@ # @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 + +## 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 diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index c279bd5d03..d1170d8d8c 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.27-next.3", + "version": "0.1.28-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "devtools", diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx index 959670f3a5..52caaf4786 100644 --- a/plugins/devtools/src/alpha/plugin.tsx +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -71,6 +71,7 @@ export const devToolsNavItem = NavItemBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'devtools', + info: { packageJson: () => import('../../package.json') }, routes: { root: convertLegacyRouteRef(rootRouteRef), }, diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index 090e9be560..6372fb6b46 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.4.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 diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index 17d7017de1..7cd59759cc 100644 --- a/plugins/events-backend-module-aws-sqs/package.json +++ b/plugins/events-backend-module-aws-sqs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-aws-sqs", - "version": "0.4.11-next.2", + "version": "0.4.12-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md index a715c9d945..791a72a413 100644 --- a/plugins/events-backend-module-azure/CHANGELOG.md +++ b/plugins/events-backend-module-azure/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-events-backend-module-azure +## 0.2.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 diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index 5aaba0499b..3ccc5436db 100644 --- a/plugins/events-backend-module-azure/package.json +++ b/plugins/events-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-azure", - "version": "0.2.20-next.2", + "version": "0.2.21-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md index ea36c98810..c56b358dfa 100644 --- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-events-backend-module-bitbucket-cloud +## 0.2.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 diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index 5c340dad46..68d6823859 100644 --- a/plugins/events-backend-module-bitbucket-cloud/package.json +++ b/plugins/events-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-bitbucket-cloud", - "version": "0.2.20-next.2", + "version": "0.2.21-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-bitbucket-server/CHANGELOG.md b/plugins/events-backend-module-bitbucket-server/CHANGELOG.md index 6bdc04dd96..7e285f28f8 100644 --- a/plugins/events-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-events-backend-module-bitbucket-server +## 0.1.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 diff --git a/plugins/events-backend-module-bitbucket-server/package.json b/plugins/events-backend-module-bitbucket-server/package.json index e2c0c62c58..501088fd74 100644 --- a/plugins/events-backend-module-bitbucket-server/package.json +++ b/plugins/events-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-bitbucket-server", - "version": "0.1.1-next.2", + "version": "0.1.2-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md index 1e04e58cdb..b0e7a64255 100644 --- a/plugins/events-backend-module-gerrit/CHANGELOG.md +++ b/plugins/events-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-events-backend-module-gerrit +## 0.2.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 diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index bed07d1546..33dfe31b19 100644 --- a/plugins/events-backend-module-gerrit/package.json +++ b/plugins/events-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-gerrit", - "version": "0.2.20-next.2", + "version": "0.2.21-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md index 94d7d10009..1521413713 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-events-backend-module-github +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## 0.4.0 + +### Minor Changes + +- 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 diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index d11562a47d..ebfefa8965 100644 --- a/plugins/events-backend-module-github/package.json +++ b/plugins/events-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-github", - "version": "0.4.0-next.3", + "version": "0.4.1-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-gitlab/CHANGELOG.md b/plugins/events-backend-module-gitlab/CHANGELOG.md index 6f0b139cb3..7941142c71 100644 --- a/plugins/events-backend-module-gitlab/CHANGELOG.md +++ b/plugins/events-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-events-backend-module-gitlab +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## 0.3.1 + +### Patch Changes + +- 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 diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index 28fd7ace5b..23b90b6832 100644 --- a/plugins/events-backend-module-gitlab/package.json +++ b/plugins/events-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-gitlab", - "version": "0.3.1-next.2", + "version": "0.3.2-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-google-pubsub/CHANGELOG.md b/plugins/events-backend-module-google-pubsub/CHANGELOG.md index 5af310dd6e..aee8ff1327 100644 --- a/plugins/events-backend-module-google-pubsub/CHANGELOG.md +++ b/plugins/events-backend-module-google-pubsub/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-events-backend-module-google-pubsub +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## 0.1.0 + +### Minor Changes + +- 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 diff --git a/plugins/events-backend-module-google-pubsub/package.json b/plugins/events-backend-module-google-pubsub/package.json index 946263032a..591067de48 100644 --- a/plugins/events-backend-module-google-pubsub/package.json +++ b/plugins/events-backend-module-google-pubsub/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-google-pubsub", - "version": "0.1.0-next.2", + "version": "0.1.1-next.1", "description": "The google-pubsub backend module for the events plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/events-backend-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..35da6f8529 --- /dev/null +++ b/plugins/events-backend-module-kafka/CHANGELOG.md @@ -0,0 +1,17 @@ +# @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 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/package.json b/plugins/events-backend-module-kafka/package.json new file mode 100644 index 0000000000..ca001a4503 --- /dev/null +++ b/plugins/events-backend-module-kafka/package.json @@ -0,0 +1,51 @@ +{ + "name": "@backstage/plugin-events-backend-module-kafka", + "version": "0.1.0-next.0", + "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", + "luxon": "^3.0.0" + }, + "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..735b0fc224 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.ts @@ -0,0 +1,79 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 + * + * @public + */ +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..223e4e99ac --- /dev/null +++ b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.ts @@ -0,0 +1,109 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerService } from '@backstage/backend-plugin-api'; +import { 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. + * + * @public + */ +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..b9e9379104 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/publisher/LoggerServiceAdapter.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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) => (_level: logLevel) => { + return (entry: LogEntry) => { + const { namespace, level, log } = entry; + const { message, ...extra } = log; + + const logMethods: Record< + logLevel, + (message: string, meta?: object) => void + > = { + [logLevel.ERROR]: loggerService.error, + [logLevel.WARN]: loggerService.warn, + [logLevel.INFO]: loggerService.info, + [logLevel.DEBUG]: loggerService.debug, + [logLevel.NOTHING]: () => {}, + }; + + // 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..aa5921a7f3 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/publisher/config.ts @@ -0,0 +1,159 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 { ConsumerConfig, ConsumerSubscribeTopics, KafkaConfig } from 'kafkajs'; +import { Duration } from 'luxon'; + +/** + * @public + */ +export interface KafkaConsumerConfig { + backstageTopic: string; + consumerConfig: ConsumerConfig; + consumerSubscribeTopics: ConsumerSubscribeTopics; +} + +/** + * @public + */ +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 Duration.fromObject(humanDuration).as('milliseconds'); +}; + +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 bce3d51721..0acf569d83 100644 --- a/plugins/events-backend-test-utils/CHANGELOG.md +++ b/plugins/events-backend-test-utils/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-events-backend-test-utils +## 0.1.45-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.1.45-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.12-next.0 + +## 0.1.44 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.11 + ## 0.1.44-next.2 ### Patch Changes diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json index 9c728b6875..1662f4f943 100644 --- a/plugins/events-backend-test-utils/package.json +++ b/plugins/events-backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-test-utils", - "version": "0.1.44-next.2", + "version": "0.1.45-next.1", "description": "The plugin-events-backend-test-utils for @backstage/plugin-events-node", "backstage": { "role": "node-library", diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md index 62a72ec740..90f5f6009f 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-events-backend +## 0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.5.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + - @backstage/backend-openapi-utils@0.5.4-next.0 + +## 0.5.2 + +### Patch Changes + +- 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 diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index 472e0cf49f..0f57e0da5a 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend", - "version": "0.5.2-next.2", + "version": "0.5.3-next.1", "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 5864a91deb..06960faf33 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-events-node +## 0.4.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## 0.4.11 + +### Patch Changes + +- 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 diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index 1b117621f0..c60bb7ded1 100644 --- a/plugins/events-node/package.json +++ b/plugins/events-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-node", - "version": "0.4.11-next.2", + "version": "0.4.12-next.1", "description": "The plugin-events-node module for @backstage/plugin-events-backend", "backstage": { "role": "node-library", diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index d1f59ca675..eb0f46dc95 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,28 @@ # @internal/plugin-todo-list-backend +## 1.0.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 diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index d522874de1..1e8152f632 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-backend", - "version": "1.0.39-next.2", + "version": "1.0.40-next.1", "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..09a053b1ac 100644 --- a/plugins/example-todo-list-common/CHANGELOG.md +++ b/plugins/example-todo-list-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @internal/plugin-todo-list-common +## 1.0.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..6c1b783755 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.25", "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..1e80df6334 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,21 @@ # @internal/plugin-todo-list +## 1.0.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..e3568ae63b 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.40-next.0", "backstage": { "role": "frontend-plugin", "pluginId": "todo-list", diff --git a/plugins/gateway-backend/CHANGELOG.md b/plugins/gateway-backend/CHANGELOG.md index 47723dfc69..5521af45d0 100644 --- a/plugins/gateway-backend/CHANGELOG.md +++ b/plugins/gateway-backend/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-gateway-backend +## 1.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 1.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## 1.0.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + ## 1.0.1-next.2 ### Patch Changes diff --git a/plugins/gateway-backend/package.json b/plugins/gateway-backend/package.json index bb49127c62..c7efdbfaec 100644 --- a/plugins/gateway-backend/package.json +++ b/plugins/gateway-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gateway-backend", - "version": "1.0.1-next.2", + "version": "1.0.2-next.1", "backstage": { "role": "backend-plugin", "pluginId": "gateway", diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index 1ebfec3ff3..a59e7cdd4c 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,27 @@ # @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 + +## 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 diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 4cba4959f9..25845ae0d7 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.2", + "version": "0.1.27-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/scaffolder-backend/src/lib/index.ts b/plugins/home-react/src/alpha.ts similarity index 85% rename from plugins/scaffolder-backend/src/lib/index.ts rename to plugins/home-react/src/alpha.ts index 20096f9c03..3a08699f66 100644 --- a/plugins/scaffolder-backend/src/lib/index.ts +++ b/plugins/home-react/src/alpha.ts @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -export * from './templating'; +export { homeReactTranslationRef } from './translation'; 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/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/overridableComponents.ts b/plugins/home-react/src/overridableComponents.ts index 8a88fd5c94..3653257507 100644 --- a/plugins/home-react/src/overridableComponents.ts +++ b/plugins/home-react/src/overridableComponents.ts @@ -16,7 +16,7 @@ import { Overrides } from '@material-ui/core/styles/overrides'; import { StyleRules } from '@material-ui/core/styles/withStyles'; -import { PluginHomeContentModalClassKey } from './'; +import { PluginHomeContentModalClassKey } from './components/ContentModal'; /** @public */ export type 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 1a1eba2dae..6cc000226b 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,80 @@ # @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 + +## 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 diff --git a/plugins/home/package.json b/plugins/home/package.json index 30cd16529a..5cdcd76b51 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.8.8-next.3", + "version": "0.8.9-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 5e43163e31..f36589ea51 100644 --- a/plugins/home/report-alpha.api.md +++ b/plugins/home/report-alpha.api.md @@ -10,6 +10,7 @@ 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< @@ -69,6 +70,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 61ce9a77f8..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'; @@ -336,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 04d612ef1c..fc34c6e806 100644 --- a/plugins/home/src/alpha.tsx +++ b/plugins/home/src/alpha.tsx @@ -68,8 +68,11 @@ const homePage = PageBlueprint.makeWithOverrides({ */ export default createFrontendPlugin({ pluginId: 'home', + info: { packageJson: () => import('../package.json') }, extensions: [homePage], 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 675e17357e..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() @@ -340,7 +343,7 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { {!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/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/VisitListEmpty.tsx b/plugins/home/src/components/VisitList/VisitListEmpty.tsx index e8b6d369d2..144c9a2066 100644 --- a/plugins/home/src/components/VisitList/VisitListEmpty.tsx +++ b/plugins/home/src/components/VisitList/VisitListEmpty.tsx @@ -14,16 +14,20 @@ * limitations under the License. */ +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Typography from '@material-ui/core/Typography'; +import { homeTranslationRef } from '../../translation'; -export const VisitListEmpty = () => ( - <> - - 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 b923970888..5317312626 100644 --- a/plugins/home/src/homePageComponents/QuickStart/Content.tsx +++ b/plugins/home/src/homePageComponents/QuickStart/Content.tsx @@ -20,6 +20,8 @@ import Typography from '@material-ui/core/Typography'; import Grid from '@material-ui/core/Grid'; 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. @@ -54,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')} 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 c162bcb8cc..eab166f2b5 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-kubernetes-backend +## 0.19.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-kubernetes-node@0.3.1-next.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## 0.19.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-kubernetes-node@0.3.1-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + +## 0.19.6 + +### Patch Changes + +- 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 diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 9f7dc6fd31..bbd01f2c45 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-backend", - "version": "0.19.6-next.3", + "version": "0.19.7-next.1", "description": "A Backstage backend plugin that integrates towards Kubernetes", "backstage": { "role": "backend-plugin", 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/KubernetesBuilder.test.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts index 3046232bad..a3353eec43 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts @@ -35,7 +35,6 @@ import { } 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/KubernetesProxy.test.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts index 3ed8b25c56..3ef7f95b7d 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, @@ -78,7 +77,7 @@ describe('KubernetesProxy', () => { >(), }; - const permissionApi = mockServices.permissions.mock(); + const permissionApi = mockServices.permissions(); const mockDisocveryApi = mockServices.discovery.mock(); registerMswTestHooks(worker); @@ -156,9 +155,6 @@ describe('KubernetesProxy', () => { authStrategy, discovery: mockDisocveryApi, }); - permissionApi.authorize.mockResolvedValue([ - { result: AuthorizeResult.ALLOW }, - ]); }); it('should return a ERROR_NOT_FOUND if no clusters are found', async () => { diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index 4343e9f5a2..e2c96239e0 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,52 @@ # @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 + +## 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 diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index 02e6253c2e..e8c9eb4d07 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.3", + "version": "0.0.26-next.2", "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..d9c4ff3c5b 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 + ## 0.9.5-next.0 ### Patch Changes diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index 4956cdff76..1a5266a280 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.5", "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 6cbd464a9f..887629d69a 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-kubernetes-node +## 0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## 0.3.0 + +### Minor Changes + +- 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 diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index d4842f9030..598c3e4a7f 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-node", - "version": "0.3.0-next.2", + "version": "0.3.1-next.1", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library", diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index 51e96bbea9..25bf670916 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,32 @@ # @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 + +## 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..e91ed5d0a5 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.8-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 685e751982..bc66eafc91 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,63 @@ # @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 + +## 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 diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 6dc09d3c6a..339e72df45 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.12.7-next.3", + "version": "0.12.8-next.2", "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/src/alpha/plugin.tsx b/plugins/kubernetes/src/alpha/plugin.tsx index 35cb9e037d..db7b2439bf 100644 --- a/plugins/kubernetes/src/alpha/plugin.tsx +++ b/plugins/kubernetes/src/alpha/plugin.tsx @@ -28,6 +28,7 @@ import { export default createFrontendPlugin({ pluginId: 'kubernetes', + info: { packageJson: () => import('../../package.json') }, extensions: [ kubernetesPage, entityKubernetesContent, diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index 8b39ef8055..bb6708e588 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,61 @@ # @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 + +## 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 diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 94e953d480..cbafdca953 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.3", + "version": "0.3.10-next.2", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/notifications-backend-module-email/src/module.ts b/plugins/notifications-backend-module-email/src/module.ts index c83d27b6a3..0ee7e811b4 100644 --- a/plugins/notifications-backend-module-email/src/module.ts +++ b/plugins/notifications-backend-module-email/src/module.ts @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; -import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; import { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node'; import { NotificationsEmailProcessor } from './processor'; import { diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index d165973424..3496d431e3 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -25,7 +25,7 @@ import { } from '@backstage/backend-plugin-api'; import { Config, readDurationFromConfig } from '@backstage/config'; import { durationToMilliseconds } from '@backstage/types'; -import { CATALOG_FILTER_EXISTS, CatalogApi } from '@backstage/catalog-client'; +import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client'; import { getProcessorFiltersFromConfig, Notification, @@ -39,6 +39,7 @@ import { createStreamTransport, } from './transports'; import { UserEntity } from '@backstage/catalog-model'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { compact } from 'lodash'; import { DefaultAwsCredentialsManager } from '@backstage/integration-aws-node'; import { NotificationTemplateRenderer } from '../extensions'; @@ -62,7 +63,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { constructor( private readonly logger: LoggerService, private readonly config: Config, - private readonly catalog: CatalogApi, + private readonly catalog: CatalogService, private readonly auth: AuthService, private readonly cache?: CacheService, private readonly templateRenderer?: NotificationTemplateRenderer, @@ -152,10 +153,6 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return cached; } - const { token } = await this.auth.getPluginRequestToken({ - onBehalfOf: await this.auth.getOwnServiceCredentials(), - targetPluginId: 'catalog', - }); const entities = await this.catalog.getEntities( { filter: [ @@ -163,7 +160,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { ], fields: ['spec.profile.email'], }, - { token }, + { credentials: await this.auth.getOwnServiceCredentials() }, ); const ret = compact([ ...new Set( @@ -188,11 +185,9 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return cached; } - const { token } = await this.auth.getPluginRequestToken({ - onBehalfOf: await this.auth.getOwnServiceCredentials(), - targetPluginId: 'catalog', + const entity = await this.catalog.getEntityByRef(entityRef, { + credentials: await this.auth.getOwnServiceCredentials(), }); - const entity = await this.catalog.getEntityByRef(entityRef, { token }); const ret: string[] = []; if (entity) { const userEntity = entity as UserEntity; diff --git a/plugins/notifications-backend-module-slack/CHANGELOG.md b/plugins/notifications-backend-module-slack/CHANGELOG.md index 6b8aede3b6..2effe53d63 100644 --- a/plugins/notifications-backend-module-slack/CHANGELOG.md +++ b/plugins/notifications-backend-module-slack/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-notifications-backend-module-slack +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-notifications-node@0.2.16-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.0 + +## 0.1.1 + +### Patch Changes + +- 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 diff --git a/plugins/notifications-backend-module-slack/package.json b/plugins/notifications-backend-module-slack/package.json index 33bfcc55bc..bb5ed460df 100644 --- a/plugins/notifications-backend-module-slack/package.json +++ b/plugins/notifications-backend-module-slack/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-slack", - "version": "0.1.1-next.3", + "version": "0.1.2-next.1", "description": "The slack backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index 03f8828ea3..2aeda7dd17 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,72 @@ # @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 + +## 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 diff --git a/plugins/notifications-backend/config.d.ts b/plugins/notifications-backend/config.d.ts index 5dd795f8d3..61ae411df8 100644 --- a/plugins/notifications-backend/config.d.ts +++ b/plugins/notifications-backend/config.d.ts @@ -28,5 +28,10 @@ export interface Config { * Throttle duration between notification sending, defaults to 50ms */ throttleInterval?: HumanDuration | string; + /** + * 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..c8f78a5640 --- /dev/null +++ b/plugins/notifications-backend/migrations/20250317_addTopic.js @@ -0,0 +1,56 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const crypto = require('crypto'); + +exports.up = async function up(knex) { + await knex.schema.alterTable('user_settings', table => { + table.string('topic').nullable().after('origin'); + table.string('settings_key_hash', 64).notNullable(); + table.dropUnique([], 'user_settings_unique_idx'); + }); + + await knex.schema.alterTable('user_settings', table => { + table.unique(['settings_key_hash'], 'user_settings_unique_idx'); + }); + + const rows = await knex('user_settings').select('user', 'channel', 'origin'); + for (const row of rows) { + const rawKey = `${row.user}|${row.channel}|${row.origin}|}`; + const hash = crypto.createHash('sha256').update(rawKey).digest('hex'); + await knex('user_settings') + .where({ + user: row.user, + channel: row.channel, + origin: row.origin, + topic: row.topic, + }) + .update({ settings_key_hash: hash }); + } +}; + +exports.down = async function down(knex) { + await knex.schema.table('user_settings', table => { + table.dropUnique([], 'user_settings_unique_idx'); + table.dropColumn('settings_key_hash'); + table.dropColumn('topic'); + }); + + await knex.schema.alterTable('user_settings', table => { + table.unique(['user', 'channel', 'origin'], { + indexName: 'user_settings_unique_idx', + }); + }); +}; diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 1f1c328abe..804d084803 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.2", + "version": "0.5.7-next.2", "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..1b3f2e8fd6 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, @@ -83,6 +85,9 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { beforeAll(async () => { database = await createDatabase(databaseId); + store = await DatabaseNotificationsStore.create({ + database, + }); }); describe('POST /notifications', () => { @@ -93,7 +98,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 +312,10 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { expect(notifications).toHaveLength(1); }); - it('should not send to user entity if disabled in settings', async () => { + it('should not send to user entity if origin is disabled in settings', async () => { const client = await database.getClient(); await client('user_settings').insert({ + settings_key_hash: 'hash', user: 'user:default/mock', channel: 'Web', origin: 'external:test-service', @@ -335,6 +341,85 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { expect(notifications).toHaveLength(0); }); + it('should not send to user entity if topic is disabled in settings', async () => { + const client = await database.getClient(); + await client('user_settings').insert({ + settings_key_hash: 'hash', + user: 'user:default/mock', + channel: 'Web', + origin: 'external:test-service', + topic: 'test-topic', + enabled: false, + }); + + const response = await sendNotification({ + recipients: { + type: 'entity', + entityRef: ['user:default/mock'], + }, + payload: { + title: 'test notification', + topic: 'test-topic', + }, + }); + + expect(response.status).toEqual(200); + expect(response.body).toEqual([]); + + const notifications = await client('notification') + .where('user', 'user:default/mock') + .select(); + expect(notifications).toHaveLength(0); + }); + + it('should send to user entity if origin is enabled, but topic is disabled in settings', async () => { + const client = await database.getClient(); + await client('user_settings').insert({ + settings_key_hash: 'hash', + user: 'user:default/mock', + channel: 'Web', + origin: 'external:test-service', + enabled: true, + }); + await client('user_settings').insert({ + settings_key_hash: 'hash1', + user: 'user:default/mock', + channel: 'Web', + origin: 'external:test-service', + topic: 'test-topic', + enabled: false, + }); + + const response = await sendNotification({ + recipients: { + type: 'entity', + entityRef: ['user:default/mock'], + }, + payload: { + title: 'test notification', + }, + }); + + expect(response.status).toEqual(200); + expect(response.body).toEqual([ + { + created: expect.any(String), + id: expect.any(String), + origin: 'external:test-service', + payload: { + severity: 'normal', + title: 'test notification', + }, + user: 'user:default/mock', + }, + ]); + + const notifications = await client('notification') + .where('user', 'user:default/mock') + .select(); + expect(notifications).toHaveLength(1); + }); + it('should fail without recipients', async () => { const response = await sendNotification({ payload: { @@ -380,7 +465,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 +555,7 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { beforeAll(async () => { const router = await createRouter({ logger: mockServices.logger.mock(), - database, + store, signals: signalService, userInfo, config, @@ -490,6 +575,7 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { it('should return user settings', async () => { const client = await database.getClient(); await client('user_settings').insert({ + settings_key_hash: 'hash', user: 'user:default/mock', channel: 'Web', origin: 'external:test-service', @@ -502,7 +588,9 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { channels: [ { id: 'Web', - origins: [{ enabled: false, id: 'external:test-service' }], + origins: [ + { enabled: false, id: 'external:test-service', topics: [] }, + ], }, ], }); @@ -517,7 +605,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..9563d7965b 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; @@ -104,40 +104,86 @@ export async function createRouter( return info.userEntityRef; }; + const getTopicSettings = ( + topic: any, + existingOrigin: OriginSetting | undefined, + defaultEnabled: boolean, + ) => { + const existingTopic = existingOrigin?.topics?.find( + t => t.id === topic.topic, + ); + return { + id: topic.topic, + enabled: existingTopic ? existingTopic.enabled : defaultEnabled, + }; + }; + + const getOriginSettings = ( + originId: string, + existingChannel: ChannelSetting | undefined, + topics: { origin: string; topic: string }[], + ) => { + const existingOrigin = existingChannel?.origins.find( + o => o.id === originId, + ); + const defaultEnabled = existingOrigin ? existingOrigin.enabled : true; + return { + id: originId, + enabled: defaultEnabled, + topics: topics + .filter(t => t.origin === originId) + .map(t => getTopicSettings(t, existingOrigin, defaultEnabled)), + }; + }; + const getNotificationChannels = () => { return [WEB_NOTIFICATION_CHANNEL, ...processors.map(p => p.getName())]; }; + const getChannelSettings = ( + channelId: string, + settings: NotificationSettings, + origins: string[], + topics: { origin: string; topic: string }[], + ) => { + const existingChannel = settings.channels.find(c => c.id === channelId); + if (existingChannel) { + return existingChannel; + } + return { + id: channelId, + origins: origins.map(originId => + getOriginSettings(originId, existingChannel, topics), + ), + }; + }; + const getNotificationSettings = async (user: string) => { const { origins } = await store.getUserNotificationOrigins({ user }); + const { topics } = await store.getUserNotificationTopics({ user }); const settings = await store.getNotificationSettings({ user }); const channels = getNotificationChannels(); - const response: NotificationSettings = { - channels: channels.map(channel => { - const channelSettings = settings.channels.find(c => c.id === channel); - if (channelSettings) { - return channelSettings; - } - return { - id: channel, - origins: origins.map(origin => ({ - id: origin, - enabled: true, - })), - }; - }), + return { + channels: channels.map(channelId => + getChannelSettings(channelId, settings, origins, topics), + ), }; - return response; }; const isNotificationsEnabled = async (opts: { user: string; channel: string; origin: string; + topic: string | null; }) => { const settings = await getNotificationSettings(opts.user); - return isNotificationsEnabledFor(settings, opts.channel, opts.origin); + return isNotificationsEnabledFor( + settings, + opts.channel, + opts.origin, + opts.topic, + ); }; const filterProcessors = async ( @@ -154,6 +200,7 @@ export async function createRouter( user, origin, channel: processor.getName(), + topic: payload.topic ?? null, }); if (!enabled) { continue; @@ -508,6 +555,7 @@ export async function createRouter( user, channel: WEB_NOTIFICATION_CHANNEL, origin: userNotification.origin, + topic: userNotification.payload.topic ?? null, }); let ret = notification; @@ -622,7 +670,7 @@ export async function createRouter( users = await getUsersForEntityRef( entityRef, recipients.excludeEntityRef ?? [], - { auth, catalogClient: catalog }, + { auth, catalog }, ); } catch (e) { throw new InputError('Failed to resolve notification receivers', e); diff --git a/plugins/notifications-common/CHANGELOG.md b/plugins/notifications-common/CHANGELOG.md index 3fd44c9e93..0bcbc7e926 100644 --- a/plugins/notifications-common/CHANGELOG.md +++ b/plugins/notifications-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-notifications-common +## 0.0.9-next.0 + +### Patch Changes + +- 1fb5f06: Adds ability for user to turn on/off notifications for specific topics within an origin. +- Updated dependencies + - @backstage/config@1.3.2 + ## 0.0.8 ### Patch Changes diff --git a/plugins/notifications-common/package.json b/plugins/notifications-common/package.json index d4df76a817..cdca935d38 100644 --- a/plugins/notifications-common/package.json +++ b/plugins/notifications-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-common", - "version": "0.0.8", + "version": "0.0.9-next.0", "description": "Common functionalities for the notifications plugin", "backstage": { "role": "common-library", diff --git a/plugins/notifications-common/report.api.md b/plugins/notifications-common/report.api.md index 167b37349d..93889b206b 100644 --- a/plugins/notifications-common/report.api.md +++ b/plugins/notifications-common/report.api.md @@ -5,6 +5,12 @@ ```ts import { Config } from '@backstage/config'; +// @public (undocumented) +export type ChannelSetting = { + id: string; + origins: OriginSetting[]; +}; + // @public (undocumented) export const getProcessorFiltersFromConfig: ( config: Config, @@ -15,6 +21,7 @@ export const isNotificationsEnabledFor: ( settings: NotificationSettings, channelId: string, originId: string, + topicId: string | null, ) => boolean; // @public (undocumented) @@ -62,13 +69,7 @@ export type NotificationReadSignal = { // @public (undocumented) export type NotificationSettings = { - channels: { - id: string; - origins: { - id: string; - enabled: boolean; - }[]; - }[]; + channels: ChannelSetting[]; }; // @public @@ -85,4 +86,17 @@ export type NotificationStatus = { unread: number; read: number; }; + +// @public (undocumented) +export type OriginSetting = { + id: string; + enabled: boolean; + topics?: TopicSetting[]; +}; + +// @public (undocumented) +export type TopicSetting = { + id: string; + enabled: boolean; +}; ``` diff --git a/plugins/notifications-common/src/types.ts b/plugins/notifications-common/src/types.ts index 237f93f40a..48f89cbbb4 100644 --- a/plugins/notifications-common/src/types.ts +++ b/plugins/notifications-common/src/types.ts @@ -129,12 +129,31 @@ export type NotificationProcessorFilters = { /** * @public */ -export type NotificationSettings = { - channels: { - id: string; - origins: { - id: string; - enabled: boolean; - }[]; - }[]; +export type TopicSetting = { + id: string; + enabled: boolean; +}; + +/** + * @public + */ +export type OriginSetting = { + id: string; + enabled: boolean; + topics?: TopicSetting[]; +}; + +/** + * @public + */ +export type ChannelSetting = { + id: string; + origins: OriginSetting[]; +}; + +/** + * @public + */ +export type NotificationSettings = { + channels: ChannelSetting[]; }; diff --git a/plugins/notifications-common/src/utils.ts b/plugins/notifications-common/src/utils.ts index c57412bd97..b95df5bea5 100644 --- a/plugins/notifications-common/src/utils.ts +++ b/plugins/notifications-common/src/utils.ts @@ -20,6 +20,7 @@ export const isNotificationsEnabledFor = ( settings: NotificationSettings, channelId: string, originId: string, + topicId: string | null, ) => { const channel = settings.channels.find(c => c.id === channelId); if (!channel) { @@ -30,5 +31,12 @@ export const isNotificationsEnabledFor = ( if (!origin) { return true; } - return origin.enabled; + if (topicId === null) { + return origin.enabled; + } + const topic = origin.topics?.find(t => t.id === topicId); + if (!topic) { + return origin.enabled; + } + return topic.enabled; }; diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md index 2191de49ad..abfa9e282b 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-notifications-node +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-signals-node@0.1.21-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-signals-node@0.1.21-next.0 + +## 0.2.15 + +### Patch Changes + +- 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 diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index 02775f2030..ce823ffe0d 100644 --- a/plugins/notifications-node/package.json +++ b/plugins/notifications-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-node", - "version": "0.2.15-next.2", + "version": "0.2.16-next.1", "description": "Node.js library for the notifications plugin", "backstage": { "role": "node-library", diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index 9527fe373f..acc28ac786 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,62 @@ # @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 + +## 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 diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index 9b5df4bfe2..f40e9cb0ed 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.5.5-next.3", + "version": "0.5.6-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "notifications", diff --git a/plugins/notifications/report.api.md b/plugins/notifications/report.api.md index cdf6d9d319..62c41d07c7 100644 --- a/plugins/notifications/report.api.md +++ b/plugins/notifications/report.api.md @@ -207,6 +207,7 @@ export function useNotificationsApi( // @public (undocumented) export const UserNotificationSettingsCard: (props: { originNames?: Record; + topicNames?: Record; }) => JSX_2.Element; // (No @packageDocumentation comment for this package) diff --git a/plugins/notifications/src/alpha.tsx b/plugins/notifications/src/alpha.tsx index d7c4ec27b6..4424659593 100644 --- a/plugins/notifications/src/alpha.tsx +++ b/plugins/notifications/src/alpha.tsx @@ -54,6 +54,7 @@ const api = ApiBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'notifications', + info: { packageJson: () => import('../package.json') }, routes: convertLegacyRouteRefs({ root: rootRouteRef, }), diff --git a/packages/cli/src/modules/info/alpha.ts b/plugins/notifications/src/components/UserNotificationSettingsCard/NoBorderTableCell.tsx similarity index 54% rename from packages/cli/src/modules/info/alpha.ts rename to plugins/notifications/src/components/UserNotificationSettingsCard/NoBorderTableCell.tsx index 1e6f761f83..71cd52f8a8 100644 --- a/packages/cli/src/modules/info/alpha.ts +++ b/plugins/notifications/src/components/UserNotificationSettingsCard/NoBorderTableCell.tsx @@ -13,21 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import yargs from 'yargs'; -import { createCliPlugin } from '../../wiring/factory'; -export default createCliPlugin({ - pluginId: 'info', - init: async reg => { - reg.addCommand({ - path: ['info'], - description: 'Show helpful information for debugging and reporting bugs', - execute: async ({ args }) => { - yargs().parse(args); - const { default: command } = - require('./commands/info') as typeof import('./commands/info'); - await command(); - }, - }); +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 8ad4f304ba..0cf8c93eb8 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,45 @@ # @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 + +## 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 diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index 99d7f2b1b5..fbfc022449 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.3", + "version": "0.1.39-next.2", "backstage": { "role": "web-library", "pluginId": "org", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index e77c76c618..7725ad18ae 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,78 @@ # @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 + +## 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 diff --git a/plugins/org/package.json b/plugins/org/package.json index 78e0dad480..86fd562739 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.6.39-next.3", + "version": "0.6.40-next.2", "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..840a5472dd 100644 --- a/plugins/org/report-alpha.api.md +++ b/plugins/org/report-alpha.api.md @@ -3,14 +3,17 @@ > 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 { 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 { 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< @@ -143,13 +146,17 @@ 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; }; @@ -176,7 +183,17 @@ const _default: FrontendPlugin< optional: true; } >; - inputs: {}; + inputs: { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }; + kind: 'entity-card'; + name: 'user-profile'; params: { loader: () => Promise; filter?: string | EntityPredicate | ((entity: Entity) => boolean); @@ -187,5 +204,38 @@ const _default: FrontendPlugin< >; export default _default; +// @alpha (undocumented) +export const orgTranslationRef: TranslationRef< + 'org', + { + readonly 'groupProfileCard.groupNotFound': 'Group not found'; + readonly 'groupProfileCard.editIconButtonTitle': 'Edit Metadata'; + readonly 'groupProfileCard.refreshIconButtonTitle': 'Schedule entity refresh'; + readonly 'groupProfileCard.refreshIconButtonAriaLabel': 'Refresh'; + readonly 'groupProfileCard.listItemTitle.email': 'Email'; + readonly 'groupProfileCard.listItemTitle.entityRef': 'Entity Ref'; + readonly 'groupProfileCard.listItemTitle.parentGroup': 'Parent Group'; + readonly 'groupProfileCard.listItemTitle.childGroups': 'Child Groups'; + readonly 'membersListCard.title': 'Members'; + readonly 'membersListCard.subtitle': 'of {{groupName}}'; + readonly 'membersListCard.paginationLabel': ', page {{page}} of {{nbPages}}'; + readonly 'membersListCard.noMembersDescription': 'This group has no members.'; + readonly 'membersListCard.aggregateMembersToggle.ariaLabel': 'Users Type Switch'; + readonly 'membersListCard.aggregateMembersToggle.directMembers': 'Direct Members'; + readonly 'membersListCard.aggregateMembersToggle.aggregatedMembers': 'Aggregated Members'; + readonly 'ownershipCard.title': 'Ownership'; + readonly 'ownershipCard.aggregateRelationsToggle.ariaLabel': 'Ownership Type Switch'; + readonly 'ownershipCard.aggregateRelationsToggle.directRelations': 'Direct Relations'; + readonly 'ownershipCard.aggregateRelationsToggle.aggregatedRelations': 'Aggregated Relations'; + readonly 'userProfileCard.editIconButtonTitle': 'Edit Metadata'; + readonly 'userProfileCard.listItemTitle.email': 'Email'; + readonly 'userProfileCard.listItemTitle.memberOf': 'Member of'; + readonly 'userProfileCard.userNotFound': 'User not found'; + readonly 'userProfileCard.moreGroupButtonTitle': '...More ({{number}})'; + readonly 'userProfileCard.allGroupDialog.title': "All {{name}}'s groups:"; + readonly 'userProfileCard.allGroupDialog.closeButtonTitle': 'Close'; + } +>; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/org/report.api.md b/plugins/org/report.api.md index d3e82478f7..906e43a785 100644 --- a/plugins/org/report.api.md +++ b/plugins/org/report.api.md @@ -42,6 +42,8 @@ export type EntityRelationAggregation = 'direct' | 'aggregated'; export const EntityUserProfileCard: (props: { variant?: InfoCardVariants; showLinks?: boolean; + maxRelations?: number; + hideIcons?: boolean; }) => JSX_2.Element; // @public (undocumented) @@ -98,5 +100,7 @@ export const OwnershipCard: (props: { export const UserProfileCard: (props: { variant?: InfoCardVariants; showLinks?: boolean; + maxRelations?: number; + hideIcons?: boolean; }) => JSX_2.Element; ``` diff --git a/plugins/org/src/alpha.tsx b/plugins/org/src/alpha.tsx index beb4f573af..e5d6aa6675 100644 --- a/plugins/org/src/alpha.tsx +++ b/plugins/org/src/alpha.tsx @@ -59,20 +59,35 @@ 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({ + 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 +98,5 @@ export default createFrontendPlugin({ catalogIndex: catalogIndexRouteRef, }), }); + +export { orgTranslationRef } from './translation'; diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index 35eb82334c..5c8757a4dd 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -55,6 +55,8 @@ import PermIdentityIcon from '@material-ui/icons/PermIdentity'; import { LinksGroup } from '../../Meta'; import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha'; import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common/alpha'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { orgTranslationRef } from '../../../../translation'; const CardTitle = (props: { title: string }) => ( @@ -74,6 +76,7 @@ export const GroupProfileCard = (props: { const { allowed: canRefresh } = useEntityPermission( catalogEntityRefreshPermission, ); + const { t } = useTranslationRef(orgTranslationRef); const refreshEntity = useCallback(async () => { await catalogApi.refreshEntity(stringifyEntityRef(group)); @@ -85,7 +88,9 @@ export const GroupProfileCard = (props: { }, [catalogApi, alertApi, group]); if (!group) { - return Group not found; + return ( + {t('groupProfileCard.groupNotFound')} + ); } const { @@ -111,15 +116,19 @@ export const GroupProfileCard = (props: { const emailHref = profile?.email ? `mailto:${profile.email}` : '#'; const infoCardAction = entityMetadataEditUrl ? ( ) : ( - + ); @@ -133,8 +142,8 @@ export const GroupProfileCard = (props: { <> {allowRefresh && canRefresh && ( @@ -152,31 +161,33 @@ export const GroupProfileCard = (props: { - + {profile?.email && ( - + {profile.email}} - secondary="Email" + secondary={t('groupProfileCard.listItemTitle.email')} /> )} - + @@ -191,12 +202,14 @@ export const GroupProfileCard = (props: { 'N/A' ) } - secondary="Parent Group" + secondary={t('groupProfileCard.listItemTitle.parentGroup')} /> - + @@ -211,7 +224,7 @@ export const GroupProfileCard = (props: { 'N/A' ) } - secondary="Child Groups" + secondary={t('groupProfileCard.listItemTitle.childGroups')} /> {props?.showLinks && } diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx index c051de5ddb..07119ba85b 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx @@ -48,6 +48,8 @@ import { removeDuplicateEntitiesFrom, } from '../../../../helpers/helpers'; import { EntityRelationAggregation } from '../../types'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { orgTranslationRef } from '../../../../translation'; /** @public */ export type MemberComponentClassKey = 'card' | 'avatar'; @@ -157,8 +159,9 @@ export const MembersListCard = (props: { relationsType?: EntityRelationAggregation; relationAggregation?: EntityRelationAggregation; }) => { + const { t } = useTranslationRef(orgTranslationRef); const { - memberDisplayTitle = 'Members', + memberDisplayTitle = t('membersListCard.title'), pageSize = 50, showAggregateMembersToggle, relationType = 'memberof', @@ -236,7 +239,13 @@ export const MembersListCard = (props: { } const nbPages = Math.ceil((members?.length || 0) / pageSize); - const paginationLabel = nbPages < 2 ? '' : `, page ${page} of ${nbPages}`; + const paginationLabel = + nbPages < 2 + ? '' + : t('membersListCard.paginationLabel', { + page: String(page), + nbPages: String(nbPages), + }); const pagination = ( - - This group has no {memberDisplayTitle.toLocaleLowerCase()}. - + {t('membersListCard.noMembersDescription')} ); } @@ -273,23 +280,29 @@ export const MembersListCard = (props: { title={`${memberDisplayTitle} (${ members?.length || 0 }${paginationLabel})`} - subheader={`of ${displayName}`} + subheader={t('membersListCard.subtitle', { + groupName: displayName, + })} {...(nbPages <= 1 ? {} : { actions: pagination })} className={classes.root} cardClassName={classes.cardContent} > {showAggregateMembersToggle && ( <> - Direct Members + {t('membersListCard.aggregateMembersToggle.directMembers')} { setShowAggregateMembers(!showAggregateMembers); }} - inputProps={{ 'aria-label': 'Users Type Switch' }} + inputProps={{ + 'aria-label': t( + 'membersListCard.aggregateMembersToggle.ariaLabel', + ), + }} /> - Aggregated Members + {t('membersListCard.aggregateMembersToggle.aggregatedMembers')} )} {showAggregateMembers && loadingDescendantMembers ? ( diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index d179573b09..ff9fd6cec8 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -26,6 +26,8 @@ import { makeStyles } from '@material-ui/core/styles'; import { useEffect, useState } from 'react'; import { ComponentsGrid } from './ComponentsGrid'; import { EntityRelationAggregation } from '../types'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { orgTranslationRef } from '../../../translation'; const useStyles = makeStyles(theme => ({ card: { @@ -83,6 +85,7 @@ export const OwnershipCard = (props: { hideRelationsToggle === undefined ? false : hideRelationsToggle; const classes = useStyles(); const { entity } = useEntity(); + const { t } = useTranslationRef(orgTranslationRef); const defaultRelationAggregation = entity.kind === 'User' ? 'aggregated' : 'direct'; @@ -98,7 +101,7 @@ export const OwnershipCard = (props: { return ( - Direct Relations + {t('ownershipCard.aggregateRelationsToggle.directRelations')} - Aggregated Relations + {t('ownershipCard.aggregateRelationsToggle.aggregatedRelations')} diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.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..fdfe3fd5c3 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 { 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,26 @@ 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'; + +const useStyles = makeStyles((theme: any) => ({ + 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, + }, +})); + const CardTitle = (props: { title?: string }) => props.title ? ( @@ -58,10 +86,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 +130,8 @@ export const UserProfileCard = (props: { <> {entityMetadataEditUrl && ( @@ -107,7 +151,7 @@ export const UserProfileCard = (props: { {profile?.email && ( - + @@ -117,24 +161,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/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 fd25849e16..0d5f459c4c 100644 --- a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md +++ b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-permission-backend-module-allow-all-policy +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + +## 0.2.8 + +### Patch Changes + +- 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 diff --git a/plugins/permission-backend-module-policy-allow-all/package.json b/plugins/permission-backend-module-policy-allow-all/package.json index 63c1d3e98d..3ef2bcd7e6 100644 --- a/plugins/permission-backend-module-policy-allow-all/package.json +++ b/plugins/permission-backend-module-policy-allow-all/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend-module-allow-all-policy", - "version": "0.2.8-next.2", + "version": "0.2.9-next.1", "description": "Allow all policy backend module for the permission plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index f6e675c843..8d0849da56 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-permission-backend +## 0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## 0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + +## 0.7.0 + +### Minor Changes + +- 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 diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 88ca6ad884..ef970b4c5c 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.7.0-next.2", + "version": "0.7.1-next.1", "backstage": { "role": "backend-plugin", "pluginId": "permission", diff --git a/plugins/permission-common/CHANGELOG.md b/plugins/permission-common/CHANGELOG.md index d23bb180f0..a8355d2dd7 100644 --- a/plugins/permission-common/CHANGELOG.md +++ b/plugins/permission-common/CHANGELOG.md @@ -1,5 +1,20 @@ # @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 + ## 0.9.0-next.0 ### Minor Changes diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index 19fd84bb5a..95c1fa9a00 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.0", "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-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 0b7c03bac1..c86d51f9c2 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-permission-node +## 0.10.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0 + +## 0.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## 0.10.0 + +### Minor Changes + +- 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 diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 7e388faf29..805b1c919b 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-node", - "version": "0.10.0-next.2", + "version": "0.10.1-next.1", "description": "Common permission and authorization utilities for backend plugins", "backstage": { "role": "node-library", diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index d68a3a1566..8c29c0cc6d 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 + ## 0.4.34-next.1 ### Patch Changes diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index f52f0eb58b..b9cb46ac47 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.34", "backstage": { "role": "web-library", "pluginId": "permission", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 573f50ff60..924fc7c193 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-proxy-backend +## 0.6.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/types@1.2.1 + - @backstage/plugin-proxy-node@0.1.5-next.1 + +## 0.6.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-proxy-node@0.1.5-next.0 + +## 0.6.2 + +### Patch Changes + +- 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 diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 3ad9442d97..fc3c2d3147 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.6.2-next.2", + "version": "0.6.3-next.1", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", "backstage": { "role": "backend-plugin", diff --git a/plugins/proxy-node/CHANGELOG.md b/plugins/proxy-node/CHANGELOG.md index db7df34df3..c1cfc3a736 100644 --- a/plugins/proxy-node/CHANGELOG.md +++ b/plugins/proxy-node/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-proxy-node +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## 0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.3.1 + ## 0.1.4-next.2 ### Patch Changes diff --git a/plugins/proxy-node/package.json b/plugins/proxy-node/package.json index 5318593350..a99b2b6913 100644 --- a/plugins/proxy-node/package.json +++ b/plugins/proxy-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-node", - "version": "0.1.4-next.2", + "version": "0.1.5-next.1", "description": "The plugin-proxy-node module for @backstage/plugin-proxy-backend", "backstage": { "role": "node-library", diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md index 58f802e4fd..d7fcaf327e 100644 --- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -1,5 +1,47 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index f16c53d00d..96afe7e671 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.3", + "version": "0.2.10-next.2", "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 8138ca43aa..755d78a9dd 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,53 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index d6a5761fa0..61a8c08e6e 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.3", + "version": "0.2.10-next.2", "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 cc84fff174..9322511844 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,49 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index f78ebfec25..ace646fd04 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.3", + "version": "0.2.10-next.2", "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 0e7d33210b..208fae4d41 100644 --- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,56 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json index 27ac5f6bc0..341ce13004 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.3", + "version": "0.3.11-next.2", "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 2b161776cb..b445b93afb 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,47 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index d28013752e..33dc25f75b 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.3", + "version": "0.3.10-next.2", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/report.api.md b/plugins/scaffolder-backend-module-confluence-to-markdown/report.api.md index 64584ad4b5..1a3330c135 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/report.api.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/report.api.md @@ -5,7 +5,6 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { UrlReaderService } from '@backstage/backend-plugin-api'; @@ -24,7 +23,10 @@ export const createConfluenceToMarkdownAction: (options: { confluenceUrls: string[]; repoUrl: string; }, - JsonObject, - 'v1' + { + repo?: string | undefined; + owner?: string | undefined; + }, + 'v2' >; ``` diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts index aa9f94988a..63fa738a19 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts @@ -49,33 +49,36 @@ export const createConfluenceToMarkdownAction = (options: { [key: string]: string; }; - return createTemplateAction<{ - confluenceUrls: string[]; - repoUrl: string; - }>({ + return createTemplateAction({ id: 'confluence:transform:markdown', description: 'Transforms Confluence content to Markdown', examples, schema: { input: { - properties: { - confluenceUrls: { - type: 'array', - title: 'Confluence URL', + confluenceUrls: z => + z.array(z.string(), { description: 'Paste your Confluence url. Ensure it follows this format: https://{confluence+base+url}/display/{spacekey}/{page+title} or https://{confluence+base+url}/spaces/{spacekey}/pages/1234567/{page+title} for Confluence Cloud', - items: { - type: 'string', - default: 'Confluence URL', - }, - }, - repoUrl: { - type: 'string', - title: 'GitHub Repo Url', + }), + repoUrl: z => + z.string({ description: 'mkdocs.yml file location inside the github repo you want to store the document', - }, - }, + }), + }, + output: { + repo: z => + z + .string({ + description: 'Repository name', + }) + .optional(), + owner: z => + z + .string({ + description: 'Repository owner', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index 7b6b0c3dc8..f94ba07eca 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,54 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 4618c01653..b47f184c6a 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.3", + "version": "0.3.11-next.2", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-cookiecutter/report.api.md b/plugins/scaffolder-backend-module-cookiecutter/report.api.md index 4f963ce818..2dfba3f42f 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/report.api.md +++ b/plugins/scaffolder-backend-module-cookiecutter/report.api.md @@ -4,7 +4,6 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; -import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { UrlReaderService } from '@backstage/backend-plugin-api'; @@ -48,13 +47,15 @@ export function createFetchCookiecutterAction(options: { }): TemplateAction< { url: string; - targetPath?: string; - values: JsonObject; - copyWithoutRender?: string[]; - extensions?: string[]; - imageName?: string; + values: Record; + targetPath?: string | undefined; + copyWithoutRender?: string[] | undefined; + extensions?: string[] | undefined; + imageName?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; ``` diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts index b72e25f349..3a4ee8c3d7 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts @@ -149,65 +149,50 @@ export function createFetchCookiecutterAction(options: { }) { const { reader, containerRunner, integrations } = options; - return createTemplateAction<{ - url: string; - targetPath?: string; - values: JsonObject; - copyWithoutRender?: string[]; - extensions?: string[]; - imageName?: string; - }>({ + return createTemplateAction({ id: 'fetch:cookiecutter', description: 'Downloads a template from the given URL into the workspace, and runs cookiecutter on it.', examples, schema: { input: { - type: 'object', - required: ['url'], - properties: { - url: { - title: 'Fetch URL', + url: z => + z.string({ description: 'Relative path or absolute URL pointing to the directory tree to fetch', - type: 'string', - }, - targetPath: { - title: 'Target Path', - description: - 'Target path within the working directory to download the contents to.', - type: 'string', - }, - values: { - title: 'Template Values', + }), + targetPath: z => + z + .string({ + description: + 'Target path within the working directory to download the contents to.', + }) + .optional(), + values: z => + z.record(z.unknown(), { description: 'Values to pass on to cookiecutter for templating', - type: 'object', - }, - copyWithoutRender: { - title: 'Copy Without Render', - description: - 'Avoid rendering directories and files in the template', - type: 'array', - items: { - type: 'string', - }, - }, - extensions: { - title: 'Template Extensions', - description: - "Jinja2 extensions to add filters, tests, globals or extend the parser. Extensions must be installed in the container or on the host where Cookiecutter executes. See the contrib directory in Backstage's repo for more information", - type: 'array', - items: { - type: 'string', - }, - }, - imageName: { - title: 'Cookiecutter Docker image', - description: - "Specify a custom Docker image to run cookiecutter, to override the default: 'spotify/backstage-cookiecutter'. This can be used to execute cookiecutter with Template Extensions. Used only when a local cookiecutter is not found.", - type: 'string', - }, - }, + }), + copyWithoutRender: z => + z + .array(z.string(), { + description: + 'Avoid rendering directories and files in the template', + }) + .optional(), + extensions: z => + z + .array(z.string(), { + description: + "Jinja2 extensions to add filters, tests, globals or extend the parser. Extensions must be installed in the container or on the host where Cookiecutter executes. See the contrib directory in Backstage's repo for more information", + }) + .optional(), + imageName: z => + z + .string({ + description: + "Specify a custom Docker image to run cookiecutter, to override the default: 'spotify/backstage-cookiecutter'. This can be used to execute cookiecutter with Template Extensions. Used only when a local cookiecutter is not found.", + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md index 563d5f588c..c3169b0d28 100644 --- a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,46 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-backend-module-gcp/package.json b/plugins/scaffolder-backend-module-gcp/package.json index dde1a8ba87..bed08235b8 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.3", + "version": "0.2.10-next.2", "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 23ea33f17a..91b6f96ff6 100644 --- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,48 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index 32678efabf..c45c818f54 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.3", + "version": "0.2.10-next.2", "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 72d4cbe1d7..7b364a1d07 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,47 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index c3b14f0ba1..bf8d911a81 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.3", + "version": "0.2.10-next.2", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gitea/report.api.md b/plugins/scaffolder-backend-module-gitea/report.api.md index dede7f013a..afc8286365 100644 --- a/plugins/scaffolder-backend-module-gitea/report.api.md +++ b/plugins/scaffolder-backend-module-gitea/report.api.md @@ -5,7 +5,6 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -17,16 +16,20 @@ export function createPublishGiteaAction(options: { { repoUrl: string; description: string; - defaultBranch?: string; - repoVisibility?: 'private' | 'public'; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - sourcePath?: string; - signCommit?: boolean; + defaultBranch?: string | undefined; + repoVisibility?: 'private' | 'public' | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + sourcePath?: string | undefined; + signCommit?: boolean | undefined; }, - JsonObject, - 'v1' + { + remoteUrl?: string | undefined; + repoContentsUrl?: string | undefined; + commitHash?: string | undefined; + }, + 'v2' >; // @public diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts index 4321de2167..160725c7bd 100644 --- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts @@ -171,7 +171,7 @@ async function checkAvailabilityGiteaRepository( owner?: string; repo: string; defaultBranch: string; - ctx: ActionContext; + ctx: ActionContext; }, ) { const startTimestamp = Date.now(); @@ -210,88 +210,83 @@ export function createPublishGiteaAction(options: { }) { const { integrations, config } = options; - return createTemplateAction<{ - repoUrl: string; - description: string; - defaultBranch?: string; - repoVisibility?: 'private' | 'public'; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - sourcePath?: string; - signCommit?: boolean; - }>({ + return createTemplateAction({ id: 'publish:gitea', description: 'Initializes a git repository using the content of the workspace, and publishes it to Gitea.', examples, schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: { - title: 'Repository Location', - type: 'string', - }, - description: { - title: 'Repository Description', - type: 'string', - }, - defaultBranch: { - title: 'Default Branch', - type: 'string', - description: `Sets the default branch on the repository. The default value is 'main'`, - }, - repoVisibility: { - title: 'Repository Visibility', - description: `Sets the visibility of the repository. The default value is 'public'.`, - type: 'string', - enum: ['private', 'public'], - }, - gitCommitMessage: { - title: 'Git Commit Message', - type: 'string', - description: `Sets the commit message on the repository. The default value is 'initial commit'`, - }, - gitAuthorName: { - title: 'Default Author Name', - type: 'string', - description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, - }, - gitAuthorEmail: { - title: 'Default Author Email', - type: 'string', - description: `Sets the default author email for the commit.`, - }, - sourcePath: { - title: 'Source Path', - type: 'string', - description: `Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.`, - }, - signCommit: { - title: 'Sign commit', - type: 'boolean', - description: 'Sign commit with configured PGP private key', - }, - }, + repoUrl: z => + z.string({ + description: 'Repository Location', + }), + description: z => + z.string({ + description: 'Repository Description', + }), + defaultBranch: z => + z + .string({ + description: `Sets the default branch on the repository. The default value is 'main'`, + }) + .optional(), + repoVisibility: z => + z + .enum(['private', 'public'], { + description: `Sets the visibility of the repository. The default value is 'public'.`, + }) + .optional(), + gitCommitMessage: z => + z + .string({ + description: `Sets the commit message on the repository. The default value is 'initial commit'`, + }) + .optional(), + gitAuthorName: z => + z + .string({ + description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, + }) + .optional(), + gitAuthorEmail: z => + z + .string({ + description: `Sets the default author email for the commit.`, + }) + .optional(), + sourcePath: z => + z + .string({ + description: `Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.`, + }) + .optional(), + signCommit: z => + z + .boolean({ + description: 'Sign commit with configured PGP private key', + }) + .optional(), }, output: { - type: 'object', - properties: { - remoteUrl: { - title: 'A URL to the repository with the provider', - type: 'string', - }, - repoContentsUrl: { - title: 'A URL to the root of the repository', - type: 'string', - }, - commitHash: { - title: 'The git commit hash of the initial commit', - type: 'string', - }, - }, + remoteUrl: z => + z + .string({ + description: 'A URL to the repository with the provider', + }) + .optional(), + repoContentsUrl: z => + z + .string({ + description: 'A URL to the root of the repository', + }) + .optional(), + commitHash: z => + z + .string({ + description: 'The git commit hash of the initial commit', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index 67b338beac..d4a2c34cc1 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,94 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index 1e6b265412..ba4ae9c718 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.3", + "version": "0.8.0-next.2", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", @@ -43,18 +43,19 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/types": "workspace:^", "@octokit/webhooks": "^10.9.2", "libsodium-wrappers": "^0.7.11", "octokit": "^3.0.0", "octokit-plugin-create-pull-request": "^5.0.0", - "yaml": "^2.0.0" + "yaml": "^2.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/scaffolder-backend-module-github/report.api.md b/plugins/scaffolder-backend-module-github/report.api.md index 219506d948..28f710557a 100644 --- a/plugins/scaffolder-backend-module-github/report.api.md +++ b/plugins/scaffolder-backend-module-github/report.api.md @@ -3,13 +3,11 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AuthService } from '@backstage/backend-plugin-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; import { createPullRequest } from 'octokit-plugin-create-pull-request'; import { GithubCredentialsProvider } from '@backstage/integration'; -import { JsonObject } from '@backstage/types'; import { Octokit } from 'octokit'; import { OctokitOptions } from '@octokit/core/dist-types/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; @@ -25,13 +23,13 @@ export function createGithubActionsDispatchAction(options: { repoUrl: string; workflowId: string; branchOrTagName: string; - workflowInputs?: { - [key: string]: string; - }; - token?: string; + workflowInputs?: Record | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public @@ -43,11 +41,13 @@ export function createGithubAutolinksAction(options: { repoUrl: string; keyPrefix: string; urlTemplate: string; - isAlphanumeric?: boolean; - token?: string; + isAlphanumeric?: boolean | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public @@ -56,35 +56,37 @@ export function createGithubBranchProtectionAction(options: { }): TemplateAction< { repoUrl: string; - branch?: string; - enforceAdmins?: boolean; - requiredApprovingReviewCount?: number; - requireCodeOwnerReviews?: boolean; - dismissStaleReviews?: boolean; + branch?: string | undefined; + enforceAdmins?: boolean | undefined; + requiredApprovingReviewCount?: number | undefined; + requireCodeOwnerReviews?: boolean | undefined; + dismissStaleReviews?: boolean | undefined; bypassPullRequestAllowances?: | { - users?: string[]; - teams?: string[]; - apps?: string[]; + apps?: string[] | undefined; + teams?: string[] | undefined; + users?: string[] | undefined; } | undefined; restrictions?: | { - users: string[]; teams: string[]; - apps?: string[]; + users: string[]; + apps?: string[] | undefined; } | undefined; - requiredStatusCheckContexts?: string[]; - requireBranchesToBeUpToDate?: boolean; - requiredConversationResolution?: boolean; - requireLastPushApproval?: boolean; - requiredCommitSigning?: boolean; - requiredLinearHistory?: boolean; - token?: string; + requiredStatusCheckContexts?: string[] | undefined; + requireBranchesToBeUpToDate?: boolean | undefined; + requiredConversationResolution?: boolean | undefined; + requireLastPushApproval?: boolean | undefined; + requiredCommitSigning?: boolean | undefined; + requiredLinearHistory?: boolean | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public @@ -96,41 +98,42 @@ export function createGithubDeployKeyAction(options: { publicKey: string; privateKey: string; deployKeyName: string; - privateKeySecretName?: string; - token?: string; + privateKeySecretName?: string | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + privateKeySecretName: string; + }, + 'v2' >; // @public export function createGithubEnvironmentAction(options: { integrations: ScmIntegrationRegistry; - catalogClient?: CatalogApi; - auth?: AuthService; + catalog: CatalogService; }): TemplateAction< { repoUrl: string; name: string; - deploymentBranchPolicy?: { - protected_branches: boolean; - custom_branch_policies: boolean; - }; - customBranchPolicyNames?: string[]; - customTagPolicyNames?: string[]; - environmentVariables?: { - [key: string]: string; - }; - secrets?: { - [key: string]: string; - }; - token?: string; - waitTimer?: number; - preventSelfReview?: boolean; - reviewers?: string[]; + deploymentBranchPolicy?: + | { + protected_branches: boolean; + custom_branch_policies: boolean; + } + | undefined; + customBranchPolicyNames?: string[] | undefined; + customTagPolicyNames?: string[] | undefined; + environmentVariables?: Record | undefined; + secrets?: Record | undefined; + token?: string | undefined; + waitTimer?: number | undefined; + preventSelfReview?: boolean | undefined; + reviewers?: string[] | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public @@ -142,10 +145,12 @@ export function createGithubIssuesLabelAction(options: { repoUrl: string; number: number; labels: string[]; - token?: string; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public @@ -155,13 +160,15 @@ export function createGithubPagesEnableAction(options: { }): TemplateAction< { repoUrl: string; - buildType?: 'legacy' | 'workflow'; - sourceBranch?: string; - sourcePath?: '/' | '/docs'; - token?: string; + buildType?: 'legacy' | 'workflow' | undefined; + sourceBranch?: string | undefined; + sourcePath?: '/' | '/docs' | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public @@ -198,73 +205,76 @@ export function createGithubRepoCreateAction(options: { }): TemplateAction< { repoUrl: string; - description?: string; - homepage?: string; - access?: string; - deleteBranchOnMerge?: boolean; - gitAuthorName?: string; - gitAuthorEmail?: string; - allowRebaseMerge?: boolean; - allowSquashMerge?: boolean; - squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE'; - squashMergeCommitMessage?: 'PR_BODY' | 'COMMIT_MESSAGES' | 'BLANK'; - allowMergeCommit?: boolean; - allowAutoMerge?: boolean; - allowUpdateBranch?: boolean; - requireCodeOwnerReviews?: boolean; - bypassPullRequestAllowances?: { - users?: string[]; - teams?: string[]; - apps?: string[]; - }; - requiredApprovingReviewCount?: number; - restrictions?: { - users: string[]; - teams: string[]; - apps?: string[]; - }; - requiredStatusCheckContexts?: string[]; - requireBranchesToBeUpToDate?: boolean; - requiredConversationResolution?: boolean; - repoVisibility?: 'private' | 'internal' | 'public'; - collaborators?: Array< + description?: string | undefined; + homepage?: string | undefined; + access?: string | undefined; + requireCodeOwnerReviews?: boolean | undefined; + bypassPullRequestAllowances?: | { - user: string; - access: string; + apps?: string[] | undefined; + teams?: string[] | undefined; + users?: string[] | undefined; } + | undefined; + requiredApprovingReviewCount?: number | undefined; + restrictions?: | { - team: string; - access: string; + teams: string[]; + users: string[]; + apps?: string[] | undefined; } + | undefined; + requiredStatusCheckContexts?: string[] | undefined; + requireBranchesToBeUpToDate?: boolean | undefined; + requiredConversationResolution?: boolean | undefined; + repoVisibility?: 'internal' | 'private' | 'public' | undefined; + deleteBranchOnMerge?: boolean | undefined; + allowMergeCommit?: boolean | undefined; + allowSquashMerge?: boolean | undefined; + squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE' | undefined; + squashMergeCommitMessage?: + | 'PR_BODY' + | 'COMMIT_MESSAGES' + | 'BLANK' + | undefined; + allowRebaseMerge?: boolean | undefined; + allowAutoMerge?: boolean | undefined; + allowUpdateBranch?: boolean | undefined; + collaborators?: + | ( + | { + user: string; + access: string; + } + | { + team: string; + access: string; + } + )[] + | undefined; + hasProjects?: boolean | undefined; + hasWiki?: boolean | undefined; + hasIssues?: boolean | undefined; + token?: string | undefined; + topics?: string[] | undefined; + repoVariables?: Record | undefined; + secrets?: Record | undefined; + oidcCustomization?: | { - username: string; - access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; + useDefault: boolean; + includeClaimKeys?: string[] | undefined; } - >; - hasProjects?: boolean; - hasWiki?: boolean; - hasIssues?: boolean; - token?: string; - topics?: string[]; - repoVariables?: { - [key: string]: string; - }; - secrets?: { - [key: string]: string; - }; - oidcCustomization?: { - useDefault: boolean; - includeClaimKeys?: string[]; - }; - requireCommitSigning?: boolean; - requiredLinearHistory?: boolean; - customProperties?: { - [key: string]: string; - }; - subscribe?: boolean; + | undefined; + requiredCommitSigning?: boolean | undefined; + requiredLinearHistory?: boolean | undefined; + customProperties?: Record | undefined; + subscribe?: boolean | undefined; }, - JsonObject, - 'v1' + { + remoteUrl: string; + repoContentsUrl: string; + }, + 'v2' >; // @public @@ -275,41 +285,44 @@ export function createGithubRepoPushAction(options: { }): TemplateAction< { repoUrl: string; - description?: string; - defaultBranch?: string; - protectDefaultBranch?: boolean; - protectEnforceAdmins?: boolean; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - requireCodeOwnerReviews?: boolean; - dismissStaleReviews?: boolean; + requireCodeOwnerReviews?: boolean | undefined; + dismissStaleReviews?: boolean | undefined; + requiredStatusCheckContexts?: string[] | undefined; bypassPullRequestAllowances?: | { - users?: string[]; - teams?: string[]; - apps?: string[]; + apps?: string[] | undefined; + teams?: string[] | undefined; + users?: string[] | undefined; } | undefined; - requiredApprovingReviewCount?: number; + requiredApprovingReviewCount?: number | undefined; restrictions?: | { - users: string[]; teams: string[]; - apps?: string[]; + users: string[]; + apps?: string[] | undefined; } | undefined; - requiredStatusCheckContexts?: string[]; - requireBranchesToBeUpToDate?: boolean; - requiredConversationResolution?: boolean; - sourcePath?: string; - token?: string; - requiredCommitSigning?: boolean; - requiredLinearHistory?: boolean; - requireLastPushApproval?: boolean; + requireBranchesToBeUpToDate?: boolean | undefined; + requiredConversationResolution?: boolean | undefined; + requireLastPushApproval?: boolean | undefined; + defaultBranch?: string | undefined; + protectDefaultBranch?: boolean | undefined; + protectEnforceAdmins?: boolean | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + sourcePath?: string | undefined; + token?: string | undefined; + requiredCommitSigning?: boolean | undefined; + requiredLinearHistory?: boolean | undefined; }, - JsonObject, - 'v1' + { + remoteUrl: string; + repoContentsUrl: string; + commitHash: string; + }, + 'v2' >; // @public @@ -321,15 +334,17 @@ export function createGithubWebhookAction(options: { { repoUrl: string; webhookUrl: string; - webhookSecret?: string; - events?: string[]; - active?: boolean; - contentType?: 'form' | 'json'; - insecureSsl?: boolean; - token?: string; + webhookSecret?: string | undefined; + events?: string[] | '*'[] | undefined; + active?: boolean | undefined; + contentType?: 'form' | 'json' | undefined; + insecureSsl?: boolean | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public @@ -340,84 +355,86 @@ export function createPublishGithubAction(options: { }): TemplateAction< { repoUrl: string; - description?: string; - homepage?: string; - access?: string; - defaultBranch?: string; - protectDefaultBranch?: boolean; - protectEnforceAdmins?: boolean; - deleteBranchOnMerge?: boolean; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - allowRebaseMerge?: boolean; - allowSquashMerge?: boolean; - squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE'; - squashMergeCommitMessage?: 'PR_BODY' | 'COMMIT_MESSAGES' | 'BLANK'; - allowMergeCommit?: boolean; - allowAutoMerge?: boolean; - allowUpdateBranch?: boolean; - sourcePath?: string; + description?: string | undefined; + homepage?: string | undefined; + access?: string | undefined; bypassPullRequestAllowances?: | { - users?: string[]; - teams?: string[]; - apps?: string[]; + apps?: string[] | undefined; + teams?: string[] | undefined; + users?: string[] | undefined; } | undefined; - requiredApprovingReviewCount?: number; + requiredApprovingReviewCount?: number | undefined; restrictions?: | { - users: string[]; teams: string[]; - apps?: string[]; + users: string[]; + apps?: string[] | undefined; } | undefined; - requireCodeOwnerReviews?: boolean; - dismissStaleReviews?: boolean; - requiredStatusCheckContexts?: string[]; - requireBranchesToBeUpToDate?: boolean; - requiredConversationResolution?: boolean; - requireLastPushApproval?: boolean; - repoVisibility?: 'private' | 'internal' | 'public'; - collaborators?: Array< - | { - user: string; - access: string; - } - | { - team: string; - access: string; - } - | { - username: string; - access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; - } - >; + requireCodeOwnerReviews?: boolean | undefined; + dismissStaleReviews?: boolean | undefined; + requiredStatusCheckContexts?: string[] | undefined; + requireBranchesToBeUpToDate?: boolean | undefined; + requiredConversationResolution?: boolean | undefined; + requireLastPushApproval?: boolean | undefined; + repoVisibility?: 'internal' | 'private' | 'public' | undefined; + defaultBranch?: string | undefined; + protectDefaultBranch?: boolean | undefined; + protectEnforceAdmins?: boolean | undefined; + deleteBranchOnMerge?: boolean | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + allowMergeCommit?: boolean | undefined; + allowSquashMerge?: boolean | undefined; + squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE' | undefined; + squashMergeCommitMessage?: + | 'PR_BODY' + | 'COMMIT_MESSAGES' + | 'BLANK' + | undefined; + allowRebaseMerge?: boolean | undefined; + allowAutoMerge?: boolean | undefined; + allowUpdateBranch?: boolean | undefined; + sourcePath?: string | undefined; + collaborators?: + | ( + | { + user: string; + access: string; + } + | { + team: string; + access: string; + } + )[] + | undefined; hasProjects?: boolean | undefined; hasWiki?: boolean | undefined; hasIssues?: boolean | undefined; - token?: string; - topics?: string[]; - repoVariables?: { - [key: string]: string; - }; - secrets?: { - [key: string]: string; - }; - oidcCustomization?: { - useDefault: boolean; - includeClaimKeys?: string[]; - }; - requiredCommitSigning?: boolean; - requiredLinearHistory?: boolean; - customProperties?: { - [key: string]: string; - }; - subscribe?: boolean; + token?: string | undefined; + topics?: string[] | undefined; + repoVariables?: Record | undefined; + secrets?: Record | undefined; + oidcCustomization?: + | { + useDefault: boolean; + includeClaimKeys?: string[] | undefined; + } + | undefined; + requiredCommitSigning?: boolean | undefined; + requiredLinearHistory?: boolean | undefined; + customProperties?: Record | undefined; + subscribe?: boolean | undefined; }, - JsonObject, - 'v1' + { + remoteUrl: string; + repoContentsUrl: string; + commitHash: string; + }, + 'v2' >; // @public @@ -425,28 +442,32 @@ export const createPublishGithubPullRequestAction: ( options: CreateGithubPullRequestActionOptions, ) => TemplateAction< { - title: string; - branchName: string; - targetBranchName?: string; - description: string; repoUrl: string; - draft?: boolean; - targetPath?: string; - sourcePath?: string; - token?: string; - reviewers?: string[]; - assignees?: string[]; - teamReviewers?: string[]; - commitMessage?: string; - update?: boolean; - forceFork?: boolean; - gitAuthorName?: string; - gitAuthorEmail?: string; - forceEmptyGitAuthor?: boolean; - createWhenEmpty?: boolean; + branchName: string; + title: string; + description: string; + targetBranchName?: string | undefined; + draft?: boolean | undefined; + sourcePath?: string | undefined; + targetPath?: string | undefined; + token?: string | undefined; + reviewers?: string[] | undefined; + assignees?: string[] | undefined; + teamReviewers?: string[] | undefined; + commitMessage?: string | undefined; + update?: boolean | undefined; + forceFork?: boolean | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + forceEmptyGitAuthor?: boolean | undefined; + createWhenEmpty?: boolean | undefined; }, - JsonObject, - 'v1' + { + targetBranchName: string; + remoteUrl: string; + pullRequestNumber: number; + }, + 'v2' >; // @public diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts index 4f3cc9f03e..0d279f09b4 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts @@ -98,7 +98,7 @@ describe('publish:github', () => { jest.requireActual('./helpers'); const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts index f6a67fb398..2fd377ee04 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts @@ -101,7 +101,7 @@ describe('publish:github', () => { jest.requireActual('./gitHelpers'); const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { @@ -605,6 +605,7 @@ describe('publish:github', () => { const customAuthorIntegrations = ScmIntegrations.fromConfig(customAuthorConfig); + const customAuthorAction = createPublishGithubAction({ integrations: customAuthorIntegrations, config: customAuthorConfig, diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.ts b/plugins/scaffolder-backend-module-github/src/actions/github.ts index c47e789bdd..c3583a6e78 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.ts @@ -47,140 +47,61 @@ export function createPublishGithubAction(options: { }) { const { integrations, config, githubCredentialsProvider } = options; - return createTemplateAction<{ - repoUrl: string; - description?: string; - homepage?: string; - access?: string; - defaultBranch?: string; - protectDefaultBranch?: boolean; - protectEnforceAdmins?: boolean; - deleteBranchOnMerge?: boolean; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - allowRebaseMerge?: boolean; - allowSquashMerge?: boolean; - squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE'; - squashMergeCommitMessage?: 'PR_BODY' | 'COMMIT_MESSAGES' | 'BLANK'; - allowMergeCommit?: boolean; - allowAutoMerge?: boolean; - allowUpdateBranch?: boolean; - sourcePath?: string; - bypassPullRequestAllowances?: - | { - users?: string[]; - teams?: string[]; - apps?: string[]; - } - | undefined; - requiredApprovingReviewCount?: number; - restrictions?: - | { - users: string[]; - teams: string[]; - apps?: string[]; - } - | undefined; - requireCodeOwnerReviews?: boolean; - dismissStaleReviews?: boolean; - requiredStatusCheckContexts?: string[]; - requireBranchesToBeUpToDate?: boolean; - requiredConversationResolution?: boolean; - requireLastPushApproval?: boolean; - repoVisibility?: 'private' | 'internal' | 'public'; - collaborators?: Array< - | { - user: string; - access: string; - } - | { - team: string; - access: string; - } - | { - /** @deprecated This field is deprecated in favor of team */ - username: string; - access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; - } - >; - hasProjects?: boolean | undefined; - hasWiki?: boolean | undefined; - hasIssues?: boolean | undefined; - token?: string; - topics?: string[]; - repoVariables?: { [key: string]: string }; - secrets?: { [key: string]: string }; - oidcCustomization?: { - useDefault: boolean; - includeClaimKeys?: string[]; - }; - requiredCommitSigning?: boolean; - requiredLinearHistory?: boolean; - customProperties?: { [key: string]: string }; - subscribe?: boolean; - }>({ + return createTemplateAction({ id: 'publish:github', description: 'Initializes a git repository of contents in workspace and publishes it to GitHub.', examples, schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: inputProps.repoUrl, - description: inputProps.description, - homepage: inputProps.homepage, - access: inputProps.access, - bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, - requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, - restrictions: inputProps.restrictions, - requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, - dismissStaleReviews: inputProps.dismissStaleReviews, - requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, - requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, - requiredConversationResolution: - inputProps.requiredConversationResolution, - requireLastPushApproval: inputProps.requireLastPushApproval, - repoVisibility: inputProps.repoVisibility, - defaultBranch: inputProps.defaultBranch, - protectDefaultBranch: inputProps.protectDefaultBranch, - protectEnforceAdmins: inputProps.protectEnforceAdmins, - deleteBranchOnMerge: inputProps.deleteBranchOnMerge, - gitCommitMessage: inputProps.gitCommitMessage, - gitAuthorName: inputProps.gitAuthorName, - gitAuthorEmail: inputProps.gitAuthorEmail, - allowMergeCommit: inputProps.allowMergeCommit, - allowSquashMerge: inputProps.allowSquashMerge, - squashMergeCommitTitle: inputProps.squashMergeCommitTitle, - squashMergeCommitMessage: inputProps.squashMergeCommitMessage, - allowRebaseMerge: inputProps.allowRebaseMerge, - allowAutoMerge: inputProps.allowAutoMerge, - allowUpdateBranch: inputProps.allowUpdateBranch, - sourcePath: inputProps.sourcePath, - collaborators: inputProps.collaborators, - hasProjects: inputProps.hasProjects, - hasWiki: inputProps.hasWiki, - hasIssues: inputProps.hasIssues, - token: inputProps.token, - topics: inputProps.topics, - repoVariables: inputProps.repoVariables, - secrets: inputProps.secrets, - oidcCustomization: inputProps.oidcCustomization, - requiredCommitSigning: inputProps.requiredCommitSigning, - requiredLinearHistory: inputProps.requiredLinearHistory, - customProperties: inputProps.customProperties, - subscribe: inputProps.subscribe, - }, + repoUrl: inputProps.repoUrl, + description: inputProps.description, + homepage: inputProps.homepage, + access: inputProps.access, + bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, + requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, + restrictions: inputProps.restrictions, + requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, + dismissStaleReviews: inputProps.dismissStaleReviews, + requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, + requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, + requiredConversationResolution: + inputProps.requiredConversationResolution, + requireLastPushApproval: inputProps.requireLastPushApproval, + repoVisibility: inputProps.repoVisibility, + defaultBranch: inputProps.defaultBranch, + protectDefaultBranch: inputProps.protectDefaultBranch, + protectEnforceAdmins: inputProps.protectEnforceAdmins, + deleteBranchOnMerge: inputProps.deleteBranchOnMerge, + gitCommitMessage: inputProps.gitCommitMessage, + gitAuthorName: inputProps.gitAuthorName, + gitAuthorEmail: inputProps.gitAuthorEmail, + allowMergeCommit: inputProps.allowMergeCommit, + allowSquashMerge: inputProps.allowSquashMerge, + squashMergeCommitTitle: inputProps.squashMergeCommitTitle, + squashMergeCommitMessage: inputProps.squashMergeCommitMessage, + allowRebaseMerge: inputProps.allowRebaseMerge, + allowAutoMerge: inputProps.allowAutoMerge, + allowUpdateBranch: inputProps.allowUpdateBranch, + sourcePath: inputProps.sourcePath, + collaborators: inputProps.collaborators, + hasProjects: inputProps.hasProjects, + hasWiki: inputProps.hasWiki, + hasIssues: inputProps.hasIssues, + token: inputProps.token, + topics: inputProps.topics, + repoVariables: inputProps.repoVariables, + secrets: inputProps.secrets, + oidcCustomization: inputProps.oidcCustomization, + requiredCommitSigning: inputProps.requiredCommitSigning, + requiredLinearHistory: inputProps.requiredLinearHistory, + customProperties: inputProps.customProperties, + subscribe: inputProps.subscribe, }, output: { - type: 'object', - properties: { - remoteUrl: outputProps.remoteUrl, - repoContentsUrl: outputProps.repoContentsUrl, - commitHash: outputProps.commitHash, - }, + remoteUrl: outputProps.remoteUrl, + repoContentsUrl: outputProps.repoContentsUrl, + commitHash: outputProps.commitHash, }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts index 1e59037c04..5583b745d5 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts @@ -53,7 +53,7 @@ describe('github:actions:dispatch', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts index 7739259c5d..94a51e5b4e 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts @@ -50,7 +50,7 @@ describe('github:actions:dispatch', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts index 40e48bb902..f35cbcc04c 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts @@ -37,52 +37,41 @@ export function createGithubActionsDispatchAction(options: { }) { const { integrations, githubCredentialsProvider } = options; - return createTemplateAction<{ - repoUrl: string; - workflowId: string; - branchOrTagName: string; - workflowInputs?: { [key: string]: string }; - token?: string; - }>({ + return createTemplateAction({ id: 'github:actions:dispatch', description: 'Dispatches a GitHub Action workflow for a given branch or tag', examples, schema: { input: { - type: 'object', - required: ['repoUrl', 'workflowId', 'branchOrTagName'], - properties: { - repoUrl: { - title: 'Repository Location', + repoUrl: z => + z.string({ description: 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the new repository name and `owner` is an organization or username', - type: 'string', - }, - workflowId: { - title: 'Workflow ID', + }), + workflowId: z => + z.string({ description: 'The GitHub Action Workflow filename', - type: 'string', - }, - branchOrTagName: { - title: 'Branch or Tag name', + }), + branchOrTagName: z => + z.string({ description: 'The git branch or tag name used to dispatch the workflow', - type: 'string', - }, - workflowInputs: { - title: 'Workflow Inputs', - description: - 'Inputs keys and values to send to GitHub Action configured on the workflow file. The maximum number of properties is 10. ', - type: 'object', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: - 'The `GITHUB_TOKEN` to use for authorization to GitHub', - }, - }, + }), + workflowInputs: z => + z + .record(z.string(), { + description: + 'Inputs keys and values to send to GitHub Action configured on the workflow file. The maximum number of properties is 10.', + }) + .optional(), + token: z => + z + .string({ + description: + 'The `GITHUB_TOKEN` to use for authorization to GitHub', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.examples.test.ts index a686ea7f6d..05d19e5ad6 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.examples.test.ts @@ -53,7 +53,7 @@ describe('github:autolinks:create', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const input = yaml.parse(examples[0].example).steps[0].input; const mockContext = createMockActionContext({ input, diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.test.ts index c378afa17d..532da41946 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.test.ts @@ -51,7 +51,7 @@ describe('github:autolinks:create', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const workspacePath = createMockDirectory().resolve('workspace'); it('should pass context logger to Octokit client', async () => { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.ts b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.ts index 50ed052458..a57386a856 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.ts @@ -37,52 +37,41 @@ export function createGithubAutolinksAction(options: { }) { const { integrations, githubCredentialsProvider } = options; - return createTemplateAction<{ - repoUrl: string; - keyPrefix: string; - urlTemplate: string; - isAlphanumeric?: boolean; - token?: string; - }>({ + return createTemplateAction({ id: 'github:autolinks:create', description: 'Create an autolink reference for a repository', examples, schema: { input: { - type: 'object', - required: ['repoUrl', 'keyPrefix', 'urlTemplate'], - properties: { - repoUrl: { - title: 'Repository Location', + repoUrl: z => + z.string({ description: 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the new repository name and `owner` is an organization or username', - type: 'string', - }, - keyPrefix: { - title: 'Key Prefix', + }), + keyPrefix: z => + z.string({ description: 'This prefix appended by certain characters will generate a link any time it is found in an issue, pull request, or commit.', - type: 'string', - }, - urlTemplate: { - title: 'URL Template', + }), + urlTemplate: z => + z.string({ description: 'The URL must contain `` for the reference number. `` matches different characters depending on the value of isAlphanumeric.', - type: 'string', - }, - isAlphanumeric: { - title: 'Alphanumeric', - description: - 'Whether this autolink reference matches alphanumeric characters. If `true`, the `` parameter of the `url_template` matches alphanumeric characters `A-Z` (case insensitive), `0-9`, and `-`. If `false`, this autolink reference only matches numeric characters. Default: `true`', - type: 'boolean', - default: true, - }, - token: { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to GitHub', - }, - }, + }), + isAlphanumeric: z => + z + .boolean({ + description: + 'Whether this autolink reference matches alphanumeric characters. If `true`, the `` parameter of the `url_template` matches alphanumeric characters `A-Z` (case insensitive), `0-9`, and `-`. If `false`, this autolink reference only matches numeric characters. Default: `true`', + }) + .default(true) + .optional(), + token: z => + z + .string({ + description: 'The token to use for authorization to GitHub', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.examples.test.ts index 14ebc9a1cd..31a15f6019 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.examples.test.ts @@ -50,7 +50,7 @@ describe('github:branch-protection:create', () => { }); const integrations = ScmIntegrations.fromConfig(config); - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.test.ts index 190743c556..d20a02d460 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.test.ts @@ -47,7 +47,7 @@ describe('github:branch-protection:create', () => { }); const integrations = ScmIntegrations.fromConfig(config); - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts index 79eebb4096..f82bfc6950 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts @@ -36,64 +36,28 @@ export function createGithubBranchProtectionAction(options: { }) { const { integrations } = options; - return createTemplateAction<{ - repoUrl: string; - branch?: string; - enforceAdmins?: boolean; - requiredApprovingReviewCount?: number; - requireCodeOwnerReviews?: boolean; - dismissStaleReviews?: boolean; - bypassPullRequestAllowances?: - | { - users?: string[]; - teams?: string[]; - apps?: string[]; - } - | undefined; - restrictions?: - | { - users: string[]; - teams: string[]; - apps?: string[]; - } - | undefined; - requiredStatusCheckContexts?: string[]; - requireBranchesToBeUpToDate?: boolean; - requiredConversationResolution?: boolean; - requireLastPushApproval?: boolean; - requiredCommitSigning?: boolean; - requiredLinearHistory?: boolean; - token?: string; - }>({ + return createTemplateAction({ id: 'github:branch-protection:create', description: 'Configures Branch Protection', examples, schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: inputProps.repoUrl, - branch: { - title: 'Branch name', - description: `The branch to protect. Defaults to the repository's default branch`, - type: 'string', - }, - enforceAdmins: inputProps.protectEnforceAdmins, - requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, - requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, - dismissStaleReviews: inputProps.dismissStaleReviews, - bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, - restrictions: inputProps.restrictions, - requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, - requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, - requiredConversationResolution: - inputProps.requiredConversationResolution, - requireLastPushApproval: inputProps.requireLastPushApproval, - requiredCommitSigning: inputProps.requiredCommitSigning, - requiredLinearHistory: inputProps.requiredLinearHistory, - token: inputProps.token, - }, + repoUrl: inputProps.repoUrl, + branch: inputProps.branch, + enforceAdmins: inputProps.protectEnforceAdmins, + requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, + requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, + dismissStaleReviews: inputProps.dismissStaleReviews, + bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, + restrictions: inputProps.restrictions, + requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, + requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, + requiredConversationResolution: + inputProps.requiredConversationResolution, + requireLastPushApproval: inputProps.requireLastPushApproval, + requiredCommitSigning: inputProps.requiredCommitSigning, + requiredLinearHistory: inputProps.requiredLinearHistory, + token: inputProps.token, }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.examples.test.ts index 62e79dcd68..21d33a24a7 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.examples.test.ts @@ -52,7 +52,7 @@ describe('Usage examples', () => { }, }); const integrations = ScmIntegrations.fromConfig(config); - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext(); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.test.ts index 794ec2cf65..cf506de4b3 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.test.ts @@ -51,7 +51,7 @@ describe('github:deployKey:create', () => { }); const integrations = ScmIntegrations.fromConfig(config); - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.ts b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.ts index 1cb72dac3e..b33e5d1112 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.ts @@ -36,65 +36,50 @@ export function createGithubDeployKeyAction(options: { const { integrations } = options; // For more information on how to define custom actions, see // https://backstage.io/docs/features/software-templates/writing-custom-actions - return createTemplateAction<{ - repoUrl: string; - publicKey: string; - privateKey: string; - deployKeyName: string; - privateKeySecretName?: string; - token?: string; - }>({ + return createTemplateAction({ id: 'github:deployKey:create', description: 'Creates and stores Deploy Keys', examples, schema: { input: { - type: 'object', - required: ['repoUrl', 'publicKey', 'privateKey', 'deployKeyName'], - properties: { - repoUrl: { - title: 'Repository Location', + repoUrl: z => + z.string({ description: 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the new repository name and `owner` is an organization or username', - type: 'string', - }, - publicKey: { - title: 'SSH Public Key', + }), + publicKey: z => + z.string({ description: 'Generated from `ssh-keygen`. Begins with `ssh-rsa`, `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`, `ssh-ed25519`, `sk-ecdsa-sha2-nistp256@openssh.com`, or `sk-ssh-ed25519@openssh.com`.', - type: 'string', - }, - privateKey: { - title: 'SSH Private Key', + }), + privateKey: z => + z.string({ description: 'SSH Private Key generated from `ssh-keygen`', - type: 'string', - }, - deployKeyName: { - title: 'Deploy Key Name', + }), + deployKeyName: z => + z.string({ description: `Name of the Deploy Key`, - type: 'string', - }, - privateKeySecretName: { - title: 'Private Key GitHub Secret Name', - description: - 'Name of the GitHub Secret to store the private key related to the Deploy Key. Defaults to: `KEY_NAME_PRIVATE_KEY` where `KEY_NAME` is the name of the Deploy Key', - type: 'string', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to GitHub', - }, - }, + }), + privateKeySecretName: z => + z + .string({ + description: + 'Name of the GitHub Secret to store the private key related to the Deploy Key. Defaults to: `KEY_NAME_PRIVATE_KEY` where `KEY_NAME` is the name of the Deploy Key', + }) + .optional(), + token: z => + z + .string({ + description: 'The token to use for authorization to GitHub', + }) + .optional(), }, output: { - type: 'object', - properties: { - privateKeySecretName: { - title: 'The GitHub Action Repo Secret Name for the Private Key', - type: 'string', - }, - }, + privateKeySecretName: z => + z.string({ + description: + 'The GitHub Action Repo Secret Name for the Private Key', + }), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts index 9aefd39bb5..c1ddbb5b0e 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts @@ -16,11 +16,11 @@ import { createGithubEnvironmentAction } from './githubEnvironment'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import yaml from 'yaml'; import { examples } from './gitHubEnvironment.examples'; -import { CatalogApi } from '@backstage/catalog-client'; const mockOctokit = { rest: { @@ -42,9 +42,7 @@ const mockOctokit = { }, }, }; -const mockCatalogClient: Partial = { - getEntitiesByRefs: jest.fn(), -}; + jest.mock('octokit', () => ({ Octokit: class { constructor() { @@ -52,9 +50,6 @@ jest.mock('octokit', () => ({ } }, })); -jest.mock('@backstage/catalog-client', () => ({ - CatalogClient: mockCatalogClient, -})); const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU='; @@ -69,9 +64,10 @@ describe('github:environment:create examples', () => { }); const integrations = ScmIntegrations.fromConfig(config); - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext(); + const mockCatalogService = catalogServiceMock.mock(); beforeEach(() => { mockOctokit.rest.actions.getEnvironmentPublicKey.mockResolvedValue({ @@ -95,15 +91,17 @@ describe('github:environment:create examples', () => { id: 2, }, }); - (mockCatalogClient.getEntitiesByRefs as jest.Mock).mockResolvedValue({ + mockCatalogService.getEntitiesByRefs.mockResolvedValue({ items: [ { + apiVersion: 'v1', kind: 'User', metadata: { name: 'johndoe', }, }, { + apiVersion: 'v1', kind: 'Group', metadata: { name: 'team-a', @@ -114,7 +112,7 @@ describe('github:environment:create examples', () => { action = createGithubEnvironmentAction({ integrations, - catalogClient: mockCatalogClient as CatalogApi, + catalog: mockCatalogService, }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts index 6a75bde69f..fc7c3a8276 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts @@ -19,10 +19,10 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test- import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; -import { CatalogApi } from '@backstage/catalog-client'; -import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials } from '@backstage/backend-test-utils'; import { Octokit } from 'octokit'; +import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; const octokitMock = Octokit as unknown as jest.Mock; @@ -47,18 +47,10 @@ const mockOctokit = { }, }; -const mockCatalogClient: Partial = { - getEntitiesByRefs: jest.fn(), -}; - jest.mock('octokit', () => ({ Octokit: jest.fn(), })); -jest.mock('@backstage/catalog-client', () => ({ - CatalogClient: mockCatalogClient, -})); - const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU='; describe('github:environment:create', () => { @@ -72,22 +64,17 @@ describe('github:environment:create', () => { }); const integrations = ScmIntegrations.fromConfig(config); + const mockCatalogService = catalogServiceMock.mock(); const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { repoUrl: 'github.com?repo=repository&owner=owner', name: 'envname', }, - secrets: { backstageToken: token }, }); beforeEach(() => { @@ -114,15 +101,18 @@ describe('github:environment:create', () => { id: 2, }, }); - (mockCatalogClient.getEntitiesByRefs as jest.Mock).mockResolvedValue({ + + mockCatalogService.getEntitiesByRefs.mockResolvedValue({ items: [ { + apiVersion: '1', kind: 'User', metadata: { name: 'johndoe', }, }, { + apiVersion: '1', kind: 'Group', metadata: { name: 'team-a', @@ -133,8 +123,7 @@ describe('github:environment:create', () => { action = createGithubEnvironmentAction({ integrations, - catalogClient: mockCatalogClient as CatalogApi, - auth: mockServices.auth(), + catalog: mockCatalogService, }); }); @@ -496,11 +485,11 @@ describe('github:environment:create', () => { }, }); - expect(mockCatalogClient.getEntitiesByRefs).toHaveBeenCalledWith( + expect(mockCatalogService.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['group:default/team-a', 'user:default/johndoe'], }, - { token }, + { credentials }, ); expect( diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts index 522b6ed8b5..4a36be3b5b 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts @@ -24,9 +24,8 @@ import { getOctokitOptions } from '../util'; import { Octokit } from 'octokit'; import Sodium from 'libsodium-wrappers'; import { examples } from './gitHubEnvironment.examples'; -import { CatalogApi } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; -import { AuthService } from '@backstage/backend-plugin-api'; +import { CatalogService } from '@backstage/plugin-catalog-node'; /** * Creates an `github:environment:create` Scaffolder action that creates a Github Environment. @@ -35,124 +34,100 @@ import { AuthService } from '@backstage/backend-plugin-api'; */ export function createGithubEnvironmentAction(options: { integrations: ScmIntegrationRegistry; - catalogClient?: CatalogApi; - auth?: AuthService; + catalog: CatalogService; }) { - const { integrations, catalogClient, auth } = options; + const { integrations, catalog } = options; // For more information on how to define custom actions, see // https://backstage.io/docs/features/software-templates/writing-custom-actions - return createTemplateAction<{ - repoUrl: string; - name: string; - deploymentBranchPolicy?: { - protected_branches: boolean; - custom_branch_policies: boolean; - }; - customBranchPolicyNames?: string[]; - customTagPolicyNames?: string[]; - environmentVariables?: { [key: string]: string }; - secrets?: { [key: string]: string }; - token?: string; - waitTimer?: number; - preventSelfReview?: boolean; - reviewers?: string[]; - }>({ + return createTemplateAction({ id: 'github:environment:create', description: 'Creates Deployment Environments', examples, schema: { input: { - type: 'object', - required: ['repoUrl', 'name'], - properties: { - repoUrl: { - title: 'Repository Location', + repoUrl: z => + z.string({ description: 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the new repository name and `owner` is an organization or username', - type: 'string', - }, - name: { - title: 'Environment Name', + }), + name: z => + z.string({ description: `Name of the deployment environment to create`, - type: 'string', - }, - deploymentBranchPolicy: { - title: 'Deployment Branch Policy', - description: - 'The type of deployment branch policy for this environment. To allow all branches to deploy, set to `null`.', - type: 'object', - required: ['protected_branches', 'custom_branch_policies'], - properties: { - protected_branches: { - title: 'Protected Branches', - description: - 'Whether only branches with branch protection rules can deploy to this environment. If `protected_branches` is `true`, `custom_branch_policies` must be `false`; if `protected_branches` is `false`, `custom_branch_policies` must be `true`.', - type: 'boolean', + }), + deploymentBranchPolicy: z => + z + .object( + { + protected_branches: z.boolean({ + description: + 'Whether only branches with branch protection rules can deploy to this environment. If `protected_branches` is `true`, `custom_branch_policies` must be `false`; if `protected_branches` is `false`, `custom_branch_policies` must be `true`.', + }), + custom_branch_policies: z.boolean({ + description: + 'Whether only branches that match the specified name patterns can deploy to this environment. If `custom_branch_policies` is `true`, `protected_branches` must be `false`; if `custom_branch_policies` is `false`, `protected_branches` must be `true`.', + }), }, - custom_branch_policies: { - title: 'Custom Branch Policies', + { description: - 'Whether only branches that match the specified name patterns can deploy to this environment. If `custom_branch_policies` is `true`, `protected_branches` must be `false`; if `custom_branch_policies` is `false`, `protected_branches` must be `true`.', - type: 'boolean', + 'The type of deployment branch policy for this environment. To allow all branches to deploy, set to `null`.', }, - }, - }, - customBranchPolicyNames: { - title: 'Custom Branch Policy Name', - description: `The name pattern that branches must match in order to deploy to the environment. + ) + .optional(), + customBranchPolicyNames: z => + z + .array(z.string(), { + description: `The name pattern that branches must match in order to deploy to the environment. Wildcard characters will not match \`/\`. For example, to match branches that begin with \`release/\` and contain an additional single slash, use \`release/*/*\`. For more information about pattern matching syntax, see the Ruby File.fnmatch documentation.`, - type: 'array', - items: { - type: 'string', - }, - }, - customTagPolicyNames: { - title: 'Custom Tag Policy Name', - description: `The name pattern that tags must match in order to deploy to the environment. + }) + .optional(), + customTagPolicyNames: z => + z + .array(z.string(), { + description: `The name pattern that tags must match in order to deploy to the environment. Wildcard characters will not match \`/\`. For example, to match tags that begin with \`release/\` and contain an additional single slash, use \`release/*/*\`. For more information about pattern matching syntax, see the Ruby File.fnmatch documentation.`, - type: 'array', - items: { - type: 'string', - }, - }, - environmentVariables: { - title: 'Environment Variables', - description: `Environment variables attached to the deployment environment`, - type: 'object', - }, - secrets: { - title: 'Deployment Secrets', - description: `Secrets attached to the deployment environment`, - type: 'object', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to GitHub', - }, - waitTimer: { - title: 'Wait Timer', - type: 'integer', - description: - 'The time to wait before creating or updating the environment (in milliseconds)', - }, - preventSelfReview: { - title: 'Prevent Self Review', - type: 'boolean', - description: 'Whether to prevent self-review for this environment', - }, - reviewers: { - title: 'Reviewers', - type: 'array', - description: - 'Reviewers for this environment. Must be a list of Backstage entity references.', - items: { - type: 'string', - }, - }, - }, + }) + .optional(), + environmentVariables: z => + z + .record(z.string(), { + description: `Environment variables attached to the deployment environment`, + }) + .optional(), + secrets: z => + z + .record(z.string(), { + description: `Secrets attached to the deployment environment`, + }) + .optional(), + token: z => + z + .string({ + description: 'The token to use for authorization to GitHub', + }) + .optional(), + waitTimer: z => + z + .number({ + description: + 'The time to wait before creating or updating the environment (in milliseconds)', + }) + .optional(), + preventSelfReview: z => + z + .boolean({ + description: + 'Whether to prevent self-review for this environment', + }) + .optional(), + reviewers: z => + z + .array(z.string(), { + description: + 'Reviewers for this environment. Must be a list of Backstage entity references.', + }) + .optional(), }, }, async handler(ctx) { @@ -170,11 +145,6 @@ Wildcard characters will not match \`/\`. For example, to match tags that begin reviewers, } = ctx.input; - const { token } = (await auth?.getPluginRequestToken({ - onBehalfOf: await ctx.getInitiatorCredentials(), - targetPluginId: 'catalog', - })) ?? { token: ctx.secrets?.backstageToken }; - // When environment creation step is executed right after a repo publish step, the repository might not be available immediately. // Add a 2-second delay before initiating the steps in this action. await new Promise(resolve => setTimeout(resolve, 2000)); @@ -213,12 +183,12 @@ Wildcard characters will not match \`/\`. For example, to match tags that begin if (reviewers) { let reviewersEntityRefs: Array = []; // Fetch reviewers from Catalog - const catalogResponse = await catalogClient?.getEntitiesByRefs( + const catalogResponse = await catalog.getEntitiesByRefs( { entityRefs: reviewers, }, { - token, + credentials: await ctx.getInitiatorCredentials(), }, ); if (catalogResponse?.items?.length) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.examples.test.ts index 4aee57130c..c8a778a069 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.examples.test.ts @@ -61,7 +61,7 @@ describe('github:issues:label examples', () => { const getOctokitOptionsMock = getOctokitOptions as jest.Mock; const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext(); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.test.ts index b1e1c6e87c..d7da860227 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.test.ts @@ -58,7 +58,7 @@ describe('github:issues:label', () => { const getOctokitOptionsMock = getOctokitOptions as jest.Mock; const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.ts index f91bd01a73..bbda5a1839 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.ts @@ -37,46 +37,32 @@ export function createGithubIssuesLabelAction(options: { }) { const { integrations, githubCredentialsProvider } = options; - return createTemplateAction<{ - repoUrl: string; - number: number; - labels: string[]; - token?: string; - }>({ + return createTemplateAction({ id: 'github:issues:label', description: 'Adds labels to a pull request or issue on GitHub.', examples, schema: { input: { - type: 'object', - required: ['repoUrl', 'number', 'labels'], - properties: { - repoUrl: { - title: 'Repository Location', + repoUrl: z => + z.string({ description: 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the repository name and `owner` is an organization or username', - type: 'string', - }, - number: { - title: 'Pull Request or issue number', + }), + number: z => + z.number({ description: 'The pull request or issue number to add labels to', - type: 'number', - }, - labels: { - title: 'Labels', + }), + labels: z => + z.array(z.string(), { description: 'The labels to add to the pull request or issue', - type: 'array', - items: { - type: 'string', - }, - }, - token: { - title: 'Authentication Token', - type: 'string', - description: - 'The `GITHUB_TOKEN` to use for authorization to GitHub', - }, - }, + }), + token: z => + z + .string({ + description: + 'The `GITHUB_TOKEN` to use for authorization to GitHub', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts index f0aa0ea5a2..38f7bd8de0 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts @@ -50,7 +50,7 @@ describe('github:pages', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.test.ts index 23e3077861..e81dfa14fd 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.test.ts @@ -48,7 +48,7 @@ describe('github:pages', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.ts index 01830a252d..21cde027ec 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.ts @@ -38,55 +38,46 @@ export function createGithubPagesEnableAction(options: { }) { const { integrations, githubCredentialsProvider } = options; - return createTemplateAction<{ - repoUrl: string; - buildType?: 'legacy' | 'workflow'; - sourceBranch?: string; - sourcePath?: '/' | '/docs'; - token?: string; - }>({ + return createTemplateAction({ id: 'github:pages:enable', examples, description: 'Enables GitHub Pages for a repository.', schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: { - title: 'Repository Location', + repoUrl: z => + z.string({ description: 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the new repository name and `owner` is an organization or username', - type: 'string', - }, - buildType: { - title: 'Build Type', - type: 'string', - default: 'workflow', - description: - 'The GitHub Pages build type - `legacy` or `workflow`. Default is `workflow`', - enum: ['legacy', 'workflow'], - }, - sourceBranch: { - title: 'Source Branch', - type: 'string', - default: 'main', - description: 'The GitHub Pages source branch. Default is "main"', - }, - sourcePath: { - title: 'Source Path', - type: 'string', - default: '/', - description: - 'The GitHub Pages source path - "/" or "/docs". Default is "/"', - enum: ['/', '/docs'], - }, - token: { - title: 'Authorization Token', - type: 'string', - description: 'The token to use for authorization to GitHub', - }, - }, + }), + buildType: z => + z + .enum(['legacy', 'workflow'], { + description: + 'The GitHub Pages build type - `legacy` or `workflow`. Default is `workflow`', + }) + .default('workflow') + .optional(), + sourceBranch: z => + z + .string({ + description: 'The GitHub Pages source branch. Default is "main"', + }) + .default('main') + .optional(), + sourcePath: z => + z + .enum(['/', '/docs'], { + description: + 'The GitHub Pages source path - "/" or "/docs". Default is "/"', + }) + .default('/') + .optional(), + token: z => + z + .string({ + description: 'The token to use for authorization to GitHub', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts index bbbbed6511..27547aa80d 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts @@ -57,7 +57,7 @@ describe('publish:github:pull-request examples', () => { }); const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext(); let fakeClient: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts index 44e4cb17b3..670af15595 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts @@ -30,12 +30,12 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test- type GithubPullRequestActionInput = ReturnType< typeof createPublishGithubPullRequestAction -> extends TemplateAction +> extends TemplateAction ? U : never; describe('createPublishGithubPullRequestAction', () => { - let instance: TemplateAction; + let instance: TemplateAction; let fakeClient: { createPullRequest: jest.Mock; rest: { @@ -97,7 +97,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with targetBranchName', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { fakeClient = { @@ -181,7 +181,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with no sourcePath', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -250,7 +250,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with sourcePath', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -306,7 +306,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with repoUrl', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -359,7 +359,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with reviewers and teamReviewers', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -406,7 +406,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with no reviewers and teamReviewers', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -431,7 +431,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with assignees', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -475,7 +475,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with broken symlink', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -520,7 +520,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with executable file mode 755', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -579,7 +579,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with executable file mode 775', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -638,7 +638,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with commit message', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -683,7 +683,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with force fork', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -729,7 +729,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author name and email', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -779,7 +779,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author name', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -828,7 +828,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author email', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -877,7 +877,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author from config file', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -946,7 +946,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author attributes and config file', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -1017,7 +1017,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author fallback and no config', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -1112,7 +1112,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with createWhenEmpty equals true', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -1175,7 +1175,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with createWhenEmpty equals false', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { fakeClient.createPullRequest.mockResolvedValueOnce(null); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index adf80c0e0a..54b90e3544 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -128,169 +128,141 @@ export const createPublishGithubPullRequestAction = ( config, } = options; - return createTemplateAction<{ - title: string; - branchName: string; - targetBranchName?: string; - description: string; - repoUrl: string; - draft?: boolean; - targetPath?: string; - sourcePath?: string; - token?: string; - reviewers?: string[]; - assignees?: string[]; - teamReviewers?: string[]; - commitMessage?: string; - update?: boolean; - forceFork?: boolean; - gitAuthorName?: string; - gitAuthorEmail?: string; - forceEmptyGitAuthor?: boolean; - createWhenEmpty?: boolean; - }>({ + return createTemplateAction({ id: 'publish:github:pull-request', examples, supportsDryRun: true, schema: { input: { - required: ['repoUrl', 'title', 'description', 'branchName'], - type: 'object', - properties: { - repoUrl: { - title: 'Repository Location', + repoUrl: z => + z.string({ description: 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the repository name and `owner` is an organization or username', - type: 'string', - }, - branchName: { - type: 'string', - title: 'Branch Name', + }), + branchName: z => + z.string({ description: 'The name for the branch', - }, - targetBranchName: { - type: 'string', - title: 'Target Branch Name', - description: 'The target branch name of the pull request', - }, - title: { - type: 'string', - title: 'Pull Request Name', + }), + targetBranchName: z => + z + .string({ + description: 'The target branch name of the pull request', + }) + .optional(), + title: z => + z.string({ description: 'The name for the pull request', - }, - description: { - type: 'string', - title: 'Pull Request Description', + }), + description: z => + z.string({ description: 'The description of the pull request', - }, - draft: { - type: 'boolean', - title: 'Create as Draft', - description: 'Create a draft pull request', - }, - sourcePath: { - type: 'string', - title: 'Working Subdirectory', - description: - 'Subdirectory of working directory to copy changes from', - }, - targetPath: { - type: 'string', - title: 'Repository Subdirectory', - description: 'Subdirectory of repository to apply changes to', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to GitHub', - }, - reviewers: { - title: 'Pull Request Reviewers', - type: 'array', - items: { - type: 'string', - }, - description: - 'The users that will be added as reviewers to the pull request', - }, - assignees: { - title: 'Pull Request Assignees', - type: 'array', - items: { - type: 'string', - }, - description: - 'The users that will be added as assignees to the pull request', - }, - teamReviewers: { - title: 'Pull Request Team Reviewers', - type: 'array', - items: { - type: 'string', - }, - description: - 'The teams that will be added as reviewers to the pull request', - }, - commitMessage: { - type: 'string', - title: 'Commit Message', - description: 'The commit message for the pull request commit', - }, - update: { - type: 'boolean', - title: 'Update', - description: 'Update pull request if already exists', - }, - forceFork: { - type: 'boolean', - title: 'Force Fork', - description: 'Create pull request from a fork', - }, - gitAuthorName: { - type: 'string', - title: 'Default Author Name', - description: - 'Sets the default author name for the commit. The default value is the authenticated user or `Scaffolder`', - }, - gitAuthorEmail: { - type: 'string', - title: 'Default Author Email', - description: - 'Sets the default author email for the commit. The default value is the authenticated user or `scaffolder@backstage.io`', - }, - forceEmptyGitAuthor: { - type: 'boolean', - title: 'Force Empty Git Author', - description: - 'Forces the author to be empty. This is useful when using a Github App, it permit the commit to be verified on Github', - }, - createWhenEmpty: { - type: 'boolean', - title: 'Create When Empty', - description: - 'Set whether to create pull request when there are no changes to commit. The default value is true. If set to false, remoteUrl is no longer a required output.', - }, - }, + }), + draft: z => + z + .boolean({ + description: 'Create a draft pull request', + }) + .optional(), + sourcePath: z => + z + .string({ + description: + 'Subdirectory of working directory to copy changes from', + }) + .optional(), + targetPath: z => + z + .string({ + description: 'Subdirectory of repository to apply changes to', + }) + .optional(), + token: z => + z + .string({ + description: 'The token to use for authorization to GitHub', + }) + .optional(), + reviewers: z => + z + .array(z.string(), { + description: + 'The users that will be added as reviewers to the pull request', + }) + .optional(), + assignees: z => + z + .array(z.string(), { + description: + 'The users that will be added as assignees to the pull request', + }) + .optional(), + teamReviewers: z => + z + .array(z.string(), { + description: + 'The teams that will be added as reviewers to the pull request', + }) + .optional(), + commitMessage: z => + z + .string({ + description: 'The commit message for the pull request commit', + }) + .optional(), + update: z => + z + .boolean({ + description: 'Update pull request if already exists', + }) + .optional(), + forceFork: z => + z + .boolean({ + description: 'Create pull request from a fork', + }) + .optional(), + gitAuthorName: z => + z + .string({ + description: + 'Sets the default author name for the commit. The default value is the authenticated user or `Scaffolder`', + }) + .optional(), + gitAuthorEmail: z => + z + .string({ + description: + 'Sets the default author email for the commit. The default value is the authenticated user or `scaffolder@backstage.io`', + }) + .optional(), + forceEmptyGitAuthor: z => + z + .boolean({ + description: + 'Forces the author to be empty. This is useful when using a Github App, it permit the commit to be verified on Github', + }) + .optional(), + createWhenEmpty: z => + z + .boolean({ + description: + 'Set whether to create pull request when there are no changes to commit. The default value is true. If set to false, remoteUrl is no longer a required output.', + }) + .optional(), }, output: { - required: [], - type: 'object', - properties: { - targetBranchName: { - title: 'Target branch name of the merge request', - type: 'string', - }, - remoteUrl: { - type: 'string', - title: 'Pull Request URL', + targetBranchName: z => + z.string({ + description: 'Target branch name of the merge request', + }), + remoteUrl: z => + z.string({ description: 'Link to the pull request in Github', - }, - pullRequestNumber: { - type: 'number', - title: 'Pull Request Number', + }), + pullRequestNumber: z => + z.number({ description: 'The pull request number', - }, - }, + }), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts index 8694011133..da4837adb7 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts @@ -80,7 +80,7 @@ describe('github:repo:create examples', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts index 6f901d1d84..cc8f65a5ba 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts @@ -80,7 +80,7 @@ describe('github:repo:create', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts index 442af4057c..a85010797d 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts @@ -41,118 +41,50 @@ export function createGithubRepoCreateAction(options: { }) { const { integrations, githubCredentialsProvider } = options; - return createTemplateAction<{ - repoUrl: string; - description?: string; - homepage?: string; - access?: string; - deleteBranchOnMerge?: boolean; - gitAuthorName?: string; - gitAuthorEmail?: string; - allowRebaseMerge?: boolean; - allowSquashMerge?: boolean; - squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE'; - squashMergeCommitMessage?: 'PR_BODY' | 'COMMIT_MESSAGES' | 'BLANK'; - allowMergeCommit?: boolean; - allowAutoMerge?: boolean; - allowUpdateBranch?: boolean; - requireCodeOwnerReviews?: boolean; - bypassPullRequestAllowances?: { - users?: string[]; - teams?: string[]; - apps?: string[]; - }; - requiredApprovingReviewCount?: number; - restrictions?: { - users: string[]; - teams: string[]; - apps?: string[]; - }; - requiredStatusCheckContexts?: string[]; - requireBranchesToBeUpToDate?: boolean; - requiredConversationResolution?: boolean; - repoVisibility?: 'private' | 'internal' | 'public'; - collaborators?: Array< - | { - user: string; - access: string; - } - | { - team: string; - access: string; - } - | { - /** @deprecated This field is deprecated in favor of team */ - username: string; - access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; - } - >; - hasProjects?: boolean; - hasWiki?: boolean; - hasIssues?: boolean; - token?: string; - topics?: string[]; - repoVariables?: { [key: string]: string }; - secrets?: { [key: string]: string }; - oidcCustomization?: { - useDefault: boolean; - includeClaimKeys?: string[]; - }; - requireCommitSigning?: boolean; - requiredLinearHistory?: boolean; - customProperties?: { [key: string]: string }; - subscribe?: boolean; - }>({ + return createTemplateAction({ id: 'github:repo:create', description: 'Creates a GitHub repository.', examples, schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: inputProps.repoUrl, - description: inputProps.description, - homepage: inputProps.homepage, - access: inputProps.access, - requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, - bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, - requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, - restrictions: inputProps.restrictions, - requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, - requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, - requiredConversationResolution: - inputProps.requiredConversationResolution, - repoVisibility: inputProps.repoVisibility, - deleteBranchOnMerge: inputProps.deleteBranchOnMerge, - allowMergeCommit: inputProps.allowMergeCommit, - allowSquashMerge: inputProps.allowSquashMerge, - squashMergeCommitTitle: inputProps.squashMergeCommitTitle, - squashMergeCommitMessage: inputProps.squashMergeCommitMessage, - allowRebaseMerge: inputProps.allowRebaseMerge, - allowAutoMerge: inputProps.allowAutoMerge, - allowUpdateBranch: inputProps.allowUpdateBranch, - collaborators: inputProps.collaborators, - hasProjects: inputProps.hasProjects, - hasWiki: inputProps.hasWiki, - hasIssues: inputProps.hasIssues, - token: inputProps.token, - topics: inputProps.topics, - repoVariables: inputProps.repoVariables, - secrets: inputProps.secrets, - oidcCustomization: inputProps.oidcCustomization, - requiredCommitSigning: inputProps.requiredCommitSigning, - requiredLinearHistory: inputProps.requiredLinearHistory, - customProperties: inputProps.customProperties, - subscribe: inputProps.subscribe, - }, + repoUrl: inputProps.repoUrl, + description: inputProps.description, + homepage: inputProps.homepage, + access: inputProps.access, + requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, + bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, + requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, + restrictions: inputProps.restrictions, + requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, + requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, + requiredConversationResolution: + inputProps.requiredConversationResolution, + repoVisibility: inputProps.repoVisibility, + deleteBranchOnMerge: inputProps.deleteBranchOnMerge, + allowMergeCommit: inputProps.allowMergeCommit, + allowSquashMerge: inputProps.allowSquashMerge, + squashMergeCommitTitle: inputProps.squashMergeCommitTitle, + squashMergeCommitMessage: inputProps.squashMergeCommitMessage, + allowRebaseMerge: inputProps.allowRebaseMerge, + allowAutoMerge: inputProps.allowAutoMerge, + allowUpdateBranch: inputProps.allowUpdateBranch, + collaborators: inputProps.collaborators, + hasProjects: inputProps.hasProjects, + hasWiki: inputProps.hasWiki, + hasIssues: inputProps.hasIssues, + token: inputProps.token, + topics: inputProps.topics, + repoVariables: inputProps.repoVariables, + secrets: inputProps.secrets, + oidcCustomization: inputProps.oidcCustomization, + requiredCommitSigning: inputProps.requiredCommitSigning, + requiredLinearHistory: inputProps.requiredLinearHistory, + customProperties: inputProps.customProperties, + subscribe: inputProps.subscribe, }, output: { - type: 'object', - properties: { - remoteUrl: outputProps.remoteUrl, - repoContentsUrl: outputProps.repoContentsUrl, - }, + remoteUrl: outputProps.remoteUrl, + repoContentsUrl: outputProps.repoContentsUrl, }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.test.ts index 6570ce8f4b..a581e751b4 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.test.ts @@ -79,7 +79,7 @@ describe('github:repo:push examples', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext(); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts index 7ef6e2d793..405ef32f40 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts @@ -78,7 +78,7 @@ describe('github:repo:push', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts index 990f3ff7ca..77aabe6b84 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts @@ -44,80 +44,39 @@ export function createGithubRepoPushAction(options: { }) { const { integrations, config, githubCredentialsProvider } = options; - return createTemplateAction<{ - repoUrl: string; - description?: string; - defaultBranch?: string; - protectDefaultBranch?: boolean; - protectEnforceAdmins?: boolean; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - requireCodeOwnerReviews?: boolean; - dismissStaleReviews?: boolean; - bypassPullRequestAllowances?: - | { - users?: string[]; - teams?: string[]; - apps?: string[]; - } - | undefined; - requiredApprovingReviewCount?: number; - restrictions?: - | { - users: string[]; - teams: string[]; - apps?: string[]; - } - | undefined; - requiredStatusCheckContexts?: string[]; - requireBranchesToBeUpToDate?: boolean; - requiredConversationResolution?: boolean; - sourcePath?: string; - token?: string; - requiredCommitSigning?: boolean; - requiredLinearHistory?: boolean; - requireLastPushApproval?: boolean; - }>({ + return createTemplateAction({ id: 'github:repo:push', description: 'Initializes a git repository of contents in workspace and publishes it to GitHub.', examples, schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: inputProps.repoUrl, - requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, - dismissStaleReviews: inputProps.dismissStaleReviews, - requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, - bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, - requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, - restrictions: inputProps.restrictions, - requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, - requiredConversationResolution: - inputProps.requiredConversationResolution, - requireLastPushApproval: inputProps.requireLastPushApproval, - defaultBranch: inputProps.defaultBranch, - protectDefaultBranch: inputProps.protectDefaultBranch, - protectEnforceAdmins: inputProps.protectEnforceAdmins, - gitCommitMessage: inputProps.gitCommitMessage, - gitAuthorName: inputProps.gitAuthorName, - gitAuthorEmail: inputProps.gitAuthorEmail, - sourcePath: inputProps.sourcePath, - token: inputProps.token, - requiredCommitSigning: inputProps.requiredCommitSigning, - requiredLinearHistory: inputProps.requiredLinearHistory, - }, + repoUrl: inputProps.repoUrl, + requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, + dismissStaleReviews: inputProps.dismissStaleReviews, + requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, + bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, + requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, + restrictions: inputProps.restrictions, + requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, + requiredConversationResolution: + inputProps.requiredConversationResolution, + requireLastPushApproval: inputProps.requireLastPushApproval, + defaultBranch: inputProps.defaultBranch, + protectDefaultBranch: inputProps.protectDefaultBranch, + protectEnforceAdmins: inputProps.protectEnforceAdmins, + gitCommitMessage: inputProps.gitCommitMessage, + gitAuthorName: inputProps.gitAuthorName, + gitAuthorEmail: inputProps.gitAuthorEmail, + sourcePath: inputProps.sourcePath, + token: inputProps.token, + requiredCommitSigning: inputProps.requiredCommitSigning, + requiredLinearHistory: inputProps.requiredLinearHistory, }, output: { - type: 'object', - properties: { - remoteUrl: outputProps.remoteUrl, - repoContentsUrl: outputProps.repoContentsUrl, - commitHash: outputProps.commitHash, - }, + remoteUrl: outputProps.remoteUrl, + repoContentsUrl: outputProps.repoContentsUrl, + commitHash: outputProps.commitHash, }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.examples.test.ts index d58d737f93..b76405364b 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.examples.test.ts @@ -53,7 +53,7 @@ describe('github:webhook examples', () => { const defaultWebhookSecret = 'aafdfdivierernfdk23f'; const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext(); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts index a42d251c49..b005d5aa76 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts @@ -51,7 +51,7 @@ describe('github:repository:webhook:create', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; const defaultWebhookSecret = 'aafdfdivierernfdk23f'; - let action: TemplateAction; + let action: TemplateAction; beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts index 7345f7dd9f..3faab03f13 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts @@ -42,92 +42,74 @@ export function createGithubWebhookAction(options: { const eventNames = emitterEventNames.filter(event => !event.includes('.')); - return createTemplateAction<{ - repoUrl: string; - webhookUrl: string; - webhookSecret?: string; - events?: string[]; - active?: boolean; - contentType?: 'form' | 'json'; - insecureSsl?: boolean; - token?: string; - }>({ + return createTemplateAction({ id: 'github:webhook', description: 'Creates webhook for a repository on GitHub.', examples, supportsDryRun: true, schema: { input: { - type: 'object', - required: ['repoUrl', 'webhookUrl'], - properties: { - repoUrl: { - title: 'Repository Location', + repoUrl: z => + z.string({ description: 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the new repository name and `owner` is an organization or username', - type: 'string', - }, - webhookUrl: { - title: 'Webhook URL', + }), + webhookUrl: z => + z.string({ description: 'The URL to which the payloads will be delivered', - type: 'string', - }, - webhookSecret: { - title: 'Webhook Secret', - description: - 'Webhook secret value. The default can be provided internally in action creation', - type: 'string', - }, - events: { - title: 'Triggering Events', - description: - 'Determines what events the hook is triggered for. Default: `[push]`', - type: 'array', - default: ['push'], - oneOf: [ - { - items: { - type: 'string', - enum: eventNames, - }, - }, - { - items: { - type: 'string', - const: '*', - }, - }, - ], - }, - active: { - title: 'Active', - type: 'boolean', - default: true, - description: - 'Determines if notifications are sent when the webhook is triggered. Default: `true`', - }, - contentType: { - title: 'Content Type', - type: 'string', - enum: ['form', 'json'], - default: 'form', - description: - 'The media type used to serialize the payloads. The default is `form`', - }, - insecureSsl: { - title: 'Insecure SSL', - type: 'boolean', - default: false, - description: - 'Determines whether the SSL certificate of the host for url will be verified when delivering payloads. Default `false`', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: - 'The `GITHUB_TOKEN` to use for authorization to GitHub', - }, - }, + }), + webhookSecret: z => + z + .string({ + description: + 'Webhook secret value. The default can be provided internally in action creation', + }) + .optional(), + events: z => + z + .union([ + z.array(z.enum(eventNames as [string, ...string[]]), { + description: + 'Determines what events the hook is triggered for. Default: `[push]`', + }), + z.array(z.literal('*'), { + description: + 'Determines what events the hook is triggered for. Use "*" for all events. Default: `[push]`', + }), + ]) + .default(['push']) + .optional(), + active: z => + z + .boolean({ + description: + 'Determines if notifications are sent when the webhook is triggered. Default: `true`', + }) + .default(true) + .optional(), + contentType: z => + z + .enum(['form', 'json'], { + description: + 'The media type used to serialize the payloads. The default is `form`', + }) + .default('form') + .optional(), + insecureSsl: z => + z + .boolean({ + description: + 'Determines whether the SSL certificate of the host for url will be verified when delivering payloads. Default `false`', + }) + .default(false) + .optional(), + token: z => + z + .string({ + description: + 'The `GITHUB_TOKEN` to use for authorization to GitHub', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts index cac93ec7c6..7b58de4a5a 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts @@ -13,395 +13,440 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { z as zod } from 'zod'; -const repoUrl = { - title: 'Repository Location', - description: - 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the new repository name and `owner` is an organization or username', - type: 'string', -}; -const description = { - title: 'Repository Description', - type: 'string', -}; -const homepage = { - title: 'Repository Homepage', - type: 'string', -}; -const access = { - title: 'Repository Access', - description: - 'Sets an admin collaborator on the repository. Can either be a user reference different from `owner` in `repoUrl` or team reference, eg. `org/team-name`', - type: 'string', -}; -const requireCodeOwnerReviews = { - title: 'Require CODEOWNER Reviews?', - description: - 'Require an approved review in PR including files with a designated Code Owner', - type: 'boolean', -}; -const dismissStaleReviews = { - title: 'Dismiss Stale Reviews', - description: - 'New reviewable commits pushed to a matching branch will dismiss pull request review approvals.', - type: 'boolean', -}; -const requiredStatusCheckContexts = { - title: 'Required Status Check Contexts', - description: - 'The list of status checks to require in order to merge into this branch', - type: 'array', - items: { - type: 'string', - }, -}; -const requireBranchesToBeUpToDate = { - title: 'Require Branches To Be Up To Date?', - description: - 'Require branches to be up to date before merging. The default value is `true`', - type: 'boolean', - default: true, -}; -const requiredConversationResolution = { - title: 'Required Conversation Resolution', - description: - 'Requires all conversations on code to be resolved before a pull request can be merged into this branch', - type: 'boolean', -}; -const requireLastPushApproval = { - title: 'Require last push approval', - type: 'boolean', - default: false, - description: - 'Whether the most recent push to a PR must be approved by someone other than the person who pushed it. The default value is `false`', -}; -const repoVisibility = { - title: 'Repository Visibility', - type: 'string', - enum: ['private', 'public', 'internal'], -}; -const deleteBranchOnMerge = { - title: 'Delete Branch On Merge', - type: 'boolean', - default: false, - description: - 'Delete the branch after merging the PR. The default value is `false`', -}; -const gitAuthorName = { - title: 'Default Author Name', - type: 'string', - default: 'Scaffolder', - description: - 'Sets the default author name for the commit. The default value is `Scaffolder`', -}; -const gitAuthorEmail = { - title: 'Default Author Email', - type: 'string', - description: `Sets the default author email for the commit.`, -}; -const allowMergeCommit = { - title: 'Allow Merge Commits', - type: 'boolean', - default: true, - description: 'Allow merge commits. The default value is `true`', -}; -const allowSquashMerge = { - title: 'Allow Squash Merges', - type: 'boolean', - default: true, - description: 'Allow squash merges. The default value is `true`', -}; -const allowUpdateBranch = { - title: 'Allow Update Branch', - type: 'boolean', - default: false, - description: 'Allow branch to be updated. The default value is `false`', -}; -const squashMergeCommitTitle = { - title: 'Default squash merge commit title', - enum: ['PR_TITLE', 'COMMIT_OR_PR_TITLE'], - type: 'string', - default: 'COMMIT_OR_PR_TITLE', - description: - 'Sets the default value for a squash merge commit title. The default value is `COMMIT_OR_PR_TITLE`', -}; -const squashMergeCommitMessage = { - title: 'Default squash merge commit message', - enum: ['PR_BODY', 'COMMIT_MESSAGES', 'BLANK'], - type: 'string', - default: 'COMMIT_MESSAGES', - description: - 'Sets the default value for a squash merge commit message. The default value is `COMMIT_MESSAGES`', -}; +const repoUrl = (z: typeof zod) => + z.string({ + description: + 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the new repository name and `owner` is an organization or username', + }); -const allowRebaseMerge = { - title: 'Allow Rebase Merges', - type: 'boolean', - default: true, - description: 'Allow rebase merges. The default value is `true`', -}; -const allowAutoMerge = { - title: 'Allow Auto Merges', - type: 'boolean', - default: false, - description: - 'Allow individual PRs to merge automatically when all merge requirements are met. The default value is `false`', -}; -const collaborators = { - title: 'Collaborators', - description: 'Provide additional users or teams with permissions', - type: 'array', - items: { - type: 'object', - additionalProperties: false, - required: ['access'], - properties: { - access: { - type: 'string', - description: 'The type of access for the user', +const description = (z: typeof zod) => + z + .string({ + description: 'Repository Description', + }) + .optional(); + +const homepage = (z: typeof zod) => + z + .string({ + description: 'Repository Homepage', + }) + .optional(); + +const access = (z: typeof zod) => + z + .string({ + description: + 'Sets an admin collaborator on the repository. Can either be a user reference different from `owner` in `repoUrl` or team reference, eg. `org/team-name`', + }) + .optional(); + +const requireCodeOwnerReviews = (z: typeof zod) => + z + .boolean({ + description: + 'Require an approved review in PR including files with a designated Code Owner', + }) + .optional(); + +const dismissStaleReviews = (z: typeof zod) => + z + .boolean({ + description: + 'New reviewable commits pushed to a matching branch will dismiss pull request review approvals.', + }) + .optional(); + +const requiredStatusCheckContexts = (z: typeof zod) => + z + .array(z.string(), { + description: + 'The list of status checks to require in order to merge into this branch', + }) + .optional(); + +const requireBranchesToBeUpToDate = (z: typeof zod) => + z + .boolean({ + description: + 'Require branches to be up to date before merging. The default value is `true`', + }) + .default(true) + .optional(); + +const requiredConversationResolution = (z: typeof zod) => + z + .boolean({ + description: + 'Requires all conversations on code to be resolved before a pull request can be merged into this branch', + }) + .optional(); + +const requireLastPushApproval = (z: typeof zod) => + z + .boolean({ + description: + 'Whether the most recent push to a PR must be approved by someone other than the person who pushed it. The default value is `false`', + }) + .default(false) + .optional(); + +const repoVisibility = (z: typeof zod) => + z + .enum(['private', 'public', 'internal'], { + description: 'Repository Visibility', + }) + .optional(); + +const deleteBranchOnMerge = (z: typeof zod) => + z + .boolean({ + description: + 'Delete the branch after merging the PR. The default value is `false`', + }) + .default(false) + .optional(); + +const gitAuthorName = (z: typeof zod) => + z + .string({ + description: + 'Sets the default author name for the commit. The default value is `Scaffolder`', + }) + .default('Scaffolder') + .optional(); + +const gitAuthorEmail = (z: typeof zod) => + z + .string({ + description: `Sets the default author email for the commit.`, + }) + .optional(); + +const allowMergeCommit = (z: typeof zod) => + z + .boolean({ + description: 'Allow merge commits. The default value is `true`', + }) + .default(true) + .optional(); + +const allowSquashMerge = (z: typeof zod) => + z + .boolean({ + description: 'Allow squash merges. The default value is `true`', + }) + .default(true) + .optional(); + +const allowUpdateBranch = (z: typeof zod) => + z + .boolean({ + description: 'Allow branch to be updated. The default value is `false`', + }) + .default(false) + .optional(); + +const squashMergeCommitTitle = (z: typeof zod) => + z + .enum(['PR_TITLE', 'COMMIT_OR_PR_TITLE'], { + description: + 'Sets the default value for a squash merge commit title. The default value is `COMMIT_OR_PR_TITLE`', + }) + .default('COMMIT_OR_PR_TITLE') + .optional(); + +const squashMergeCommitMessage = (z: typeof zod) => + z + .enum(['PR_BODY', 'COMMIT_MESSAGES', 'BLANK'], { + description: + 'Sets the default value for a squash merge commit message. The default value is `COMMIT_MESSAGES`', + }) + .default('COMMIT_MESSAGES') + .optional(); + +const allowRebaseMerge = (z: typeof zod) => + z + .boolean({ + description: 'Allow rebase merges. The default value is `true`', + }) + .default(true) + .optional(); + +const allowAutoMerge = (z: typeof zod) => + z + .boolean({ + description: + 'Allow individual PRs to merge automatically when all merge requirements are met. The default value is `false`', + }) + .default(false) + .optional(); + +const collaborators = (z: typeof zod) => + z + .array( + z.union([ + z.object({ + access: z.string({ + description: 'The type of access for the user', + }), + user: z.string({ + description: + 'The name of the user that will be added as a collaborator', + }), + }), + z.object({ + access: z.string({ + description: 'The type of access for the team', + }), + team: z.string({ + description: + 'The name of the team that will be added as a collaborator', + }), + }), + ]), + { + description: 'Provide additional users or teams with permissions', }, - user: { - type: 'string', + ) + .optional(); + +const hasProjects = (z: typeof zod) => + z + .boolean({ + description: + 'Enable projects for the repository. The default value is `true` unless the organization has disabled repository projects', + }) + .optional(); + +const hasWiki = (z: typeof zod) => + z + .boolean({ + description: + 'Enable the wiki for the repository. The default value is `true`', + }) + .default(true) + .optional(); + +const hasIssues = (z: typeof zod) => + z + .boolean({ + description: + 'Enable issues for the repository. The default value is `true`', + }) + .default(true) + .optional(); + +const token = (z: typeof zod) => + z + .string({ + description: 'The token to use for authorization to GitHub', + }) + .optional(); + +const topics = (z: typeof zod) => + z + .array(z.string(), { + description: 'Adds topics to the repository', + }) + .optional(); + +const defaultBranch = (z: typeof zod) => + z + .string({ + description: `Sets the default branch on the repository. The default value is 'master'`, + }) + .default('master') + .optional(); + +const protectDefaultBranch = (z: typeof zod) => + z + .boolean({ + description: + 'Protect the default branch after creating the repository. The default value is `true`', + }) + .default(true) + .optional(); + +const protectEnforceAdmins = (z: typeof zod) => + z + .boolean({ + description: + 'Enforce admins to adhere to default branch protection. The default value is `true`', + }) + .default(true) + .optional(); + +const bypassPullRequestAllowances = (z: typeof zod) => + z + .object( + { + apps: z.array(z.string()).optional(), + users: z.array(z.string()).optional(), + teams: z.array(z.string()).optional(), + }, + { description: - 'The name of the user that will be added as a collaborator', + 'Allow specific users, teams, or apps to bypass pull request requirements.', }, - team: { - type: 'string', + ) + .optional(); + +const gitCommitMessage = (z: typeof zod) => + z + .string({ + description: + 'Sets the commit message on the repository. The default value is `initial commit`', + }) + .default('initial commit') + .optional(); + +const sourcePath = (z: typeof zod) => + z + .string({ + description: + 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', + }) + .optional(); + +const requiredApprovingReviewCount = (z: typeof zod) => + z + .number({ + description: + 'Specify the number of reviewers required to approve pull requests. Use a number between `1` and `6` or `0` to not require reviewers. Defaults to `1`.', + }) + .optional(); + +const restrictions = (z: typeof zod) => + z + .object( + { + apps: z.array(z.string()).optional(), + users: z.array(z.string()), + teams: z.array(z.string()), + }, + { description: - 'The name of the team that will be added as a collaborator', + 'Restrict who can push to the protected branch. User, app, and team restrictions are only available for organization-owned repositories.', }, - }, - oneOf: [{ required: ['user'] }, { required: ['team'] }], - }, -}; -const hasProjects = { - title: 'Enable projects', - type: 'boolean', - description: - 'Enable projects for the repository. The default value is `true` unless the organization has disabled repository projects', -}; -const hasWiki = { - title: 'Enable the wiki', - type: 'boolean', - default: true, - description: - 'Enable the wiki for the repository. The default value is `true`', -}; -const hasIssues = { - title: 'Enable issues', - type: 'boolean', - default: true, - description: 'Enable issues for the repository. The default value is `true`', -}; -const token = { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to GitHub', -}; -const topics = { - title: 'Topics', - type: 'array', - items: { - type: 'string', - }, -}; -const defaultBranch = { - title: 'Default Branch', - type: 'string', - default: 'master', - description: - 'Sets the default branch on the repository. The default value is `master`', -}; -const protectDefaultBranch = { - title: 'Protect Default Branch', - type: 'boolean', - default: true, - description: - 'Protect the default branch after creating the repository. The default value is `true`', -}; -const protectEnforceAdmins = { - title: 'Enforce Admins On Protected Branches', - type: 'boolean', - default: true, - description: - 'Enforce admins to adhere to default branch protection. The default value is `true`', -}; + ) + .optional(); -const bypassPullRequestAllowances = { - title: 'Bypass pull request requirements', - description: - 'Allow specific users, teams, or apps to bypass pull request requirements.', - type: 'object', - additionalProperties: false, - properties: { - apps: { - type: 'array', - items: { - type: 'string', +const requiredCommitSigning = (z: typeof zod) => + z + .boolean({ + description: `Require commit signing so that you must sign commits on this branch.`, + }) + .optional(); + +const requiredLinearHistory = (z: typeof zod) => + z + .boolean({ + description: `Prevent merge commits from being pushed to matching branches.`, + }) + .optional(); + +const repoVariables = (z: typeof zod) => + z + .record(z.string(), { + description: 'Variables attached to the repository', + }) + .optional(); + +const secrets = (z: typeof zod) => + z + .record(z.string(), { + description: 'Secrets attached to the repository', + }) + .optional(); + +const oidcCustomization = (z: typeof zod) => + z + .object( + { + useDefault: z + .boolean({ + description: `Whether to use the default template or not. If true, includeClaimKeys must not be set.`, + }) + .default(true), + includeClaimKeys: z + .array(z.string(), { + description: `Array of unique strings. Each claim key can only contain alphanumeric characters and underscores.`, + }) + .optional(), }, - }, - users: { - type: 'array', - items: { - type: 'string', + { + description: `OIDC customization template attached to the repository.`, }, - }, - teams: { - type: 'array', - items: { - type: 'string', - }, - }, - }, -}; + ) + .optional(); -const gitCommitMessage = { - title: 'Git Commit Message', - type: 'string', - default: 'initial commit', - description: - 'Sets the commit message on the repository. The default value is `initial commit`', -}; -const sourcePath = { - title: 'Source Path', - description: - 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', - type: 'string', -}; +const customProperties = (z: typeof zod) => + z + .record(z.string(), { + description: + 'Custom properties to be added to the repository (note, this only works for organization repositories)', + }) + .optional(); -const requiredApprovingReviewCount = { - title: 'Required approving review count', - type: 'number', - description: - 'Specify the number of reviewers required to approve pull requests. Use a number between `1` and `6` or `0` to not require reviewers. Defaults to `1`.', -}; +const subscribe = (z: typeof zod) => + z + .boolean({ + description: `Subscribe to the repository. The default value is 'false'`, + }) + .optional(); -const restrictions = { - title: 'Restrict who can push to the protected branch', - description: - 'Restrict who can push to the protected branch. User, app, and team restrictions are only available for organization-owned repositories.', - type: 'object', - additionalProperties: false, - properties: { - apps: { - type: 'array', - items: { - type: 'string', - }, - }, - users: { - type: 'array', - items: { - type: 'string', - }, - }, - teams: { - type: 'array', - items: { - type: 'string', - }, - }, - }, -}; +const branch = (z: typeof zod) => + z + .string({ + description: `The branch to protect. Defaults to the repository's default branch`, + }) + .optional(); -const requiredCommitSigning = { - title: 'Require commit signing', - type: 'boolean', - description: `Require commit signing so that you must sign commits on this branch.`, +export { + repoUrl, + description, + homepage, + access, + requireCodeOwnerReviews, + dismissStaleReviews, + requiredStatusCheckContexts, + requireBranchesToBeUpToDate, + requiredConversationResolution, + requireLastPushApproval, + repoVisibility, + deleteBranchOnMerge, + gitAuthorName, + gitAuthorEmail, + allowMergeCommit, + allowSquashMerge, + allowUpdateBranch, + squashMergeCommitTitle, + squashMergeCommitMessage, + allowRebaseMerge, + allowAutoMerge, + collaborators, + hasProjects, + hasWiki, + hasIssues, + token, + topics, + defaultBranch, + gitCommitMessage, + sourcePath, + repoVariables, + secrets, + oidcCustomization, + customProperties, + subscribe, + requiredApprovingReviewCount, + restrictions, + requiredCommitSigning, + requiredLinearHistory, + protectDefaultBranch, + protectEnforceAdmins, + bypassPullRequestAllowances, + branch, }; - -const requiredLinearHistory = { - title: 'Require linear history', - type: 'boolean', - description: `Prevent merge commits from being pushed to matching branches.`, -}; - -const repoVariables = { - title: 'Repository Variables', - description: `Variables attached to the repository`, - type: 'object', -}; - -const secrets = { - title: 'Repository Secrets', - description: `Secrets attached to the repository`, - type: 'object', -}; - -const oidcCustomization = { - title: 'Repository OIDC customization template', - description: `OIDC customization template attached to the repository.`, - type: 'object', - additionalProperties: false, - properties: { - useDefault: { - title: 'Use Default', - type: 'boolean', - description: `Whether to use the default OIDC template or not.`, - }, - includeClaimKeys: { - title: 'Include claim keys', - type: 'array', - items: { - type: 'string', - }, - description: `Array of unique strings. Each claim key can only contain alphanumeric characters and underscores.`, - }, - }, -}; - -const customProperties = { - title: 'Custom Repository Properties', - description: - 'Custom properties to be added to the repository (note, this only works for organization repositories)', - type: 'object', -}; - -const subscribe = { - title: 'Subscribe to repository', - description: `Subscribe to the repository. The default value is 'false'`, - type: 'boolean', -}; - -export { access }; -export { allowMergeCommit }; -export { allowRebaseMerge }; -export { allowSquashMerge }; -export { squashMergeCommitTitle }; -export { squashMergeCommitMessage }; -export { allowAutoMerge }; -export { allowUpdateBranch }; -export { collaborators }; -export { defaultBranch }; -export { deleteBranchOnMerge }; -export { description }; -export { gitAuthorEmail }; -export { gitAuthorName }; -export { gitCommitMessage }; -export { homepage }; -export { protectDefaultBranch }; -export { protectEnforceAdmins }; -export { bypassPullRequestAllowances }; -export { requiredApprovingReviewCount }; -export { restrictions }; -export { repoUrl }; -export { repoVisibility }; -export { requireCodeOwnerReviews }; -export { dismissStaleReviews }; -export { requiredStatusCheckContexts }; -export { requireBranchesToBeUpToDate }; -export { requiredConversationResolution }; -export { requireLastPushApproval }; -export { hasProjects }; -export { hasIssues }; -export { hasWiki }; -export { sourcePath }; -export { token }; -export { topics }; -export { requiredCommitSigning }; -export { requiredLinearHistory }; -export { repoVariables }; -export { secrets }; -export { oidcCustomization }; -export { customProperties }; -export { subscribe }; diff --git a/plugins/scaffolder-backend-module-github/src/actions/outputProperties.ts b/plugins/scaffolder-backend-module-github/src/actions/outputProperties.ts index c0cdcb1cb2..a1ee53fec5 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/outputProperties.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/outputProperties.ts @@ -14,19 +14,22 @@ * limitations under the License. */ -const remoteUrl = { - title: 'A URL to the repository with the provider', - type: 'string', -}; -const repoContentsUrl = { - title: 'A URL to the root of the repository', - type: 'string', -}; +import { z as zod } from 'zod'; -const commitHash = { - title: 'The git commit hash of the initial commit', - type: 'string', -}; +const remoteUrl = (z: typeof zod) => + z.string({ + description: 'A URL to the repository with the provider', + }); + +const repoContentsUrl = (z: typeof zod) => + z.string({ + description: 'A URL to the root of the repository', + }); + +const commitHash = (z: typeof zod) => + z.string({ + description: 'The git commit hash of the initial commit', + }); export { remoteUrl }; export { repoContentsUrl }; diff --git a/plugins/scaffolder-backend-module-github/src/module.ts b/plugins/scaffolder-backend-module-github/src/module.ts index 02dfc2d7f3..5812a22560 100644 --- a/plugins/scaffolder-backend-module-github/src/module.ts +++ b/plugins/scaffolder-backend-module-github/src/module.ts @@ -39,8 +39,8 @@ import { DefaultGithubCredentialsProvider, ScmIntegrations, } from '@backstage/integration'; -import { CatalogClient } from '@backstage/catalog-client'; import { createHandleAutocompleteRequest } from './autocomplete/autocomplete'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; /** * @public @@ -54,17 +54,13 @@ export const githubModule = createBackendModule({ deps: { scaffolder: scaffolderActionsExtensionPoint, config: coreServices.rootConfig, - discovery: coreServices.discovery, - auth: coreServices.auth, + catalog: catalogServiceRef, autocomplete: scaffolderAutocompleteExtensionPoint, }, - async init({ scaffolder, config, discovery, auth, autocomplete }) { + async init({ scaffolder, config, autocomplete, catalog }) { const integrations = ScmIntegrations.fromConfig(config); const githubCredentialsProvider = DefaultGithubCredentialsProvider.fromIntegrations(integrations); - const catalogClient = new CatalogClient({ - discoveryApi: discovery, - }); scaffolder.addActions( createGithubActionsDispatchAction({ @@ -80,8 +76,7 @@ export const githubModule = createBackendModule({ }), createGithubEnvironmentAction({ integrations, - catalogClient, - auth, + catalog, }), createGithubIssuesLabelAction({ integrations, diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index dc7c180b59..8fdbc9d0fd 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,50 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 54c6d5efec..4a451ce6fa 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.3", + "version": "0.9.2-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-gitlab/report.api.md b/plugins/scaffolder-backend-module-gitlab/report.api.md index cdb6065a3b..07e3ed87b4 100644 --- a/plugins/scaffolder-backend-module-gitlab/report.api.md +++ b/plugins/scaffolder-backend-module-gitlab/report.api.md @@ -5,7 +5,6 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -14,6 +13,7 @@ export const createGitlabGroupEnsureExistsAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { + repoUrl: string; path: ( | string | { @@ -21,13 +21,12 @@ export const createGitlabGroupEnsureExistsAction: (options: { slug: string; } )[]; - repoUrl: string; token?: string | undefined; }, { groupId?: number | undefined; }, - 'v1' + 'v2' >; // @public @@ -35,29 +34,29 @@ export const createGitlabIssueAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - title: string; repoUrl: string; projectId: number; - labels?: string | undefined; - description?: string | undefined; - weight?: number | undefined; + title: string; token?: string | undefined; assignees?: number[] | undefined; - createdAt?: string | undefined; confidential?: boolean | undefined; - milestoneId?: number | undefined; - epicId?: number | undefined; + description?: string | undefined; + createdAt?: string | undefined; dueDate?: string | undefined; discussionToResolve?: string | undefined; + epicId?: number | undefined; + labels?: string | undefined; issueType?: IssueType | undefined; mergeRequestToResolveDiscussionsOf?: number | undefined; + milestoneId?: number | undefined; + weight?: number | undefined; }, { issueUrl: string; issueId: number; issueIid: number; }, - 'v1' + 'v2' >; // @public @@ -65,18 +64,18 @@ export const createGitlabProjectAccessTokenAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - repoUrl: string; projectId: string | number; - name?: string | undefined; + repoUrl: string; token?: string | undefined; + name?: string | undefined; + accessLevel?: number | undefined; scopes?: string[] | undefined; expiresAt?: string | undefined; - accessLevel?: number | undefined; }, { access_token: string; }, - 'v1' + 'v2' >; // @public @@ -84,18 +83,18 @@ export const createGitlabProjectDeployTokenAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - name: string; - scopes: string[]; repoUrl: string; projectId: string | number; - username?: string | undefined; + name: string; + scopes: string[]; token?: string | undefined; + username?: string | undefined; }, { - user: string; deploy_token: string; + user: string; }, - 'v1' + 'v2' >; // @public @@ -103,19 +102,21 @@ export const createGitlabProjectVariableAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - key: string; - value: string; repoUrl: string; projectId: string | number; + key: string; + value: string; variableType: string; - raw?: boolean | undefined; token?: string | undefined; - masked?: boolean | undefined; - environmentScope?: string | undefined; variableProtected?: boolean | undefined; + masked?: boolean | undefined; + raw?: boolean | undefined; + environmentScope?: string | undefined; }, - any, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public @@ -126,13 +127,17 @@ export const createGitlabRepoPushAction: (options: { repoUrl: string; branchName: string; commitMessage: string; - sourcePath?: string; - targetPath?: string; - token?: string; - commitAction?: 'create' | 'delete' | 'update'; + sourcePath?: string | undefined; + targetPath?: string | undefined; + token?: string | undefined; + commitAction?: 'update' | 'delete' | 'create' | undefined; }, - JsonObject, - 'v1' + { + projectid: string; + projectPath: string; + commitHash: string; + }, + 'v2' >; // @public @@ -142,49 +147,68 @@ export function createPublishGitlabAction(options: { }): TemplateAction< { repoUrl: string; - defaultBranch?: string; - repoVisibility?: 'private' | 'internal' | 'public'; - sourcePath?: string | boolean; - skipExisting?: boolean; - token?: string; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - signCommit?: boolean; - setUserAsOwner?: boolean; - topics?: string[]; - settings?: { - path?: string; - auto_devops_enabled?: boolean; - ci_config_path?: string; - description?: string; - merge_method?: 'merge' | 'rebase_merge' | 'ff'; - squash_option?: 'default_off' | 'default_on' | 'never' | 'always'; - topics?: string[]; - visibility?: 'private' | 'internal' | 'public'; - only_allow_merge_if_all_discussions_are_resolved?: boolean; - only_allow_merge_if_pipeline_succeeds?: boolean; - allow_merge_on_skipped_pipeline?: boolean; - }; - branches?: Array<{ - name: string; - protect?: boolean; - create?: boolean; - ref?: string; - }>; - projectVariables?: Array<{ - key: string; - value: string; - description?: string; - variable_type?: string; - protected?: boolean; - masked?: boolean; - raw?: boolean; - environment_scope?: string; - }>; + repoVisibility?: 'internal' | 'private' | 'public' | undefined; + defaultBranch?: string | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + signCommit?: boolean | undefined; + sourcePath?: string | boolean | undefined; + skipExisting?: boolean | undefined; + token?: string | undefined; + setUserAsOwner?: boolean | undefined; + topics?: string[] | undefined; + settings?: + | { + visibility?: 'internal' | 'private' | 'public' | undefined; + path?: string | undefined; + description?: string | undefined; + merge_method?: 'merge' | 'rebase_merge' | 'ff' | undefined; + topics?: string[] | undefined; + auto_devops_enabled?: boolean | undefined; + only_allow_merge_if_pipeline_succeeds?: boolean | undefined; + allow_merge_on_skipped_pipeline?: boolean | undefined; + only_allow_merge_if_all_discussions_are_resolved?: + | boolean + | undefined; + squash_option?: + | 'always' + | 'never' + | 'default_on' + | 'default_off' + | undefined; + ci_config_path?: string | undefined; + } + | undefined; + branches?: + | { + name: string; + ref?: string | undefined; + create?: boolean | undefined; + protect?: boolean | undefined; + }[] + | undefined; + projectVariables?: + | { + key: string; + value: string; + raw?: boolean | undefined; + description?: string | undefined; + protected?: boolean | undefined; + variable_type?: 'file' | 'env_var' | undefined; + masked?: boolean | undefined; + environment_scope?: string | undefined; + }[] + | undefined; }, - JsonObject, - 'v1' + { + remoteUrl: string; + repoContentsUrl: string; + projectId: number; + commitHash: string; + created: boolean; + }, + 'v2' >; // @public @@ -196,19 +220,25 @@ export const createPublishGitlabMergeRequestAction: (options: { title: string; description: string; branchName: string; - targetBranchName?: string; - sourcePath?: string; - targetPath?: string; - token?: string; - commitAction?: 'create' | 'delete' | 'update' | 'skip' | 'auto'; - projectid?: string; - removeSourceBranch?: boolean; - assignee?: string; - reviewers?: string[]; - assignReviewersFromApprovalRules?: boolean; + targetBranchName?: string | undefined; + sourcePath?: string | undefined; + targetPath?: string | undefined; + token?: string | undefined; + commitAction?: 'auto' | 'update' | 'delete' | 'create' | 'skip' | undefined; + projectid?: string | undefined; + removeSourceBranch?: boolean | undefined; + assignee?: string | undefined; + reviewers?: string[] | undefined; + assignReviewersFromApprovalRules?: boolean | undefined; + labels?: string | string[] | undefined; }, - JsonObject, - 'v1' + { + targetBranchName: string; + projectid: string; + projectPath: string; + mergeRequestUrl: string; + }, + 'v2' >; // @public @@ -216,17 +246,17 @@ export const createTriggerGitlabPipelineAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - branch: string; repoUrl: string; projectId: number; tokenDescription: string; + branch: string; token?: string | undefined; variables?: Record | undefined; }, { pipelineUrl: string; }, - 'v1' + 'v2' >; // @public @@ -237,33 +267,33 @@ export const editGitlabIssueAction: (options: { repoUrl: string; projectId: number; issueIid: number; - title?: string | undefined; - labels?: string | undefined; - description?: string | undefined; - weight?: number | undefined; token?: string | undefined; - assignees?: number[] | undefined; addLabels?: string | undefined; + assignees?: number[] | undefined; confidential?: boolean | undefined; + description?: string | undefined; + discussionLocked?: boolean | undefined; + dueDate?: string | undefined; + epicId?: number | undefined; + issueType?: IssueType | undefined; + labels?: string | undefined; milestoneId?: number | undefined; removeLabels?: string | undefined; stateEvent?: IssueStateEvent | undefined; - discussionLocked?: boolean | undefined; - epicId?: number | undefined; - dueDate?: string | undefined; + title?: string | undefined; updatedAt?: string | undefined; - issueType?: IssueType | undefined; + weight?: number | undefined; }, { - state: string; - title: string; - projectId: number; - updatedAt: string; issueUrl: string; + projectId: number; issueId: number; issueIid: number; + state: string; + title: string; + updatedAt: string; }, - 'v1' + 'v2' >; // @public diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts index 753da2e253..7f3f673260 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts @@ -38,307 +38,201 @@ export function createPublishGitlabAction(options: { }) { const { integrations, config } = options; - return createTemplateAction<{ - repoUrl: string; - defaultBranch?: string; - /** @deprecated in favour of settings.visibility field */ - repoVisibility?: 'private' | 'internal' | 'public'; - sourcePath?: string | boolean; - skipExisting?: boolean; - token?: string; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - signCommit?: boolean; - setUserAsOwner?: boolean; - /** @deprecated in favour of settings.topics field */ - topics?: string[]; - settings?: { - path?: string; - auto_devops_enabled?: boolean; - ci_config_path?: string; - description?: string; - merge_method?: 'merge' | 'rebase_merge' | 'ff'; - squash_option?: 'default_off' | 'default_on' | 'never' | 'always'; - topics?: string[]; - visibility?: 'private' | 'internal' | 'public'; - only_allow_merge_if_all_discussions_are_resolved?: boolean; - only_allow_merge_if_pipeline_succeeds?: boolean; - allow_merge_on_skipped_pipeline?: boolean; - }; - branches?: Array<{ - name: string; - protect?: boolean; - create?: boolean; - ref?: string; - }>; - projectVariables?: Array<{ - key: string; - value: string; - description?: string; - variable_type?: string; - protected?: boolean; - masked?: boolean; - raw?: boolean; - environment_scope?: string; - }>; - }>({ + return createTemplateAction({ id: 'publish:gitlab', description: 'Initializes a git repository of the content in the workspace, and publishes it to GitLab.', examples, schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: { - title: 'Repository Location', - type: 'string', + repoUrl: z => + z.string({ description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, - }, - repoVisibility: { - title: 'Repository Visibility', - description: `Sets the visibility of the repository. The default value is 'private'. (deprecated, use settings.visibility instead)`, - type: 'string', - enum: ['private', 'public', 'internal'], - }, - defaultBranch: { - title: 'Default Branch', - type: 'string', - description: `Sets the default branch on the repository. The default value is 'master'`, - }, - gitCommitMessage: { - title: 'Git Commit Message', - type: 'string', - description: `Sets the commit message on the repository. The default value is 'initial commit'`, - }, - gitAuthorName: { - title: 'Default Author Name', - type: 'string', - description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, - }, - gitAuthorEmail: { - title: 'Default Author Email', - type: 'string', - description: `Sets the default author email for the commit.`, - }, - signCommit: { - title: 'Sign commit', - type: 'boolean', - description: 'Sign commit with configured PGP private key', - }, - sourcePath: { - title: 'Source Path', - description: - 'Path within the workspace that will be used as the repository root. If omitted or set to true, the entire workspace will be published as the repository. If set to false, the created repository will be empty.', - type: ['string', 'boolean'], - }, - skipExisting: { - title: 'Skip if repository exists', - description: - 'Do not publish the repository if it already exists. The default value is false.', - type: ['boolean'], - }, - token: { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to GitLab', - }, - setUserAsOwner: { - title: 'Set User As Owner', - type: 'boolean', - description: - 'Set the token user as owner of the newly created repository. Requires a token authorized to do the edit in the integration configuration for the matching host', - }, - topics: { - title: 'Topic labels', - description: - 'Topic labels to apply on the repository. (deprecated, use settings.topics instead)', - type: 'array', - items: { - type: 'string', - }, - }, - settings: { - title: 'Project settings', - description: - 'Additional project settings, based on https://docs.gitlab.com/ee/api/projects.html#create-project attributes', - type: 'object', - properties: { - path: { - title: 'Project path', - description: - 'Repository name for new project. Generated based on name if not provided (generated as lowercase with dashes).', - type: 'string', - }, - auto_devops_enabled: { - title: 'Auto DevOps enabled', - description: 'Enable Auto DevOps for this project', - type: 'boolean', - }, - ci_config_path: { - title: 'CI config path', - description: 'Custom CI config path for this project', - type: 'string', - }, - description: { - title: 'Project description', - description: 'Short project description', - type: 'string', - }, - merge_method: { - title: 'Merge Method to use', - description: 'Merge Methods (merge, rebase_merge, ff)', - type: 'string', - enum: ['merge', 'rebase_merge', 'ff'], - }, - squash_option: { - title: 'Squash option', - description: - 'Set squash option for the project (never, always, default_on, default_off)', - type: 'string', - enum: ['default_off', 'default_on', 'never', 'always'], - }, - topics: { - title: 'Topic labels', - description: 'Topic labels to apply on the repository', - type: 'array', - items: { - type: 'string', - }, - }, - visibility: { - title: 'Project visibility', - description: - 'The visibility of the project. Can be private, internal, or public. The default value is private.', - type: 'string', - enum: ['private', 'public', 'internal'], - }, - only_allow_merge_if_all_discussions_are_resolved: { - title: 'All threads must be resolved', - description: - 'Set whether merge requests can only be merged when all the discussions are resolved.', - type: 'boolean', - }, - only_allow_merge_if_pipeline_succeeds: { - title: 'Pipelines must succeed', - description: - 'Set whether merge requests can only be merged with successful pipelines. This setting is named Pipelines must succeed in the project settings.', - type: 'boolean', - }, - allow_merge_on_skipped_pipeline: { - title: 'Skipped pipelines are considered successful', - description: - 'Set whether or not merge requests can be merged with skipped jobs.', - type: 'boolean', - }, - }, - }, - branches: { - title: 'Project branches settings', - type: 'array', - items: { - type: 'object', - required: ['name'], - properties: { - name: { - title: 'Branch name', - type: 'string', - }, - protect: { - title: 'Should branch be protected', - description: `Will mark branch as protected. The default value is 'false'`, - type: 'boolean', - }, - create: { - title: 'Should branch be created', - description: `If branch does not exist, it will be created from provided ref. The default value is 'false'`, - type: 'boolean', - }, - ref: { - title: 'Branch reference', - description: `Branch reference to create branch from. The default value is 'master'`, - type: 'string', - }, - }, - }, - }, - projectVariables: { - title: 'Project variables', - description: - 'Project variables settings based on Gitlab Project Environments API - https://docs.gitlab.com/ee/api/project_level_variables.html#create-a-variable', - type: 'array', - items: { - type: 'object', - required: ['key', 'value'], - properties: { - key: { - title: 'Variable key', + }), + repoVisibility: z => + z + .enum(['private', 'public', 'internal'], { + description: `Sets the visibility of the repository. The default value is 'private'. (deprecated, use settings.visibility instead)`, + }) + .optional(), + defaultBranch: z => + z + .string({ + description: `Sets the default branch on the repository. The default value is 'master'`, + }) + .optional(), + gitCommitMessage: z => + z + .string({ + description: `Sets the commit message on the repository. The default value is 'initial commit'`, + }) + .optional(), + gitAuthorName: z => + z + .string({ + description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, + }) + .optional(), + gitAuthorEmail: z => + z + .string({ + description: `Sets the default author email for the commit.`, + }) + .optional(), + signCommit: z => + z + .boolean({ + description: 'Sign commit with configured PGP private key', + }) + .optional(), + sourcePath: z => + z + .union([z.string(), z.boolean()], { + description: + 'Path within the workspace that will be used as the repository root. If omitted or set to true, the entire workspace will be published as the repository. If set to false, the created repository will be empty.', + }) + .optional(), + skipExisting: z => + z + .boolean({ + description: + 'Do not publish the repository if it already exists. The default value is false.', + }) + .optional(), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + setUserAsOwner: z => + z + .boolean({ + description: + 'Set the token user as owner of the newly created repository. Requires a token authorized to do the edit in the integration configuration for the matching host', + }) + .optional(), + topics: z => + z + .array(z.string(), { + description: + 'Topic labels to apply on the repository. (deprecated, use settings.topics instead)', + }) + .optional(), + settings: z => + z + .object({ + path: z + .string({ description: - 'The key of a variable; must have no more than 255 characters; only A-Z, a-z, 0-9, and _ are allowed', - type: 'string', - }, - value: { - title: 'Variable value', - description: 'The value of a variable', - type: 'string', - }, - description: { - title: 'Variable description', - description: `The description of the variable. The default value is 'null'`, - type: 'string', - }, - variable_type: { - title: 'Variable type', - description: `The type of a variable. The default value is 'env_var'`, - type: 'string', - enum: ['env_var', 'file'], - }, - protected: { - title: 'Variable protection', - description: `Whether the variable is protected. The default value is 'false'`, - type: 'boolean', - }, - raw: { - title: 'Variable raw', - description: `Whether the variable is in raw format. The default value is 'false'`, - type: 'boolean', - }, - environment_scope: { - title: 'Variable environment scope', - description: `The environment_scope of the variable. The default value is '*'`, - type: 'string', - }, - }, - }, - }, - }, + 'Repository name for new project. Generated based on name if not provided (generated as lowercase with dashes).', + }) + .optional(), + auto_devops_enabled: z + .boolean({ + description: 'Enable Auto DevOps for this project', + }) + .optional(), + ci_config_path: z + .string({ + description: 'Custom CI config path for this project', + }) + .optional(), + description: z + .string({ + description: 'Short project description', + }) + .optional(), + merge_method: z + .enum(['merge', 'rebase_merge', 'ff'], { + description: 'Merge Methods (merge, rebase_merge, ff)', + }) + .optional(), + squash_option: z + .enum(['default_off', 'default_on', 'never', 'always'], { + description: + 'Set squash option for the project (never, always, default_on, default_off)', + }) + .optional(), + topics: z + .array(z.string(), { + description: 'Topic labels to apply on the repository', + }) + .optional(), + visibility: z + .enum(['private', 'public', 'internal'], { + description: + 'The visibility of the project. Can be private, internal, or public. The default value is private.', + }) + .optional(), + only_allow_merge_if_all_discussions_are_resolved: z + .boolean({ + description: + 'Set whether merge requests can only be merged when all the discussions are resolved.', + }) + .optional(), + only_allow_merge_if_pipeline_succeeds: z + .boolean({ + description: + 'Set whether merge requests can only be merged with successful pipelines. This setting is named Pipelines must succeed in the project settings.', + }) + .optional(), + allow_merge_on_skipped_pipeline: z + .boolean({ + description: + 'Set whether or not merge requests can be merged with skipped jobs.', + }) + .optional(), + }) + .optional(), + branches: z => + z + .array( + z.object({ + name: z.string(), + protect: z.boolean().optional(), + create: z.boolean().optional(), + ref: z.string().optional(), + }), + ) + .optional(), + projectVariables: z => + z + .array( + z.object({ + key: z.string(), + value: z.string(), + description: z.string().optional(), + variable_type: z.enum(['env_var', 'file']).optional(), + protected: z.boolean().optional(), + masked: z.boolean().optional(), + raw: z.boolean().optional(), + environment_scope: z.string().optional(), + }), + ) + .optional(), }, output: { - type: 'object', - properties: { - remoteUrl: { - title: 'A URL to the repository with the provider', - type: 'string', - }, - repoContentsUrl: { - title: 'A URL to the root of the repository', - type: 'string', - }, - projectId: { - title: 'The ID of the project', - type: 'number', - }, - commitHash: { - title: 'The git commit hash of the initial commit', - type: 'string', - }, - created: { - title: 'Whether the repository was created or not', - type: 'boolean', - }, - }, + remoteUrl: z => + z.string({ + description: 'A URL to the repository with the provider', + }), + repoContentsUrl: z => + z.string({ + description: 'A URL to the root of the repository', + }), + projectId: z => + z.number({ + description: 'The ID of the project', + }), + commitHash: z => + z.string({ + description: 'The git commit hash of the initial commit', + }), + created: z => + z.boolean({ + description: 'Whether the repository was created or not', + }), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts index 7e5b03ce04..c3d4fd74bf 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts @@ -17,8 +17,6 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { GroupSchema } from '@gitbeaker/rest'; -import { z } from 'zod'; -import commonGitlabConfig from '../commonGitlabConfig'; import { getClient, parseRepoUrl } from '../util'; import { examples } from './gitlabGroupEnsureExists.examples'; @@ -38,9 +36,19 @@ export const createGitlabGroupEnsureExistsAction = (options: { supportsDryRun: true, examples, schema: { - input: commonGitlabConfig.merge( - z.object({ - path: z + input: { + repoUrl: z => + z.string({ + description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, + }), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + path: z => + z .array( z.string().or( z.object({ @@ -54,13 +62,15 @@ export const createGitlabGroupEnsureExistsAction = (options: { }, ) .min(1), - }), - ), - output: z.object({ - groupId: z - .number({ description: 'The id of the innermost sub-group' }) - .optional(), - }), + }, + output: { + groupId: z => + z + .number({ + description: 'The id of the innermost sub-group', + }) + .optional(), + }, }, async handler(ctx) { if (ctx.isDryRun) { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts index b387bf4e8a..1910b5ce6c 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts @@ -17,84 +17,12 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import commonGitlabConfig, { IssueType } from '../commonGitlabConfig'; +import { IssueType } from '../commonGitlabConfig'; import { examples } from './gitlabIssueCreate.examples'; -import { z } from 'zod'; import { checkEpicScope, convertDate, getClient, parseRepoUrl } from '../util'; import { CreateIssueOptions, IssueSchema } from '@gitbeaker/rest'; import { getErrorMessage } from './helpers'; -const issueInputProperties = z.object({ - projectId: z.number().describe('Project Id'), - title: z.string({ description: 'Title of the issue' }), - assignees: z - .array(z.number(), { - description: 'IDs of the users to assign the issue to.', - }) - .optional(), - confidential: z.boolean({ description: 'Issue Confidentiality' }).optional(), - description: z.string().describe('Issue description').max(1048576).optional(), - createdAt: z - .string() - .describe('Creation date/time') - .regex( - /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, - 'Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ', - ) - .optional(), - dueDate: z - .string() - .describe('Due date/time') - .regex( - /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, - 'Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ', - ) - .optional(), - discussionToResolve: z - .string({ - description: - 'Id of a discussion to resolve. Use in combination with "merge_request_to_resolve_discussions_of"', - }) - .optional(), - epicId: z - .number({ description: 'Id of the linked Epic' }) - .min(0, 'Valid values should be equal or greater than zero') - .optional(), - labels: z.string({ description: 'Labels to apply' }).optional(), - issueType: z - .nativeEnum(IssueType, { - description: 'Type of the issue', - }) - .optional(), - mergeRequestToResolveDiscussionsOf: z - .number({ - description: 'IID of a merge request in which to resolve all issues', - }) - .optional(), - milestoneId: z - .number({ description: 'Global ID of a milestone to assign the issue' }) - .optional(), - weight: z - .number({ description: 'The issue weight' }) - .min(0) - .refine(value => { - const isValid = value >= 0; - if (!isValid) { - return { - message: 'Valid values should be equal or greater than zero', - }; - } - return isValid; - }) - .optional(), -}); - -const issueOutputProperties = z.object({ - issueUrl: z.string({ description: 'Issue Url' }), - issueId: z.number({ description: 'Issue Id' }), - issueIid: z.number({ description: 'Issue Iid' }), -}); - /** * Creates a `gitlab:issues:create` Scaffolder action. * @@ -110,8 +38,133 @@ export const createGitlabIssueAction = (options: { description: 'Creates a Gitlab issue.', examples, schema: { - input: commonGitlabConfig.merge(issueInputProperties), - output: issueOutputProperties, + input: { + repoUrl: z => + z.string({ + description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, + }), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + projectId: z => + z.number({ + description: 'Project Id', + }), + title: z => + z.string({ + description: 'Title of the issue', + }), + assignees: z => + z + .array(z.number(), { + description: 'IDs of the users to assign the issue to.', + }) + .optional(), + confidential: z => + z + .boolean({ + description: 'Issue Confidentiality', + }) + .optional(), + description: z => + z + .string({ + description: 'Issue description', + }) + .max(1048576) + .optional(), + createdAt: z => + z + .string({ + description: 'Creation date/time', + }) + .regex( + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, + 'Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ', + ) + .optional(), + dueDate: z => + z + .string({ + description: 'Due date/time', + }) + .regex( + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, + 'Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ', + ) + .optional(), + discussionToResolve: z => + z + .string({ + description: + 'Id of a discussion to resolve. Use in combination with "merge_request_to_resolve_discussions_of"', + }) + .optional(), + epicId: z => + z + .number({ + description: 'Id of the linked Epic', + }) + .min(0, 'Valid values should be equal or greater than zero') + .optional(), + labels: z => + z + .string({ + description: 'Labels to apply', + }) + .optional(), + issueType: z => + z + .nativeEnum(IssueType, { + description: 'Type of the issue', + }) + .optional(), + mergeRequestToResolveDiscussionsOf: z => + z + .number({ + description: + 'IID of a merge request in which to resolve all issues', + }) + .optional(), + milestoneId: z => + z + .number({ + description: 'Global ID of a milestone to assign the issue', + }) + .optional(), + weight: z => + z + .number({ + description: 'The issue weight', + }) + .min(0) + .refine( + value => { + return value >= 0; + }, + { + message: 'Valid values should be equal or greater than zero', + }, + ) + .optional(), + }, + output: { + issueUrl: z => + z.string({ + description: 'Issue Url', + }), + issueId: z => + z.number({ + description: 'Issue Id', + }), + issueIid: z => + z.number({ + description: 'Issue Iid', + }), + }, }, async handler(ctx) { try { @@ -132,7 +185,7 @@ export const createGitlabIssueAction = (options: { milestoneId, weight, token, - } = commonGitlabConfig.merge(issueInputProperties).parse(ctx.input); + } = ctx.input; const { host } = parseRepoUrl(repoUrl, integrations); const api = getClient({ host, integrations, token }); @@ -201,12 +254,6 @@ export const createGitlabIssueAction = (options: { ctx.output('issueUrl', response.web_url); ctx.output('issueIid', response.iid); } catch (error: any) { - if (error instanceof z.ZodError) { - // Handling Zod validation errors - throw new InputError(`Validation error: ${error.message}`, { - validationErrors: error.errors, - }); - } // Handling other errors throw new InputError( `Failed to create GitLab issue: ${getErrorMessage(error)}`, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.ts index 0754892308..bb3e717291 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.ts @@ -17,123 +17,12 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import commonGitlabConfig, { - IssueStateEvent, - IssueType, -} from '../commonGitlabConfig'; +import { IssueStateEvent, IssueType } from '../commonGitlabConfig'; import { examples } from './gitlabIssueEdit.examples'; -import { z } from 'zod'; import { checkEpicScope, convertDate, getClient, parseRepoUrl } from '../util'; import { EditIssueOptions, IssueSchema } from '@gitbeaker/rest'; import { getErrorMessage } from './helpers'; -const editIssueInputProperties = z.object({ - projectId: z - .number() - .describe( - 'The global ID or URL-encoded path of the project owned by the authenticated user.', - ), - issueIid: z.number().describe("The internal ID of a project's issue"), - addLabels: z - .string({ - description: - 'Comma-separated label names to add to an issue. If a label does not already exist, this creates a new project label and assigns it to the issue.', - }) - .optional(), - assignees: z - .array(z.number(), { - description: 'IDs of the users to assign the issue to.', - }) - .optional(), - confidential: z - .boolean({ description: 'Updates an issue to be confidential.' }) - .optional(), - description: z - .string() - .describe('The description of an issue. Limited to 1,048,576 characters.') - .max(1048576) - .optional(), - discussionLocked: z - .boolean({ - description: - 'Flag indicating if the issue’s discussion is locked. If the discussion is locked only project members can add or edit comments.', - }) - .optional(), - dueDate: z - .string() - .describe( - 'The due date. Date time string in the format YYYY-MM-DD, for example 2016-03-11.', - ) - .regex(/^\d{4}-\d{2}-\d{2}$/, 'Invalid date format. Use YYYY-MM-DD') - .optional(), - epicId: z - .number({ - description: - 'ID of the epic to add the issue to. Valid values are greater than or equal to 0.', - }) - .min(0, 'Valid values should be equal or greater than zero') - .optional(), - issueType: z - .nativeEnum(IssueType, { - description: - 'Updates the type of issue. One of issue, incident, test_case or task.', - }) - .optional(), - labels: z - .string({ - description: - 'Comma-separated label names for an issue. Set to an empty string to unassign all labels. If a label does not already exist, this creates a new project label and assigns it to the issue.', - }) - .optional(), - milestoneId: z - .number({ - description: - 'The global ID of a milestone to assign the issue to. Set to 0 or provide an empty value to unassign a milestone', - }) - .optional(), - removeLabels: z - .string({ - description: 'Comma-separated label names to remove from an issue.', - }) - .optional(), - stateEvent: z - .nativeEnum(IssueStateEvent, { - description: - 'The state event of an issue. To close the issue, use close, and to reopen it, use reopen.', - }) - .optional(), - title: z.string().describe('The title of an issue.').optional(), - updatedAt: z - .string() - .describe( - 'When the issue was updated. Date time string, ISO 8601 formatted', - ) - .regex( - /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, - 'Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ', - ) - .optional(), - weight: z - .number({ description: 'The issue weight' }) - .min(0, 'Valid values should be equal or greater than zero') - .max(10, 'Valid values should be equal or less than 10') - .optional(), -}); - -const editIssueOutputProperties = z.object({ - issueUrl: z.string({ description: 'Issue WebUrl' }), - projectId: z.number({ - description: 'The project id the issue belongs to WebUrl', - }), - issueId: z.number({ description: 'The issues Id' }), - issueIid: z.number({ - description: "The issues internal ID of a project's issue", - }), - state: z.string({ description: 'The state event of an issue' }), - title: z.string({ description: 'The title of an issue.' }), - updatedAt: z.string({ description: 'The last updated time of the issue.' }), -}); - /** * Creates a `gitlab:issue:edit` Scaffolder action. * @@ -149,8 +38,167 @@ export const editGitlabIssueAction = (options: { description: 'Edit a Gitlab issue.', examples, schema: { - input: commonGitlabConfig.merge(editIssueInputProperties), - output: editIssueOutputProperties, + input: { + repoUrl: z => + z.string({ + description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, + }), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + projectId: z => + z.number({ + description: + 'The global ID or URL-encoded path of the project owned by the authenticated user.', + }), + issueIid: z => + z.number({ + description: "The internal ID of a project's issue", + }), + addLabels: z => + z + .string({ + description: + 'Comma-separated label names to add to an issue. If a label does not already exist, this creates a new project label and assigns it to the issue.', + }) + .optional(), + assignees: z => + z + .array(z.number(), { + description: 'IDs of the users to assign the issue to.', + }) + .optional(), + confidential: z => + z + .boolean({ + description: 'Updates an issue to be confidential.', + }) + .optional(), + description: z => + z + .string({ + description: + 'The description of an issue. Limited to 1,048,576 characters.', + }) + .max(1048576) + .optional(), + discussionLocked: z => + z + .boolean({ + description: + 'Flag indicating if the issue discussion is locked. If the discussion is locked only project members can add or edit comments.', + }) + .optional(), + dueDate: z => + z + .string({ + description: + 'The due date. Date time string in the format YYYY-MM-DD, for example 2016-03-11.', + }) + .regex(/^\d{4}-\d{2}-\d{2}$/, 'Invalid date format. Use YYYY-MM-DD') + .optional(), + epicId: z => + z + .number({ + description: + 'ID of the epic to add the issue to. Valid values are greater than or equal to 0.', + }) + .min(0, 'Valid values should be equal or greater than zero') + .optional(), + issueType: z => + z + .nativeEnum(IssueType, { + description: + 'Updates the type of issue. One of issue, incident, test_case or task.', + }) + .optional(), + labels: z => + z + .string({ + description: + 'Comma-separated label names for an issue. Set to an empty string to unassign all labels. If a label does not already exist, this creates a new project label and assigns it to the issue.', + }) + .optional(), + milestoneId: z => + z + .number({ + description: + 'The global ID of a milestone to assign the issue to. Set to 0 or provide an empty value to unassign a milestone', + }) + .optional(), + removeLabels: z => + z + .string({ + description: + 'Comma-separated label names to remove from an issue.', + }) + .optional(), + stateEvent: z => + z + .nativeEnum(IssueStateEvent, { + description: + 'The state event of an issue. To close the issue, use close, and to reopen it, use reopen.', + }) + .optional(), + title: z => + z + .string({ + description: 'The title of an issue.', + }) + .optional(), + updatedAt: z => + z + .string({ + description: + 'When the issue was updated. Date time string, ISO 8601 formatted', + }) + .regex( + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, + 'Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ', + ) + .optional(), + weight: z => + z + .number({ + description: 'The issue weight', + }) + .min(0, 'Valid values should be equal or greater than zero') + .max(10, 'Valid values should be equal or less than 10') + .optional(), + }, + output: { + issueUrl: z => + z.string({ + description: 'Issue WebUrl', + }), + projectId: z => + z.number({ + description: 'The project id the issue belongs to WebUrl', + }), + issueId: z => + z.number({ + description: 'The issues Id', + }), + issueIid: z => + z.number({ + description: "The issues internal ID of a project's issue", + }), + state: z => + z.string({ + description: 'The state event of an issue', + }), + title: z => + z.string({ + description: 'The title of an issue.', + }), + updatedAt: z => + z.string({ + description: 'The last updated time of the issue.', + }), + }, }, async handler(ctx) { try { @@ -174,7 +222,7 @@ export const editGitlabIssueAction = (options: { stateEvent, weight, token, - } = commonGitlabConfig.merge(editIssueInputProperties).parse(ctx.input); + } = ctx.input; const { host } = parseRepoUrl(repoUrl, integrations); const api = getClient({ host, integrations, token }); @@ -252,12 +300,6 @@ export const editGitlabIssueAction = (options: { ctx.output('state', editedIssue.state); ctx.output('updatedAt', editedIssue.updatedAt); } catch (error: any) { - if (error instanceof z.ZodError) { - // Handling Zod validation errors - throw new InputError(`Validation error: ${error.message}`, { - validationErrors: error.errors, - }); - } // Handling other errors throw new InputError( `Failed to edit/modify GitLab issue: ${getErrorMessage(error)}`, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.examples.test.ts index a97159a6b9..27f39fe621 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.examples.test.ts @@ -125,7 +125,7 @@ jest.mock('@gitbeaker/rest', () => ({ })); describe('createGitLabMergeRequest', () => { - let instance: TemplateAction; + let instance: TemplateAction; const mockDir = createMockDirectory(); const workspacePath = mockDir.resolve('workspace'); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts index c5161b199d..0cc1ca60c3 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts @@ -238,7 +238,7 @@ jest.mock('@gitbeaker/rest', () => ({ })); describe('createGitLabMergeRequest', () => { - let instance: TemplateAction; + let instance: TemplateAction; const mockDir = createMockDirectory(); const workspacePath = mockDir.resolve('workspace'); @@ -1446,4 +1446,66 @@ describe('createGitLabMergeRequest', () => { ); }); }); + describe('with labels', () => { + it('handles single label', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + branchName: 'new-mr', + description: 'MR description', + commitAction: 'skip', + labels: 'single-label', + }; + const ctx = createMockActionContext({ input, workspacePath }); + await instance.handler(ctx); + + expect(mockGitlabClient.Branches.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'main', + ); + expect(mockGitlabClient.Commits.create).not.toHaveBeenCalled(); + expect(mockGitlabClient.MergeRequests.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'main', + 'Create my new MR', + { + description: 'MR description', + removeSourceBranch: false, + labels: 'single-label', + }, + ); + }); + it('handles array of labels', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + branchName: 'new-mr', + description: 'MR description', + commitAction: 'skip', + labels: ['foo', 'bar', 'baz'], + }; + const ctx = createMockActionContext({ input, workspacePath }); + await instance.handler(ctx); + + expect(mockGitlabClient.Branches.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'main', + ); + expect(mockGitlabClient.Commits.create).not.toHaveBeenCalled(); + expect(mockGitlabClient.MergeRequests.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'main', + 'Create my new MR', + { + description: 'MR description', + removeSourceBranch: false, + labels: ['foo', 'bar', 'baz'], + }, + ); + }); + }); }); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts index 61a6c04889..a9ee80a30f 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts @@ -134,8 +134,10 @@ async function getReviewersFromApprovalRules( } } +const commitActions = ['create', 'delete', 'update', 'skip', 'auto'] as const; + /** - * Create a new action that creates a gitlab merge request. + * Create a new action that creates a GitLab merge request. * * @public */ @@ -144,138 +146,96 @@ export const createPublishGitlabMergeRequestAction = (options: { }) => { const { integrations } = options; - return createTemplateAction<{ - repoUrl: string; - title: string; - description: string; - branchName: string; - targetBranchName?: string; - sourcePath?: string; - targetPath?: string; - token?: string; - commitAction?: 'create' | 'delete' | 'update' | 'skip' | 'auto'; - /** @deprecated projectID passed as query parameters in the repoUrl */ - projectid?: string; - removeSourceBranch?: boolean; - assignee?: string; - reviewers?: string[]; - assignReviewersFromApprovalRules?: boolean; - }>({ + return createTemplateAction({ id: 'publish:gitlab:merge-request', examples, schema: { input: { - required: ['repoUrl', 'branchName'], - type: 'object', - properties: { - repoUrl: { - type: 'string', - title: 'Repository Location', - description: `\ -Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where \ -'project_name' is the repository name and 'group_name' is a group or username`, - }, - /** @deprecated projectID is passed as query parameters in the repoUrl */ - projectid: { - type: 'string', - title: 'projectid', - description: 'Project ID/Name(slug) of the Gitlab Project', - }, - title: { - type: 'string', - title: 'Merge Request Name', - description: 'The name for the merge request', - }, - description: { - type: 'string', - title: 'Merge Request Description', - description: 'The description of the merge request', - }, - branchName: { - type: 'string', - title: 'Source Branch Name', - description: 'The source branch name of the merge request', - }, - targetBranchName: { - type: 'string', - title: 'Target Branch Name', - description: 'The target branch name of the merge request', - }, - sourcePath: { - type: 'string', - title: 'Working Subdirectory', - description: `\ + repoUrl: z => + z.string().describe(`\ +Accepts the format \`gitlab.com?repo=project_name&owner=group_name\` where \ +\`project_name\` is the repository name and \`group_name\` is a group or username`), + title: z => z.string().describe('The name for the merge request'), + description: z => + z.string().describe('The description of the merge request'), + branchName: z => + z.string().describe('The source branch name of the merge request'), + targetBranchName: z => + z + .string() + .optional() + .describe( + 'The target branch name of the merge request (defaults to _default branch of repository_)', + ), + sourcePath: z => + z.string().optional().describe(`\ Subdirectory of working directory to copy changes from. \ -For reasons of backward compatibility, any specified 'targetPath' input will \ +For reasons of backward compatibility, any specified \`targetPath\` input will \ be applied in place of an absent/falsy value for this input. \ -Circumvent this behavior using '.'`, - }, - targetPath: { - type: 'string', - title: 'Repository Subdirectory', - description: 'Subdirectory of repository to apply changes to', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to GitLab', - }, - commitAction: { - title: 'Commit action', - type: 'string', - enum: ['create', 'update', 'delete', 'auto'], - description: `\ -The action to be used for git commit. Defaults to the custom 'auto' action provided by backstage, -which uses additional API calls in order to detect whether to 'create', 'update' or 'skip' each source file.`, - }, - removeSourceBranch: { - title: 'Delete source branch', - type: 'boolean', - description: - 'Option to delete source branch once the MR has been merged. Default: false', - }, - assignee: { - title: 'Merge Request Assignee', - type: 'string', - description: 'User this merge request will be assigned to', - }, - reviewers: { - title: 'Merge Request Reviewers', - type: 'array', - items: { - type: 'string', - }, - description: 'Users that will be assigned as reviewers', - }, - assignReviewersFromApprovalRules: { - title: 'Assign reviewers from approval rules', - type: 'boolean', - description: - 'Automatically assign reviewers from the approval rules of the MR. Includes Codeowners', - }, - }, +Circumvent this behavior using \`.\``), + targetPath: z => + z + .string() + .optional() + .describe('Subdirectory of repository to apply changes to'), + token: z => + z + .string() + .optional() + .describe('The token to use for authorization to GitLab'), + commitAction: z => + z.enum(commitActions).optional().describe(`\ +The action to be used for \`git\` commit. Defaults to the custom \`auto\` action provided by Backstage, +which uses additional API calls in order to detect whether to \`create\`, \`update\` or \`skip\` each source file.`), + /** @deprecated projectID passed as query parameters in the repoUrl */ + projectid: z => + z + .string() + .optional() + .describe( + `\ +Project ID/Name(slug) of the GitLab Project +_deprecated_: \`projectid\` passed as query parameters in the \`repoUrl\``, + ), + removeSourceBranch: z => + z + .boolean() + .optional() + .describe( + 'Option to delete source branch once the MR has been merged. Default: `false`', + ), + assignee: z => + z + .string() + .optional() + .describe('User this merge request will be assigned to'), + reviewers: z => + z + .string() + .array() + .optional() + .describe('Users that will be assigned as reviewers'), + assignReviewersFromApprovalRules: z => + z + .boolean() + .optional() + .describe( + 'Automatically assign reviewers from the approval rules of the MR. Includes `CODEOWNERS`', + ), + labels: z => + z + .string() + .or(z.string().array()) + .optional() + .describe('Labels with which to tag the created merge request'), }, output: { - type: 'object', - properties: { - targetBranchName: { - title: 'Target branch name of the merge request', - type: 'string', - }, - projectid: { - title: 'Gitlab Project id/Name(slug)', - type: 'string', - }, - projectPath: { - title: 'Gitlab Project path', - type: 'string', - }, - mergeRequestUrl: { - title: 'MergeRequest(MR) URL', - type: 'string', - description: 'Link to the merge request in GitLab', - }, - }, + targetBranchName: z => + z.string().describe('Target branch name of the merge request'), + projectid: z => z.string().describe('GitLab Project id/Name(slug)'), + projectPath: z => z.string().describe('GitLab Project path'), + mergeRequestUrl: z => + z.string().describe('Link to the merge request in GitLab'), }, }, async handler(ctx) { @@ -291,6 +251,7 @@ which uses additional API calls in order to detect whether to 'create', 'update' sourcePath, title, token, + labels, } = ctx.input; const { owner, repo, project } = parseRepoUrl(repoUrl, integrations); @@ -499,6 +460,7 @@ which uses additional API calls in order to detect whether to 'create', 'update' : false, assigneeId, reviewerIds, + labels, }, ); return { @@ -552,7 +514,7 @@ which uses additional API calls in order to detect whether to 'create', 'update' ctx.output('projectid', repoID); ctx.output('targetBranchName', targetBranch); ctx.output('projectPath', repoID); - ctx.output('mergeRequestUrl', mrWebUrl); + ctx.output('mergeRequestUrl', mrWebUrl as string); }, }); }; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts index b051004ccc..41a00bc985 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts @@ -21,28 +21,10 @@ import { ExpandedPipelineSchema, PipelineTriggerTokenSchema, } from '@gitbeaker/rest'; -import { z } from 'zod'; -import commonGitlabConfig from '../commonGitlabConfig'; import { getClient, parseRepoUrl } from '../util'; import { examples } from './gitlabPipelineTrigger.examples'; import { getErrorMessage } from './helpers'; -const pipelineInputProperties = z.object({ - projectId: z.number().describe('Project Id'), - tokenDescription: z.string().describe('Pipeline token description'), - branch: z.string().describe('Project branch'), - variables: z - .record(z.string(), z.string()) - .optional() - .describe( - 'A object/record of key-valued strings containing the pipeline variables.', - ), -}); - -const pipelineOutputProperties = z.object({ - pipelineUrl: z.string({ description: 'Pipeline Url' }), -}); - /** * Creates a `gitlab:pipeline:trigger` Scaffolder action. * @@ -58,15 +40,50 @@ export const createTriggerGitlabPipelineAction = (options: { description: 'Triggers a GitLab Pipeline.', examples, schema: { - input: commonGitlabConfig.merge(pipelineInputProperties), - output: pipelineOutputProperties, + input: { + repoUrl: z => + z.string({ + description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, + }), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + projectId: z => + z.number({ + description: 'Project Id', + }), + tokenDescription: z => + z.string({ + description: 'Pipeline token description', + }), + branch: z => + z.string({ + description: 'Project branch', + }), + variables: z => + z + .record(z.string(), z.string(), { + description: + 'A object/record of key-valued strings containing the pipeline variables.', + }) + .optional(), + }, + output: { + pipelineUrl: z => + z.string({ + description: 'Pipeline Url', + }), + }, }, async handler(ctx) { let pipelineTriggerToken: string | undefined = undefined; let pipelineTriggerId: number | undefined = undefined; const { repoUrl, projectId, tokenDescription, token, branch, variables } = - commonGitlabConfig.merge(pipelineInputProperties).parse(ctx.input); + ctx.input; const { host } = parseRepoUrl(repoUrl, integrations); const api = getClient({ host, integrations, token }); @@ -118,12 +135,6 @@ export const createTriggerGitlabPipelineAction = (options: { ctx.output('pipelineUrl', pipelineTriggerResponse.web_url); } catch (error: any) { - if (error instanceof z.ZodError) { - // Handling Zod validation errors - throw new InputError(`Validation error: ${error.message}`, { - validationErrors: error.errors, - }); - } // Handling other errors throw new InputError( `Failed to trigger Pipeline: ${getErrorMessage(error)}`, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.ts index de248c56f7..47efbda306 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.ts @@ -19,7 +19,6 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { AccessTokenScopes, Gitlab } from '@gitbeaker/rest'; import { DateTime } from 'luxon'; -import { z } from 'zod'; import { getToken } from '../util'; import { examples } from './gitlabProjectAccessTokenCreate.examples'; @@ -38,39 +37,55 @@ export const createGitlabProjectAccessTokenAction = (options: { id: 'gitlab:projectAccessToken:create', examples, schema: { - input: z.object({ - projectId: z.union([z.number(), z.string()], { - description: 'Project ID/Name(slug) of the Gitlab Project', - }), - token: z - .string({ - description: 'The token to use for authorization to GitLab', - }) - .optional(), - name: z.string({ description: 'Name of Access Key' }).optional(), - repoUrl: z.string({ description: 'URL to gitlab instance' }), - accessLevel: z - .number({ - description: - 'Access Level of the Token, 10 (Guest), 20 (Reporter), 30 (Developer), 40 (Maintainer), and 50 (Owner)', - }) - .optional(), - scopes: z - .string({ - description: 'Scopes for a project access token', - }) - .array() - .optional(), - expiresAt: z - .string({ - description: - 'Expiration date of the access token in ISO format (YYYY-MM-DD). If Empty, it will set to the maximum of 365 days.', - }) - .optional(), - }), - output: z.object({ - access_token: z.string({ description: 'Access Token' }), - }), + input: { + projectId: z => + z.union([z.number(), z.string()], { + description: 'Project ID/Name(slug) of the Gitlab Project', + }), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + name: z => + z + .string({ + description: 'Name of Access Key', + }) + .optional(), + repoUrl: z => + z.string({ + description: 'URL to gitlab instance', + }), + accessLevel: z => + z + .number({ + description: + 'Access Level of the Token, 10 (Guest), 20 (Reporter), 30 (Developer), 40 (Maintainer), and 50 (Owner)', + }) + .optional(), + scopes: z => + z + .string({ + description: 'Scopes for a project access token', + }) + .array() + .optional(), + expiresAt: z => + z + .string({ + description: + 'Expiration date of the access token in ISO format (YYYY-MM-DD). If Empty, it will set to the maximum of 365 days.', + }) + .optional(), + }, + output: { + access_token: z => + z.string({ + description: 'Access Token', + }), + }, }, async handler(ctx) { ctx.logger.info(`Creating Token for Project "${ctx.input.projectId}"`); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.ts index 26cf36d6c1..ad7f98483d 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.ts @@ -18,8 +18,6 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { DeployTokenScope, Gitlab } from '@gitbeaker/rest'; -import { z } from 'zod'; -import commonGitlabConfig from '../commonGitlabConfig'; import { getToken } from '../util'; import { examples } from './gitlabProjectDeployTokenCreate.examples'; @@ -37,22 +35,46 @@ export const createGitlabProjectDeployTokenAction = (options: { id: 'gitlab:projectDeployToken:create', examples, schema: { - input: commonGitlabConfig.merge( - z.object({ - projectId: z.union([z.number(), z.string()], { + input: { + repoUrl: z => + z.string({ + description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, + }), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + projectId: z => + z.union([z.number(), z.string()], { description: 'Project ID', }), - name: z.string({ description: 'Deploy Token Name' }), - username: z - .string({ description: 'Deploy Token Username' }) + name: z => + z.string({ + description: 'Deploy Token Name', + }), + username: z => + z + .string({ + description: 'Deploy Token Username', + }) .optional(), - scopes: z.array(z.string(), { description: 'Scopes' }), - }), - ), - output: z.object({ - deploy_token: z.string({ description: 'Deploy Token' }), - user: z.string({ description: 'User' }), - }), + scopes: z => + z.array(z.string(), { + description: 'Scopes', + }), + }, + output: { + deploy_token: z => + z.string({ + description: 'Deploy Token', + }), + user: z => + z.string({ + description: 'User', + }), + }, }, async handler(ctx) { ctx.logger.info(`Creating Token for Project "${ctx.input.projectId}"`); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.examples.test.ts index d577adc896..df99638481 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.examples.test.ts @@ -39,7 +39,7 @@ jest.mock('@gitbeaker/rest', () => ({ })); describe('gitlab:group:migrate', () => { - let instance: TemplateAction; + let instance: TemplateAction; beforeEach(() => { jest.clearAllMocks(); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.test.ts index e1bf9af138..8163d83599 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.test.ts @@ -37,7 +37,7 @@ jest.mock('@gitbeaker/rest', () => ({ })); describe('createGitlabRepoMigrate', () => { - let instance: TemplateAction; + let instance: TemplateAction; beforeEach(() => { jest.clearAllMocks(); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.ts index b248210c76..5de449dba8 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.ts @@ -34,66 +34,42 @@ export const createGitlabProjectMigrateAction = (options: { }) => { const { integrations } = options; - return createTemplateAction<{ - destinationAccessToken: string; - destinationUrl: string; - sourceAccessToken: string; - sourceFullPath: string; - sourceUrl: string; - }>({ + return createTemplateAction({ id: 'gitlab:group:migrate', examples, schema: { input: { - required: [ - 'destinationAccessToken', - 'destinationUrl', - 'sourceAccessToken', - 'sourceFullPath', - 'sourceUrl', - ], - type: 'object', - properties: { - destinationAccessToken: { - type: 'string', - title: 'Target Repository Access Token', + destinationAccessToken: z => + z.string({ description: `The token to use for authorization to the target GitLab'`, - }, - destinationUrl: { - type: 'string', - title: 'Target Project Location', + }), + destinationUrl: z => + z.string({ description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, - }, - sourceAccessToken: { - type: 'string', - title: 'Source Group Access Token', + }), + sourceAccessToken: z => + z.string({ description: `The token to use for authorization to the source GitLab'`, - }, - sourceFullPath: { - type: 'string', - title: 'Group Full Path', + }), + sourceFullPath: z => + z.string({ description: 'Full path to the project in the source Gitlab instance', - }, - sourceUrl: { - type: 'string', - title: 'Source URL Location', + }), + sourceUrl: z => + z.string({ description: `Accepts the format 'https://gitlab.com/'`, - }, - }, + }), }, output: { - type: 'object', - properties: { - importedRepoUrl: { - title: 'URL to the newly imported repo', - type: 'string', - }, - migrationId: { - title: 'Id of the migration that imports the project', - type: 'number', - }, - }, + importedRepoUrl: z => + z.string({ + description: 'URL to the newly imported repo', + }), + migrationId: z => + z.number({ + description: 'Id of the migration that imports the project', + }), }, }, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts index cab4410cc6..824a8566f7 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts @@ -17,8 +17,6 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { VariableType } from '@gitbeaker/rest'; -import { z } from 'zod'; -import commonGitlabConfig from '../commonGitlabConfig'; import { getClient, parseRepoUrl } from '../util'; import { examples } from './gitlabProjectVariableCreate.examples'; @@ -36,39 +34,65 @@ export const createGitlabProjectVariableAction = (options: { id: 'gitlab:projectVariable:create', examples, schema: { - input: commonGitlabConfig.merge( - z.object({ - projectId: z.union([z.number(), z.string()], { + input: { + repoUrl: z => + z.string({ + description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, + }), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + projectId: z => + z.union([z.number(), z.string()], { description: 'Project ID', }), - key: z + key: z => + z .string({ description: 'The key of a variable; must have no more than 255 characters; only A-Z, a-z, 0-9, and _ are allowed', }) .regex(/^[A-Za-z0-9_]{1,255}$/), - value: z.string({ description: 'The value of a variable' }), - variableType: z.string({ + value: z => + z.string({ + description: 'The value of a variable', + }), + variableType: z => + z.string({ description: 'Variable Type (env_var or file)', }), - variableProtected: z - .boolean({ description: 'Whether the variable is protected' }) + variableProtected: z => + z + .boolean({ + description: 'Whether the variable is protected', + }) .default(false) .optional(), - masked: z - .boolean({ description: 'Whether the variable is masked' }) + masked: z => + z + .boolean({ + description: 'Whether the variable is masked', + }) .default(false) .optional(), - raw: z - .boolean({ description: 'Whether the variable is expandable' }) + raw: z => + z + .boolean({ + description: 'Whether the variable is expandable', + }) .default(false) .optional(), - environmentScope: z - .string({ description: 'The environment_scope of the variable' }) + environmentScope: z => + z + .string({ + description: 'The environment_scope of the variable', + }) .default('*') .optional(), - }), - ), + }, }, async handler(ctx) { const { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.examples.test.ts index c78950a0b3..25b6c89ae4 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.examples.test.ts @@ -52,7 +52,7 @@ jest.mock('@gitbeaker/rest', () => ({ })); describe('gitlab:repo:push', () => { - let instance: TemplateAction; + let instance: TemplateAction; const mockDir = createMockDirectory(); const workspacePath = mockDir.resolve('workspace'); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts index 35a0b83ae5..8005c0791b 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts @@ -50,7 +50,7 @@ jest.mock('@gitbeaker/rest', () => ({ })); describe('createGitLabCommit', () => { - let instance: TemplateAction; + let instance: TemplateAction; const mockDir = createMockDirectory(); const workspacePath = mockDir.resolve('workspace'); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts index 9ac1de13d3..a3ae415704 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts @@ -14,16 +14,16 @@ * limitations under the License. */ +import path from 'path'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { InputError } from '@backstage/errors'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { createTemplateAction, parseRepoUrl, serializeDirectoryContents, } from '@backstage/plugin-scaffolder-node'; import { CommitAction } from '@gitbeaker/rest'; -import path from 'path'; -import { ScmIntegrationRegistry } from '@backstage/integration'; -import { InputError } from '@backstage/errors'; -import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { createGitlabApi, getErrorMessage } from './helpers'; import { examples } from './gitlabRepoPush.examples'; @@ -37,78 +37,63 @@ export const createGitlabRepoPushAction = (options: { }) => { const { integrations } = options; - return createTemplateAction<{ - repoUrl: string; - branchName: string; - commitMessage: string; - sourcePath?: string; - targetPath?: string; - token?: string; - commitAction?: 'create' | 'delete' | 'update'; - }>({ + return createTemplateAction({ id: 'gitlab:repo:push', examples, schema: { input: { - required: ['repoUrl', 'branchName', 'commitMessage'], - type: 'object', - properties: { - repoUrl: { - type: 'string', - title: 'Repository Location', + repoUrl: z => + z.string({ description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, - }, - branchName: { - type: 'string', - title: 'Source Branch Name', + }), + branchName: z => + z.string({ description: 'The branch name for the commit', - }, - commitMessage: { - type: 'string', - title: 'Commit Message', + }), + commitMessage: z => + z.string({ description: `The commit message`, - }, - sourcePath: { - type: 'string', - title: 'Working Subdirectory', - description: - 'Subdirectory of working directory to copy changes from', - }, - targetPath: { - type: 'string', - title: 'Repository Subdirectory', - description: 'Subdirectory of repository to apply changes to', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to GitLab', - }, - commitAction: { - title: 'Commit action', - type: 'string', - enum: ['create', 'update', 'delete'], - description: - 'The action to be used for git commit. Defaults to create, but can be set to update or delete', - }, - }, + }), + sourcePath: z => + z + .string({ + description: + 'Subdirectory of working directory to copy changes from', + }) + .optional(), + targetPath: z => + z + .string({ + description: 'Subdirectory of repository to apply changes to', + }) + .optional(), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + commitAction: z => + z + .enum(['create', 'update', 'delete'], { + description: + 'The action to be used for git commit. Defaults to create, but can be set to update or delete', + }) + .optional(), }, output: { - type: 'object', - properties: { - projectid: { - title: 'Gitlab Project id/Name(slug)', - type: 'string', - }, - projectPath: { - title: 'Gitlab Project path', - type: 'string', - }, - commitHash: { - title: 'The git commit hash of the commit', - type: 'string', - }, - }, + projectid: z => + z.string({ + description: 'Gitlab Project id/Name(slug)', + }), + projectPath: z => + z.string({ + description: 'Gitlab Project path', + }), + commitHash: z => + z.string({ + description: 'The git commit hash of the commit', + }), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md index 3923382d94..cf37d45b43 100644 --- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -1,5 +1,47 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json index c49531786f..114c54aa87 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.3", + "version": "0.1.11-next.2", "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 d4a8d0f82b..61f91e22f0 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,50 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 6cd2e344cd..7fa2f6fd3e 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.3", + "version": "0.5.10-next.2", "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 829c30ef6a..a323f45086 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,44 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index bebe43c2f5..1a92f9ae82 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.3", + "version": "0.2.10-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-sentry/report.api.md b/plugins/scaffolder-backend-module-sentry/report.api.md index 973ef9844c..8c716b3ab3 100644 --- a/plugins/scaffolder-backend-module-sentry/report.api.md +++ b/plugins/scaffolder-backend-module-sentry/report.api.md @@ -5,7 +5,6 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; // @public @@ -16,11 +15,13 @@ export function createSentryCreateProjectAction(options: { organizationSlug: string; teamSlug: string; name: string; - slug?: string; - authToken?: string; + slug?: string | undefined; + authToken?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts index 6c173deb59..226c31bd05 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts @@ -31,42 +31,36 @@ import { Config } from '@backstage/config'; export function createSentryCreateProjectAction(options: { config: Config }) { const { config } = options; - return createTemplateAction<{ - organizationSlug: string; - teamSlug: string; - name: string; - slug?: string; - authToken?: string; - }>({ + return createTemplateAction({ id: 'sentry:project:create', schema: { input: { - required: ['organizationSlug', 'teamSlug', 'name'], - type: 'object', - properties: { - organizationSlug: { - title: 'The slug of the organization the team belongs to', - type: 'string', - }, - teamSlug: { - title: 'The slug of the team to create a new project for', - type: 'string', - }, - name: { - title: 'The name for the new project', - type: 'string', - }, - slug: { - title: - 'Optional slug for the new project. If not provided a slug is generated from the name', - type: 'string', - }, - authToken: { - title: - 'authenticate via bearer auth token. Requires scope: project:write', - type: 'string', - }, - }, + organizationSlug: z => + z.string({ + description: 'The slug of the organization the team belongs to', + }), + teamSlug: z => + z.string({ + description: 'The slug of the team to create a new project for', + }), + name: z => + z.string({ + description: 'The name for the new project', + }), + slug: z => + z + .string({ + description: + 'Optional slug for the new project. If not provided a slug is generated from the name', + }) + .optional(), + authToken: z => + z + .string({ + description: + 'authenticate via bearer auth token. Requires scope: project:write', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index df48b09214..7c6ed48801 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,45 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 44bdb6d120..1e8fba39fa 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.3", + "version": "0.4.11-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-yeoman/report.api.md b/plugins/scaffolder-backend-module-yeoman/report.api.md index 57498bbe3a..cb4df446cb 100644 --- a/plugins/scaffolder-backend-module-yeoman/report.api.md +++ b/plugins/scaffolder-backend-module-yeoman/report.api.md @@ -4,18 +4,19 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; -import { JsonObject } from '@backstage/types'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; // @public export function createRunYeomanAction(): TemplateAction< { namespace: string; - args?: string[]; - options?: JsonObject; + args?: string[] | undefined; + options?: Record | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public diff --git a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts index a8bbbf945d..aec9e3a415 100644 --- a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts +++ b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { JsonObject } from '@backstage/types'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { yeomanRun } from './yeomanRun'; import { examples } from './yeoman.examples'; @@ -29,38 +28,28 @@ import { examples } from './yeoman.examples'; * @public */ export function createRunYeomanAction() { - return createTemplateAction<{ - namespace: string; - args?: string[]; - options?: JsonObject; - }>({ + return createTemplateAction({ id: 'run:yeoman', description: 'Runs Yeoman on an installed Yeoman generator', examples, schema: { input: { - type: 'object', - required: ['namespace'], - properties: { - namespace: { - title: 'Generator Namespace', + namespace: z => + z.string({ description: 'Yeoman generator namespace, e.g: node:app', - type: 'string', - }, - args: { - title: 'Generator Arguments', - description: 'Arguments to pass on to Yeoman for templating', - type: 'array', - items: { - type: 'string', - }, - }, - options: { - title: 'Generator Options', - description: 'Options to pass on to Yeoman for templating', - type: 'object', - }, - }, + }), + args: z => + z + .array(z.string(), { + description: 'Arguments to pass on to Yeoman for templating', + }) + .optional(), + options: z => + z + .record(z.any(), { + description: 'Options to pass on to Yeoman for templating', + }) + .optional(), }, }, supportsDryRun: true, diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 8c42f7a26a..14def28f52 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,237 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 30cc5ffbf3..e7c93ec049 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.3", + "version": "2.0.0-next.2", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin", @@ -64,7 +64,6 @@ "@backstage/backend-common": "^0.25.0", "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", @@ -88,7 +87,6 @@ "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/types": "workspace:^", "@opentelemetry/api": "^1.9.0", - "@types/express": "^4.17.6", "@types/luxon": "^3.0.0", "concat-stream": "^2.0.0", "express": "^4.17.1", @@ -122,6 +120,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", + "@types/express": "^4.17.6", "@types/fs-extra": "^11.0.0", "@types/nunjucks": "^3.1.4", "@types/supertest": "^2.0.8", diff --git a/plugins/scaffolder-backend/report-alpha.api.md b/plugins/scaffolder-backend/report-alpha.api.md index c35b743f6a..958d68fc25 100644 --- a/plugins/scaffolder-backend/report-alpha.api.md +++ b/plugins/scaffolder-backend/report-alpha.api.md @@ -3,7 +3,6 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeature } from '@backstage/backend-plugin-api'; import { ConditionalPolicyDecision } from '@backstage/plugin-permission-common'; import { Conditions } from '@backstage/plugin-permission-node'; import { JsonObject } from '@backstage/types'; @@ -26,10 +25,6 @@ export const createScaffolderTemplateConditionalDecision: ( conditions: PermissionCriteria>, ) => ConditionalPolicyDecision; -// @alpha (undocumented) -const _feature: BackendFeature; -export default _feature; - // @alpha export const scaffolderActionConditions: Conditions<{ hasActionId: PermissionRule< diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index 41c228e3f1..1f2a17a384 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -3,84 +3,44 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ActionContext as ActionContext_2 } from '@backstage/plugin-scaffolder-node'; import { AuditorService } from '@backstage/backend-plugin-api'; import { AuthService } from '@backstage/backend-plugin-api'; -import { AutocompleteHandler } from '@backstage/plugin-scaffolder-node/alpha'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { BackstageCredentials } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; -import { CreatedTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; -import { CreatedTemplateGlobal } from '@backstage/plugin-scaffolder-node/alpha'; -import { createGithubActionsDispatchAction as createGithubActionsDispatchAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { createGithubDeployKeyAction as createGithubDeployKeyAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { createGithubEnvironmentAction as createGithubEnvironmentAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { createGithubIssuesLabelAction as createGithubIssuesLabelAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { CreateGithubPullRequestActionOptions as CreateGithubPullRequestActionOptions_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { createGithubRepoCreateAction as createGithubRepoCreateAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { createGithubRepoPushAction as createGithubRepoPushAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { createGithubWebhookAction as createGithubWebhookAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { createPublishAzureAction as createPublishAzureAction_2 } from '@backstage/plugin-scaffolder-backend-module-azure'; -import { createPublishBitbucketAction as createPublishBitbucketAction_2 } from '@backstage/plugin-scaffolder-backend-module-bitbucket'; -import { createPublishBitbucketCloudAction as createPublishBitbucketCloudAction_2 } from '@backstage/plugin-scaffolder-backend-module-bitbucket-cloud'; -import { createPublishBitbucketServerAction as createPublishBitbucketServerAction_2 } from '@backstage/plugin-scaffolder-backend-module-bitbucket-server'; -import { createPublishBitbucketServerPullRequestAction as createPublishBitbucketServerPullRequestAction_2 } from '@backstage/plugin-scaffolder-backend-module-bitbucket-server'; -import { createPublishGerritAction as createPublishGerritAction_2 } from '@backstage/plugin-scaffolder-backend-module-gerrit'; -import { createPublishGerritReviewAction as createPublishGerritReviewAction_2 } from '@backstage/plugin-scaffolder-backend-module-gerrit'; -import { createPublishGithubAction as createPublishGithubAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { createPublishGitlabAction as createPublishGitlabAction_2 } from '@backstage/plugin-scaffolder-backend-module-gitlab'; -import { createTemplateAction as createTemplateAction_2 } from '@backstage/plugin-scaffolder-node'; import { DatabaseService } from '@backstage/backend-plugin-api'; -import { DiscoveryService } from '@backstage/backend-plugin-api'; import { Duration } from 'luxon'; import { EventsService } from '@backstage/plugin-events-node'; -import { executeShellCommand as executeShellCommand_2 } from '@backstage/plugin-scaffolder-node'; -import { ExecuteShellCommandOptions } from '@backstage/plugin-scaffolder-node'; -import express from 'express'; -import { fetchContents as fetchContents_2 } from '@backstage/plugin-scaffolder-node'; -import { HttpAuthService } from '@backstage/backend-plugin-api'; import { HumanDuration } from '@backstage/types'; -import { IdentityApi } from '@backstage/plugin-auth-node'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { Knex } from 'knex'; -import { LifecycleService } from '@backstage/backend-plugin-api'; -import { Logger } from 'winston'; +import { LoggerService } from '@backstage/backend-plugin-api'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; import { PermissionRuleParams } from '@backstage/plugin-permission-common'; -import { PermissionsService } from '@backstage/backend-plugin-api'; import { RESOURCE_TYPE_SCAFFOLDER_ACTION } from '@backstage/plugin-scaffolder-common/alpha'; import { RESOURCE_TYPE_SCAFFOLDER_TEMPLATE } from '@backstage/plugin-scaffolder-common/alpha'; -import { ScaffolderEntitiesProcessor as ScaffolderEntitiesProcessor_2 } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'; -import { SchedulerService } from '@backstage/backend-plugin-api'; -import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmIntegrations } from '@backstage/integration'; -import { SerializedTask as SerializedTask_2 } from '@backstage/plugin-scaffolder-node'; -import { SerializedTaskEvent as SerializedTaskEvent_2 } from '@backstage/plugin-scaffolder-node'; -import { TaskBroker as TaskBroker_2 } from '@backstage/plugin-scaffolder-node'; -import { TaskBrokerDispatchOptions as TaskBrokerDispatchOptions_2 } from '@backstage/plugin-scaffolder-node'; -import { TaskBrokerDispatchResult as TaskBrokerDispatchResult_2 } from '@backstage/plugin-scaffolder-node'; -import { TaskCompletionState as TaskCompletionState_2 } from '@backstage/plugin-scaffolder-node'; -import { TaskContext as TaskContext_2 } from '@backstage/plugin-scaffolder-node'; -import { TaskEventType as TaskEventType_2 } from '@backstage/plugin-scaffolder-node'; +import { SerializedTask } from '@backstage/plugin-scaffolder-node'; +import { SerializedTaskEvent } from '@backstage/plugin-scaffolder-node'; +import { TaskBroker } from '@backstage/plugin-scaffolder-node'; +import { TaskCompletionState } from '@backstage/plugin-scaffolder-node'; +import { TaskContext } from '@backstage/plugin-scaffolder-node'; import { TaskRecovery } from '@backstage/plugin-scaffolder-common'; -import { TaskSecrets as TaskSecrets_2 } from '@backstage/plugin-scaffolder-node'; +import { TaskSecrets } from '@backstage/plugin-scaffolder-node'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TaskSpecV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { TaskStatus as TaskStatus_2 } from '@backstage/plugin-scaffolder-node'; -import { TemplateAction as TemplateAction_2 } from '@backstage/plugin-scaffolder-node'; +import { TaskStatus } from '@backstage/plugin-scaffolder-node'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { TemplateFilter as TemplateFilter_2 } from '@backstage/plugin-scaffolder-node'; -import { TemplateGlobal as TemplateGlobal_2 } from '@backstage/plugin-scaffolder-node'; +import { TemplateFilter } from '@backstage/plugin-scaffolder-node'; +import { TemplateGlobal } from '@backstage/plugin-scaffolder-node'; import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; -// @public @deprecated (undocumented) -export type ActionContext = ActionContext_2; - // @public (undocumented) export type ActionPermissionRuleInput< TParams extends PermissionRuleParams = PermissionRuleParams, @@ -91,60 +51,63 @@ export type ActionPermissionRuleInput< TParams >; -// @public -export const createBuiltinActions: ( - options: CreateBuiltInActionsOptions, -) => TemplateAction_2[]; - -// @public -export interface CreateBuiltInActionsOptions { - additionalTemplateFilters?: Record; - // (undocumented) - additionalTemplateGlobals?: Record; - auth?: AuthService; - catalogClient: CatalogApi; - config: Config; - integrations: ScmIntegrations; - reader: UrlReaderService; -} - // @public export function createCatalogRegisterAction(options: { - catalogClient: CatalogApi; + catalog: CatalogService; integrations: ScmIntegrations; - auth?: AuthService; -}): TemplateAction_2< +}): TemplateAction< | { catalogInfoUrl: string; - optional?: boolean; + optional?: boolean | undefined; + } + | { + catalogInfoUrl: string; + optional?: boolean | undefined; + catalogInfoPath?: string | undefined; } | { repoContentsUrl: string; - catalogInfoPath?: string; - optional?: boolean; + optional?: boolean | undefined; + } + | { + repoContentsUrl: string; + optional?: boolean | undefined; + catalogInfoPath?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public -export function createCatalogWriteAction(): TemplateAction_2< +export function createCatalogWriteAction(): TemplateAction< { entity: Record; filePath?: string | undefined; }, - any, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public -export function createDebugLogAction(): TemplateAction_2; +export function createDebugLogAction(): TemplateAction< + { + message?: string | undefined; + listWorkspace?: boolean | 'with-contents' | 'with-filenames' | undefined; + }, + { + [x: string]: any; + }, + 'v2' +>; // @public export function createFetchCatalogEntityAction(options: { - catalogClient: CatalogApi; - auth?: AuthService; -}): TemplateAction_2< + catalog: CatalogService; +}): TemplateAction< { entityRef?: string | undefined; entityRefs?: string[] | undefined; @@ -163,89 +126,99 @@ export function createFetchCatalogEntityAction(options: { export function createFetchPlainAction(options: { reader: UrlReaderService; integrations: ScmIntegrations; -}): TemplateAction_2< +}): TemplateAction< { url: string; - targetPath?: string; - token?: string; + targetPath?: string | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public export function createFetchPlainFileAction(options: { reader: UrlReaderService; integrations: ScmIntegrations; -}): TemplateAction_2< +}): TemplateAction< { url: string; targetPath: string; - token?: string; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public export function createFetchTemplateAction(options: { reader: UrlReaderService; integrations: ScmIntegrations; - additionalTemplateFilters?: Record; - additionalTemplateGlobals?: Record; -}): TemplateAction_2< + additionalTemplateFilters?: Record; + additionalTemplateGlobals?: Record; +}): TemplateAction< { url: string; - targetPath?: string; - values: any; - templateFileExtension?: string | boolean; - copyWithoutRender?: string[]; - copyWithoutTemplating?: string[]; - cookiecutterCompat?: boolean; - replace?: boolean; - trimBlocks?: boolean; - lstripBlocks?: boolean; - token?: string; + targetPath?: string | undefined; + values?: Record | undefined; + copyWithoutRender?: string[] | undefined; + copyWithoutTemplating?: string[] | undefined; + cookiecutterCompat?: boolean | undefined; + templateFileExtension?: string | boolean | undefined; + replace?: boolean | undefined; + trimBlocks?: boolean | undefined; + lstripBlocks?: boolean | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public export function createFetchTemplateFileAction(options: { reader: UrlReaderService; integrations: ScmIntegrations; - additionalTemplateFilters?: Record; - additionalTemplateGlobals?: Record; -}): TemplateAction_2< + additionalTemplateFilters?: Record; + additionalTemplateGlobals?: Record; +}): TemplateAction< { url: string; targetPath: string; - values: any; - cookiecutterCompat?: boolean; - replace?: boolean; - trimBlocks?: boolean; - lstripBlocks?: boolean; - token?: string; + values?: Record | undefined; + cookiecutterCompat?: boolean | undefined; + replace?: boolean | undefined; + trimBlocks?: boolean | undefined; + lstripBlocks?: boolean | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public -export const createFilesystemDeleteAction: () => TemplateAction_2< +export const createFilesystemDeleteAction: () => TemplateAction< { files: string[]; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public -export const createFilesystemReadDirAction: () => TemplateAction_2< +export const createFilesystemReadDirAction: () => TemplateAction< { - recursive: boolean; paths: string[]; + recursive: boolean; }, { files: { @@ -259,149 +232,50 @@ export const createFilesystemReadDirAction: () => TemplateAction_2< fullPath: string; }[]; }, - 'v1' + 'v2' >; // @public -export const createFilesystemRenameAction: () => TemplateAction_2< +export const createFilesystemRenameAction: () => TemplateAction< { - files: Array<{ + files: { from: string; to: string; - overwrite?: boolean; - }>; + overwrite?: boolean | undefined; + }[]; }, - JsonObject, - 'v1' ->; - -// @public @deprecated (undocumented) -export const createGithubActionsDispatchAction: typeof createGithubActionsDispatchAction_2; - -// @public @deprecated (undocumented) -export const createGithubDeployKeyAction: typeof createGithubDeployKeyAction_2; - -// @public @deprecated (undocumented) -export const createGithubEnvironmentAction: typeof createGithubEnvironmentAction_2; - -// @public @deprecated (undocumented) -export const createGithubIssuesLabelAction: typeof createGithubIssuesLabelAction_2; - -// @public @deprecated (undocumented) -export type CreateGithubPullRequestActionOptions = - CreateGithubPullRequestActionOptions_2; - -// @public @deprecated (undocumented) -export const createGithubRepoCreateAction: typeof createGithubRepoCreateAction_2; - -// @public @deprecated (undocumented) -export const createGithubRepoPushAction: typeof createGithubRepoPushAction_2; - -// @public @deprecated (undocumented) -export const createGithubWebhookAction: typeof createGithubWebhookAction_2; - -// @public @deprecated (undocumented) -export const createPublishAzureAction: typeof createPublishAzureAction_2; - -// @public @deprecated (undocumented) -export const createPublishBitbucketAction: typeof createPublishBitbucketAction_2; - -// @public @deprecated (undocumented) -export const createPublishBitbucketCloudAction: typeof createPublishBitbucketCloudAction_2; - -// @public @deprecated (undocumented) -export const createPublishBitbucketServerAction: typeof createPublishBitbucketServerAction_2; - -// @public @deprecated (undocumented) -export const createPublishBitbucketServerPullRequestAction: typeof createPublishBitbucketServerPullRequestAction_2; - -// @public @deprecated (undocumented) -export const createPublishGerritAction: typeof createPublishGerritAction_2; - -// @public @deprecated (undocumented) -export const createPublishGerritReviewAction: typeof createPublishGerritReviewAction_2; - -// @public @deprecated (undocumented) -export const createPublishGithubAction: typeof createPublishGithubAction_2; - -// @public @deprecated (undocumented) -export const createPublishGithubPullRequestAction: ( - options: CreateGithubPullRequestActionOptions_2, -) => TemplateAction_2< { - title: string; - branchName: string; - targetBranchName?: string; - description: string; - repoUrl: string; - draft?: boolean; - targetPath?: string; - sourcePath?: string; - token?: string; - reviewers?: string[]; - assignees?: string[]; - teamReviewers?: string[]; - commitMessage?: string; - update?: boolean; - forceFork?: boolean; - gitAuthorName?: string; - gitAuthorEmail?: string; - forceEmptyGitAuthor?: boolean; - createWhenEmpty?: boolean; + [x: string]: any; }, - JsonObject, - 'v1' + 'v2' >; -// @public @deprecated (undocumented) -export const createPublishGitlabAction: typeof createPublishGitlabAction_2; - -// @public @deprecated (undocumented) -export const createPublishGitlabMergeRequestAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction_2< - { - repoUrl: string; - title: string; - description: string; - branchName: string; - targetBranchName?: string; - sourcePath?: string; - targetPath?: string; - token?: string; - commitAction?: 'create' | 'delete' | 'update' | 'skip' | 'auto'; - projectid?: string; - removeSourceBranch?: boolean; - assignee?: string; - reviewers?: string[]; - assignReviewersFromApprovalRules?: boolean; - }, - JsonObject, - 'v1' ->; - -// @public @deprecated -export function createRouter(options: RouterOptions): Promise; - -// @public @deprecated (undocumented) -export const createTemplateAction: typeof createTemplateAction_2; - // @public export function createWaitAction(options?: { maxWaitTime?: Duration | HumanDuration; -}): TemplateAction_2; +}): TemplateAction< + { + minutes?: number | undefined; + seconds?: number | undefined; + milliseconds?: number | undefined; + }, + { + [x: string]: any; + }, + 'v2' +>; -// @public +// @public @deprecated export type CreateWorkerOptions = { - taskBroker: TaskBroker_2; + taskBroker: TaskBroker; actionRegistry: TemplateActionRegistry; integrations: ScmIntegrations; workingDirectory: string; - logger: Logger; + logger: LoggerService; auditor?: AuditorService; - additionalTemplateFilters?: Record; + additionalTemplateFilters?: Record; concurrentTasksLimit?: number; - additionalTemplateGlobals?: Record; + additionalTemplateGlobals?: Record; permissions?: PermissionEvaluator; gracefulShutdown?: boolean; }; @@ -409,14 +283,14 @@ export type CreateWorkerOptions = { // @public export interface CurrentClaimedTask { createdBy?: string; - secrets?: TaskSecrets_2; + secrets?: TaskSecrets; spec: TaskSpec; state?: JsonObject; taskId: string; workspace?: Promise; } -// @public +// @public @deprecated export class DatabaseTaskStore implements TaskStore { // (undocumented) cancelTask( @@ -427,13 +301,13 @@ export class DatabaseTaskStore implements TaskStore { >, ): Promise; // (undocumented) - claimTask(): Promise; + claimTask(): Promise; // (undocumented) cleanWorkspace({ taskId }: { taskId: string }): Promise; // (undocumented) completeTask(options: { taskId: string; - status: TaskStatus_2; + status: TaskStatus; eventBody: JsonObject; }): Promise; // (undocumented) @@ -451,7 +325,7 @@ export class DatabaseTaskStore implements TaskStore { >, ): Promise; // (undocumented) - getTask(taskId: string): Promise; + getTask(taskId: string): Promise; // (undocumented) getTaskState({ taskId }: { taskId: string }): Promise< | { @@ -464,10 +338,10 @@ export class DatabaseTaskStore implements TaskStore { // (undocumented) list(options: { createdBy?: string; - status?: TaskStatus_2; + status?: TaskStatus; filters?: { createdBy?: string | string[]; - status?: TaskStatus_2 | TaskStatus_2[]; + status?: TaskStatus | TaskStatus[]; }; pagination?: { limit?: number; @@ -478,12 +352,12 @@ export class DatabaseTaskStore implements TaskStore { field: string; }[]; }): Promise<{ - tasks: SerializedTask_2[]; + tasks: SerializedTask[]; totalTasks?: number; }>; // (undocumented) listEvents(options: TaskStoreListEventsOptions): Promise<{ - events: SerializedTaskEvent_2[]; + events: SerializedTaskEvent[]; }>; // (undocumented) listStaleTasks(options: { timeoutS: number }): Promise<{ @@ -502,10 +376,7 @@ export class DatabaseTaskStore implements TaskStore { targetPath: string; }): Promise; // (undocumented) - retryTask?(options: { - secrets?: TaskSecrets_2; - taskId: string; - }): Promise; + retryTask?(options: { secrets?: TaskSecrets; taskId: string }): Promise; // (undocumented) saveTaskState(options: { taskId: string; state?: JsonObject }): Promise; // (undocumented) @@ -514,121 +385,30 @@ export class DatabaseTaskStore implements TaskStore { shutdownTask(options: TaskStoreShutDownTaskOptions): Promise; } -// @public +// @public @deprecated export type DatabaseTaskStoreOptions = { database: DatabaseService | Knex; events?: EventsService; }; -// @public @deprecated -export const executeShellCommand: typeof executeShellCommand_2; - -// @public @deprecated -export const fetchContents: typeof fetchContents_2; - -// @public @deprecated -export interface RouterOptions { - // (undocumented) - actions?: TemplateAction_2[]; - // (undocumented) - additionalTemplateFilters?: - | Record - | CreatedTemplateFilter[]; - // (undocumented) - additionalTemplateGlobals?: - | Record - | CreatedTemplateGlobal[]; - // (undocumented) - additionalWorkspaceProviders?: Record; - // (undocumented) - auditor?: AuditorService; - // (undocumented) - auth?: AuthService; - // (undocumented) - autocompleteHandlers?: Record; - // (undocumented) - catalogClient: CatalogApi; - concurrentTasksLimit?: number; - // (undocumented) - config: Config; - // (undocumented) - database: DatabaseService; - // (undocumented) - discovery?: DiscoveryService; - // (undocumented) - events?: EventsService; - // (undocumented) - httpAuth?: HttpAuthService; - // (undocumented) - identity?: IdentityApi; - // (undocumented) - lifecycle?: LifecycleService; - // (undocumented) - logger: Logger; - // (undocumented) - permissionRules?: Array< - TemplatePermissionRuleInput | ActionPermissionRuleInput - >; - // (undocumented) - permissions?: PermissionsService; - // (undocumented) - reader: UrlReaderService; - // (undocumented) - scheduler?: SchedulerService; - // (undocumented) - taskBroker?: TaskBroker_2; - // @deprecated (undocumented) - taskWorkers?: number; -} - -// @public @deprecated -export type RunCommandOptions = ExecuteShellCommandOptions; - -// @public @deprecated -export const ScaffolderEntitiesProcessor: typeof ScaffolderEntitiesProcessor_2; - // @public const scaffolderPlugin: BackendFeature; export default scaffolderPlugin; // @public @deprecated -export type SerializedTask = SerializedTask_2; - -// @public @deprecated -export type SerializedTaskEvent = SerializedTaskEvent_2; - -// @public @deprecated -export type TaskBroker = TaskBroker_2; - -// @public @deprecated -export type TaskBrokerDispatchOptions = TaskBrokerDispatchOptions_2; - -// @public @deprecated -export type TaskBrokerDispatchResult = TaskBrokerDispatchResult_2; - -// @public @deprecated -export type TaskCompletionState = TaskCompletionState_2; - -// @public @deprecated -export type TaskContext = TaskContext_2; - -// @public @deprecated -export type TaskEventType = TaskEventType_2; - -// @public -export class TaskManager implements TaskContext_2 { +export class TaskManager implements TaskContext { // (undocumented) get cancelSignal(): AbortSignal; // (undocumented) cleanWorkspace?(): Promise; // (undocumented) - complete(result: TaskCompletionState_2, metadata?: JsonObject): Promise; + complete(result: TaskCompletionState, metadata?: JsonObject): Promise; // (undocumented) static create( task: CurrentClaimedTask, storage: TaskStore, abortSignal: AbortSignal, - logger: Logger, + logger: LoggerService, auth?: AuthService, config?: Config, additionalWorkspaceProviders?: Record, @@ -656,7 +436,7 @@ export class TaskManager implements TaskContext_2 { targetPath: string; }): Promise; // (undocumented) - get secrets(): TaskSecrets_2 | undefined; + get secrets(): TaskSecrets | undefined; // (undocumented) serializeWorkspace?(options: { path: string }): Promise; // (undocumented) @@ -677,13 +457,7 @@ export class TaskManager implements TaskContext_2 { ): Promise; } -// @public @deprecated (undocumented) -export type TaskSecrets = TaskSecrets_2; - // @public @deprecated -export type TaskStatus = TaskStatus_2; - -// @public export interface TaskStore { // (undocumented) cancelTask?(options: TaskStoreEmitOptions): Promise; @@ -772,10 +546,7 @@ export interface TaskStore { targetPath: string; }): Promise; // (undocumented) - retryTask?(options: { - secrets?: TaskSecrets_2; - taskId: string; - }): Promise; + retryTask?(options: { secrets?: TaskSecrets; taskId: string }): Promise; // (undocumented) saveTaskState?(options: { taskId: string; @@ -793,42 +564,42 @@ export interface TaskStore { shutdownTask?(options: TaskStoreShutDownTaskOptions): Promise; } -// @public +// @public @deprecated export type TaskStoreCreateTaskOptions = { spec: TaskSpec; createdBy?: string; - secrets?: TaskSecrets_2; + secrets?: TaskSecrets; }; -// @public +// @public @deprecated export type TaskStoreCreateTaskResult = { taskId: string; }; -// @public +// @public @deprecated export type TaskStoreEmitOptions = { taskId: string; body: TBody; }; -// @public +// @public @deprecated export type TaskStoreListEventsOptions = { isTaskRecoverable?: boolean; taskId: string; after?: number | undefined; }; -// @public +// @public @deprecated export type TaskStoreRecoverTaskOptions = { timeout: HumanDuration; }; -// @public +// @public @deprecated export type TaskStoreShutDownTaskOptions = { taskId: string; }; -// @public +// @public @deprecated export class TaskWorker { // (undocumented) static create(options: CreateWorkerOptions): Promise; @@ -837,33 +608,23 @@ export class TaskWorker { // (undocumented) recoverTasks(): Promise; // (undocumented) - runOneTask(task: TaskContext_2): Promise; + runOneTask(task: TaskContext): Promise; // (undocumented) start(): void; // (undocumented) stop(): Promise; } -// @public @deprecated (undocumented) -export type TemplateAction = - TemplateAction_2; - -// @public +// @public @deprecated export class TemplateActionRegistry { // (undocumented) - get(actionId: string): TemplateAction_2; + get(actionId: string): TemplateAction; // (undocumented) - list(): TemplateAction_2[]; + list(): TemplateAction[]; // (undocumented) - register(action: TemplateAction_2): void; + register(action: TemplateAction): void; } -// @public @deprecated (undocumented) -export type TemplateFilter = TemplateFilter_2; - -// @public @deprecated (undocumented) -export type TemplateGlobal = TemplateGlobal_2; - // @public (undocumented) export type TemplatePermissionRuleInput< TParams extends PermissionRuleParams = PermissionRuleParams, diff --git a/plugins/scaffolder-backend/sample-templates/notifications-demo/template.yaml b/plugins/scaffolder-backend/sample-templates/notifications-demo/template.yaml index a85f53b12f..abd18b1d42 100644 --- a/plugins/scaffolder-backend/sample-templates/notifications-demo/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/notifications-demo/template.yaml @@ -50,6 +50,10 @@ spec: - normal - high - critical + topic: + title: Topic + type: string + description: Notification topic scope: title: Scope type: string @@ -66,4 +70,5 @@ spec: description: ${{ parameters.description }} link: ${{ parameters.link }} severity: ${{ parameters.severity }} + topic: ${{ parameters.topic }} scope: ${{ parameters.scope }} diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.test.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.test.ts new file mode 100644 index 0000000000..b9cb397ae6 --- /dev/null +++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.test.ts @@ -0,0 +1,1266 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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`, + ); + 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 = - ActionContextNode; - -/** - * @public - * @deprecated Use `createTemplateAction` from `@backstage/plugin-scaffolder-node` instead - */ -export const createTemplateAction = createTemplateActionNode; - -/** - * @public - * @deprecated Use `TaskSecrets` from `@backstage/plugin-scaffolder-node` instead - */ -export type TaskSecrets = TaskSecretsNode; - -/** - * @public - * @deprecated Use `TemplateAction` from `@backstage/plugin-scaffolder-node` instead - */ -export type TemplateAction = - TemplateActionNode; - -/** - * Options for {@link executeShellCommand}. - * - * @public - * @deprecated Use `ExecuteShellCommandOptions` from `@backstage/plugin-scaffolder-node` instead - */ -export type RunCommandOptions = ExecuteShellCommandOptionsNode; - -/** - * Run a command in a sub-process, normally a shell command. - * - * @public - * @deprecated Use `executeShellCommand` from `@backstage/plugin-scaffolder-node` instead - */ -export const executeShellCommand = executeShellCommandNode; - -/** - * A helper function that reads the contents of a directory from the given URL. - * Can be used in your own actions, and also used behind fetch:template and fetch:plain - * - * @public - * @deprecated Use `fetchContents` from `@backstage/plugin-scaffolder-node` instead - */ -export const fetchContents = fetchContentsNode; - -/** - * Adds support for scaffolder specific entity kinds to the catalog. - * - * @public - * @deprecated Import from `@backstage/plugin-catalog-backend-module-scaffolder-entity-model` instead - */ -export const ScaffolderEntitiesProcessor = ScaffolderEntitiesProcessorModule; diff --git a/plugins/scaffolder-backend/src/index.ts b/plugins/scaffolder-backend/src/index.ts index dddd42d83f..4273ed8197 100644 --- a/plugins/scaffolder-backend/src/index.ts +++ b/plugins/scaffolder-backend/src/index.ts @@ -22,7 +22,7 @@ export { scaffolderPlugin as default } from './ScaffolderPlugin'; export * from './scaffolder'; -export * from './service/router'; -export * from './lib'; - -export * from './deprecated'; +export { + type TemplatePermissionRuleInput, + type ActionPermissionRuleInput, +} from './service/permissions'; diff --git a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts index fea04c0c1c..709cb1308d 100644 --- a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts +++ b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts @@ -17,8 +17,8 @@ import { Isolate } from 'isolated-vm'; import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { - TemplateFilter as _TemplateFilter, - TemplateGlobal as _TemplateGlobal, + TemplateFilter, + TemplateGlobal, } from '@backstage/plugin-scaffolder-node'; import fs from 'fs-extra'; import { JsonValue } from '@backstage/types'; @@ -96,18 +96,6 @@ const { render, renderCompat } = (() => { })(); `; -/** - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TemplateFilter = _TemplateFilter; - -/** - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TemplateGlobal = _TemplateGlobal; - interface SecureTemplaterOptions { /* Enables jinja compatibility and the "jsonify" filter */ cookiecutterCompat?: boolean; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts b/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts index 11d6234083..c819d4c21f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts @@ -19,6 +19,7 @@ import { TemplateAction } from '@backstage/plugin-scaffolder-node'; /** * Registry of all registered template actions. * @public + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. */ export class TemplateActionRegistry { private readonly actions = new Map(); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.examples.test.ts index 4854e39f3b..b10670d8a8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.examples.test.ts @@ -19,7 +19,7 @@ import { Entity } from '@backstage/catalog-model'; import { createFetchCatalogEntityAction } from './fetch'; import { examples } from './fetch.examples'; import yaml from 'yaml'; -import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; describe('catalog:fetch examples', () => { @@ -31,27 +31,19 @@ describe('catalog:fetch examples', () => { }, } as Entity; - const catalogClient = catalogServiceMock({ entities: [entity] }); + const catalogMock = catalogServiceMock({ entities: [entity] }); const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - - const mockContext = createMockActionContext({ - secrets: { backstageToken: token }, - }); + const mockContext = createMockActionContext(); beforeEach(() => { jest.resetAllMocks(); - jest.spyOn(catalogClient, 'getEntityByRef'); + jest.spyOn(catalogMock, 'getEntityByRef'); }); describe('fetch single entity', () => { @@ -61,9 +53,9 @@ describe('catalog:fetch examples', () => { input: yaml.parse(examples[0].example).steps[0].input, }); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith( 'component:default/name', - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith('entity', entity); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.test.ts index e049c7522c..0bea408355 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.test.ts @@ -17,7 +17,7 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { Entity } from '@backstage/catalog-model'; import { createFetchCatalogEntityAction } from './fetch'; -import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; describe('catalog:fetch', () => { @@ -31,14 +31,7 @@ describe('catalog:fetch', () => { const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - - const mockContext = createMockActionContext({ - secrets: { backstageToken: token }, - }); + const mockContext = createMockActionContext(); beforeEach(() => { jest.resetAllMocks(); @@ -46,11 +39,11 @@ describe('catalog:fetch', () => { describe('fetch single entity', () => { it('should return entity from catalog', async () => { - const catalogClient = catalogServiceMock({ entities: [component] }); - jest.spyOn(catalogClient, 'getEntityByRef'); + const catalogMock = catalogServiceMock({ entities: [component] }); + jest.spyOn(catalogMock, 'getEntityByRef'); + const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); await action.handler({ @@ -60,21 +53,20 @@ describe('catalog:fetch', () => { }, }); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith( 'component:default/test', - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith('entity', component); }); it('should throw error if entity fetch fails from catalog and optional is false', async () => { - const catalogClient = catalogServiceMock.mock({ + const catalogMock = catalogServiceMock.mock({ getEntityByRef: () => Promise.reject(new Error('Not found')), }); - jest.spyOn(catalogClient, 'getEntityByRef'); + const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); await expect( @@ -86,19 +78,19 @@ describe('catalog:fetch', () => { }), ).rejects.toThrow('Not found'); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith( 'component:default/test', - { token }, + { credentials }, ); expect(mockContext.output).not.toHaveBeenCalled(); }); it('should throw error if entity not in catalog and optional is false', async () => { - const catalogClient = catalogServiceMock({ entities: [] }); - jest.spyOn(catalogClient, 'getEntityByRef'); + const catalogMock = catalogServiceMock({ entities: [] }); + jest.spyOn(catalogMock, 'getEntityByRef'); + const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); await expect( @@ -110,9 +102,9 @@ describe('catalog:fetch', () => { }), ).rejects.toThrow('Entity component:default/test not found'); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith( 'component:default/test', - { token }, + { credentials }, ); expect(mockContext.output).not.toHaveBeenCalled(); }); @@ -125,11 +117,12 @@ describe('catalog:fetch', () => { namespace: 'ns', }, } as Entity; - const catalogClient = catalogServiceMock({ entities: [entity] }); - jest.spyOn(catalogClient, 'getEntityByRef'); + + const catalogMock = catalogServiceMock({ entities: [entity] }); + jest.spyOn(catalogMock, 'getEntityByRef'); + const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); await action.handler({ @@ -141,21 +134,21 @@ describe('catalog:fetch', () => { }, }); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( - 'group:ns/test', - { token }, - ); + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith('group:ns/test', { + credentials, + }); + expect(mockContext.output).toHaveBeenCalledWith('entity', entity); }); }); describe('fetch multiple entities', () => { it('should return entities from catalog', async () => { - const catalogClient = catalogServiceMock({ entities: [component] }); - jest.spyOn(catalogClient, 'getEntitiesByRefs'); + const catalogMock = catalogServiceMock({ entities: [component] }); + jest.spyOn(catalogMock, 'getEntitiesByRefs'); + const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); await action.handler({ @@ -165,19 +158,18 @@ describe('catalog:fetch', () => { }, }); - expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith( + expect(catalogMock.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['component:default/test'] }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith('entities', [component]); }); it('should throw error if undefined is returned for some entity', async () => { - const catalogClient = catalogServiceMock({ entities: [component] }); - jest.spyOn(catalogClient, 'getEntitiesByRefs'); + const catalogMock = catalogServiceMock({ entities: [component] }); + jest.spyOn(catalogMock, 'getEntitiesByRefs'); const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); await expect( @@ -190,21 +182,21 @@ describe('catalog:fetch', () => { }), ).rejects.toThrow('Entity component:default/test2 not found'); - expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith( + expect(catalogMock.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['component:default/test', 'component:default/test2'], }, - { token }, + { credentials }, ); expect(mockContext.output).not.toHaveBeenCalled(); }); it('should return null in case some of the entities not found and optional is true', async () => { - const catalogClient = catalogServiceMock({ entities: [component] }); - jest.spyOn(catalogClient, 'getEntitiesByRefs'); + const catalogMock = catalogServiceMock({ entities: [component] }); + jest.spyOn(catalogMock, 'getEntitiesByRefs'); + const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); await action.handler({ @@ -215,9 +207,9 @@ describe('catalog:fetch', () => { }, }); - expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith( + expect(catalogMock.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['component:default/test', 'component:default/test2'] }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith('entities', [ component, @@ -240,13 +232,14 @@ describe('catalog:fetch', () => { }, kind: 'User', } as Entity; - const catalogClient = catalogServiceMock({ + + const catalogMock = catalogServiceMock({ entities: [entity1, entity2], }); - jest.spyOn(catalogClient, 'getEntitiesByRefs'); + jest.spyOn(catalogMock, 'getEntitiesByRefs'); + const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); await action.handler({ @@ -258,9 +251,9 @@ describe('catalog:fetch', () => { }, }); - expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith( + expect(catalogMock.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['group:ns/test', 'user:default/test'] }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith('entities', [ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts index e21930ed78..e73c882a9a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts @@ -14,11 +14,10 @@ * limitations under the License. */ -import { CatalogApi } from '@backstage/catalog-client'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model'; import { examples } from './fetch.examples'; -import { AuthService } from '@backstage/backend-plugin-api'; +import { CatalogService } from '@backstage/plugin-catalog-node'; const id = 'catalog:fetch'; @@ -28,10 +27,9 @@ const id = 'catalog:fetch'; * @public */ export function createFetchCatalogEntityAction(options: { - catalogClient: CatalogApi; - auth?: AuthService; + catalog: CatalogService; }) { - const { catalogClient, auth } = options; + const { catalog } = options; return createTemplateAction({ id, @@ -95,18 +93,13 @@ export function createFetchCatalogEntityAction(options: { throw new Error('Missing entity reference or references'); } - const { token } = (await auth?.getPluginRequestToken({ - onBehalfOf: await ctx.getInitiatorCredentials(), - targetPluginId: 'catalog', - })) ?? { token: ctx.secrets?.backstageToken }; - if (entityRef) { - const entity = await catalogClient.getEntityByRef( + const entity = await catalog.getEntityByRef( stringifyEntityRef( parseEntityRef(entityRef, { defaultKind, defaultNamespace }), ), { - token, + credentials: await ctx.getInitiatorCredentials(), }, ); @@ -117,7 +110,7 @@ export function createFetchCatalogEntityAction(options: { } if (entityRefs) { - const entities = await catalogClient.getEntitiesByRefs( + const entities = await catalog.getEntitiesByRefs( { entityRefs: entityRefs.map(ref => stringifyEntityRef( @@ -125,9 +118,7 @@ export function createFetchCatalogEntityAction(options: { ), ), }, - { - token, - }, + { credentials: await ctx.getInitiatorCredentials() }, ); const finalEntities = entities.items.map((e, i) => { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.examples.test.ts index 27690b0df3..b0f17d98c3 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.examples.test.ts @@ -21,7 +21,7 @@ import { createCatalogRegisterAction } from './register'; import { Entity } from '@backstage/catalog-model'; import { examples } from './register.examples'; import yaml from 'yaml'; -import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; describe('catalog:register', () => { @@ -33,28 +33,22 @@ describe('catalog:register', () => { }), ); - const catalogClient = catalogServiceMock.mock(); + const catalogMock = catalogServiceMock.mock(); const action = createCatalogRegisterAction({ integrations, - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - const mockContext = createMockActionContext(); beforeEach(() => { jest.resetAllMocks(); }); it('should register location in catalog', async () => { - catalogClient.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -71,21 +65,22 @@ describe('catalog:register', () => { } as Entity, ], }); + await action.handler({ ...mockContext, input: yaml.parse(examples[0].example).steps[0].input, }); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 1, { type: 'url', target: 'http://github.com/backstage/backstage/blob/master/catalog-info.yaml', }, - { token }, + { credentials }, ); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 2, { dryRun: true, @@ -93,7 +88,7 @@ describe('catalog:register', () => { target: 'http://github.com/backstage/backstage/blob/master/catalog-info.yaml', }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts index 839ec50886..ad84624129 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts @@ -19,7 +19,7 @@ import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { createCatalogRegisterAction } from './register'; import { Entity } from '@backstage/catalog-model'; -import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; describe('catalog:register', () => { @@ -31,21 +31,15 @@ describe('catalog:register', () => { }), ); - const catalogClient = catalogServiceMock.mock(); + const catalogMock = catalogServiceMock.mock(); const action = createCatalogRegisterAction({ integrations, - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - const mockContext = createMockActionContext(); beforeEach(() => { @@ -66,7 +60,7 @@ describe('catalog:register', () => { }); it('should register location in catalog', async () => { - catalogClient.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -90,22 +84,22 @@ describe('catalog:register', () => { }, }); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 1, { type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 2, { dryRun: true, type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith( @@ -119,7 +113,7 @@ describe('catalog:register', () => { }); it('should return entityRef with the Component entity and not the generated location', async () => { - catalogClient.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -170,7 +164,7 @@ describe('catalog:register', () => { }); it('should return entityRef with the next non-generated entity if no Component kind can be found', async () => { - catalogClient.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -214,7 +208,7 @@ describe('catalog:register', () => { }); it('should return entityRef with the first entity if no non-generated entities can be found', async () => { - catalogClient.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -251,7 +245,7 @@ describe('catalog:register', () => { }); it('should not return entityRef if there are no entities', async () => { - catalogClient.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -273,7 +267,7 @@ describe('catalog:register', () => { }); it('should ignore failures when dry running the location in the catalog if `optional` is set', async () => { - catalogClient.addLocation + catalogMock.addLocation .mockRejectedValueOnce(new Error('Not found')) .mockRejectedValueOnce(new Error('Not found')); await action.handler({ @@ -284,22 +278,22 @@ describe('catalog:register', () => { }, }); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 1, { type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 2, { dryRun: true, type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith( @@ -309,7 +303,7 @@ describe('catalog:register', () => { }); it('should fetch entities when adding location in the catalog fails and `optional` is set', async () => { - catalogClient.addLocation + catalogMock.addLocation .mockRejectedValueOnce(new Error('Already registered')) .mockResolvedValueOnce({ location: null as any, @@ -331,22 +325,22 @@ describe('catalog:register', () => { }, }); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 1, { type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 2, { dryRun: true, type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts index eab31f14df..65102d8375 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts @@ -16,11 +16,10 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; -import { CatalogApi } from '@backstage/catalog-client'; import { stringifyEntityRef, Entity } from '@backstage/catalog-model'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { examples } from './register.examples'; -import { AuthService } from '@backstage/backend-plugin-api'; +import { CatalogService } from '@backstage/plugin-catalog-node'; const id = 'catalog:register'; @@ -29,79 +28,50 @@ const id = 'catalog:register'; * @public */ export function createCatalogRegisterAction(options: { - catalogClient: CatalogApi; + catalog: CatalogService; integrations: ScmIntegrations; - auth?: AuthService; }) { - const { catalogClient, integrations, auth } = options; + const { catalog, integrations } = options; - return createTemplateAction< - | { catalogInfoUrl: string; optional?: boolean } - | { repoContentsUrl: string; catalogInfoPath?: string; optional?: boolean } - >({ + return createTemplateAction({ id, description: 'Registers entities from a catalog descriptor file in the workspace into the software catalog.', examples, schema: { - input: { - oneOf: [ - { - type: 'object', - required: ['catalogInfoUrl'], - properties: { - catalogInfoUrl: { - title: 'Catalog Info URL', - description: - 'An absolute URL pointing to the catalog info file location', - type: 'string', - }, - optional: { - title: 'Optional', + input: z => + z.union([ + z.object({ + catalogInfoUrl: z.string({ + description: + 'An absolute URL pointing to the catalog info file location', + }), + optional: z + .boolean({ description: 'Permit the registered location to optionally exist. Default: false', - type: 'boolean', - }, - }, - }, - { - type: 'object', - required: ['repoContentsUrl'], - properties: { - repoContentsUrl: { - title: 'Repository Contents URL', - description: - 'An absolute URL pointing to the root of a repository directory tree', - type: 'string', - }, - catalogInfoPath: { - title: 'Fetch URL', + }) + .optional(), + }), + z.object({ + repoContentsUrl: z.string({ + description: + 'An absolute URL pointing to the root of a repository directory tree', + }), + catalogInfoPath: z + .string({ description: 'A relative path from the repo root pointing to the catalog info file, defaults to /catalog-info.yaml', - type: 'string', - }, - optional: { - title: 'Optional', + }) + .optional(), + optional: z + .boolean({ description: 'Permit the registered location to optionally exist. Default: false', - type: 'boolean', - }, - }, - }, - ], - }, - output: { - type: 'object', - required: ['catalogInfoUrl'], - properties: { - entityRef: { - type: 'string', - }, - catalogInfoUrl: { - type: 'string', - }, - }, - }, + }) + .optional(), + }), + ]), }, async handler(ctx) { const { input } = ctx; @@ -127,19 +97,14 @@ export function createCatalogRegisterAction(options: { ctx.logger.info(`Registering ${catalogInfoUrl} in the catalog`); - const { token } = (await auth?.getPluginRequestToken({ - onBehalfOf: await ctx.getInitiatorCredentials(), - targetPluginId: 'catalog', - })) ?? { token: ctx.secrets?.backstageToken }; - try { // 1st try to register the location, this will throw an error if the location already exists (see catch) - await catalogClient.addLocation( + await catalog.addLocation( { type: 'url', target: catalogInfoUrl, }, - token ? { token } : {}, + { credentials: await ctx.getInitiatorCredentials() }, ); } catch (e) { if (!input.optional) { @@ -150,13 +115,13 @@ export function createCatalogRegisterAction(options: { try { // 2nd retry the registration as a dry run, this will not throw an error if the location already exists - const result = await catalogClient.addLocation( + const result = await catalog.addLocation( { dryRun: true, type: 'url', target: catalogInfoUrl, }, - token ? { token } : {}, + { credentials: await ctx.getInitiatorCredentials() }, ); if (result.entities.length) { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts index 29ce6c986d..e01cc3b88a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts @@ -18,7 +18,6 @@ import fs from 'fs-extra'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import * as yaml from 'yaml'; import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; -import { z } from 'zod'; import { examples } from './write.examples'; const id = 'catalog:write'; @@ -33,18 +32,17 @@ export function createCatalogWriteAction() { id, description: 'Writes the catalog-info.yaml for your template', schema: { - input: z.object({ - filePath: z - .string() - .optional() - .describe('Defaults to catalog-info.yaml'), + input: { + filePath: z => + z.string().optional().describe('Defaults to catalog-info.yaml'), // TODO: this should reference an zod entity validator if it existed. - entity: z - .record(z.any()) - .describe( - 'You can provide the same values used in the Entity schema.', - ), - }), + entity: z => + z + .record(z.any()) + .describe( + 'You can provide the same values used in the Entity schema.', + ), + }, }, examples, supportsDryRun: true, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts deleted file mode 100644 index d2a03412cc..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { CatalogApi } from '@backstage/catalog-client'; -import { Config } from '@backstage/config'; -import { - DefaultGithubCredentialsProvider, - GithubCredentialsProvider, - ScmIntegrations, -} from '@backstage/integration'; -import { - TemplateAction, - TemplateFilter, - TemplateGlobal, -} from '@backstage/plugin-scaffolder-node'; -import { - createCatalogRegisterAction, - createCatalogWriteAction, - createFetchCatalogEntityAction, -} from './catalog'; - -import { createDebugLogAction, createWaitAction } from './debug'; -import { - createFetchPlainAction, - createFetchPlainFileAction, - createFetchTemplateAction, - createFetchTemplateFileAction, -} from './fetch'; -import { - createFilesystemDeleteAction, - createFilesystemReadDirAction, - createFilesystemRenameAction, -} from './filesystem'; -import { - createGithubActionsDispatchAction, - createGithubAutolinksAction, - createGithubDeployKeyAction, - createGithubEnvironmentAction, - createGithubIssuesLabelAction, - createGithubRepoCreateAction, - createGithubRepoPushAction, - createGithubWebhookAction, - createPublishGithubAction, - createPublishGithubPullRequestAction, -} from '@backstage/plugin-scaffolder-backend-module-github'; - -import { createPublishAzureAction } from '@backstage/plugin-scaffolder-backend-module-azure'; - -import { createPublishBitbucketAction } from '@backstage/plugin-scaffolder-backend-module-bitbucket'; - -import { - createPublishBitbucketCloudAction, - createBitbucketPipelinesRunAction, - createPublishBitbucketCloudPullRequestAction, -} from '@backstage/plugin-scaffolder-backend-module-bitbucket-cloud'; - -import { - createPublishBitbucketServerAction, - createPublishBitbucketServerPullRequestAction, -} from '@backstage/plugin-scaffolder-backend-module-bitbucket-server'; - -import { - createPublishGerritAction, - createPublishGerritReviewAction, -} from '@backstage/plugin-scaffolder-backend-module-gerrit'; - -import { - createPublishGitlabAction, - createGitlabRepoPushAction, - createPublishGitlabMergeRequestAction, -} from '@backstage/plugin-scaffolder-backend-module-gitlab'; - -import { createPublishGiteaAction } from '@backstage/plugin-scaffolder-backend-module-gitea'; -import { AuthService, UrlReaderService } from '@backstage/backend-plugin-api'; - -/** - * The options passed to {@link createBuiltinActions} - * @public - */ -export interface CreateBuiltInActionsOptions { - /** - * The {@link @backstage/backend-plugin-api#UrlReaderService} interface that will be used in the default actions. - */ - reader: UrlReaderService; - /** - * The {@link @backstage/integrations#ScmIntegrations} that will be used in the default actions. - */ - integrations: ScmIntegrations; - /** - * The {@link @backstage/catalog-client#CatalogApi} that will be used in the default actions. - */ - catalogClient: CatalogApi; - /** - * The {@link @backstage/backend-plugin-api#AuthService} that will be used in the default actions. - */ - auth?: AuthService; - /** - * The {@link @backstage/config#Config} that will be used in the default actions. - */ - config: Config; - /** - * Additional custom filters that will be passed to the nunjucks template engine for use in - * Template Manifests and also template skeleton files when using `fetch:template`. - */ - additionalTemplateFilters?: Record; - additionalTemplateGlobals?: Record; -} - -/** - * A function to generate create a list of default actions that the scaffolder provides. - * Is called internally in the default setup, but can be used when adding your own actions or overriding the default ones - * - * TODO(blam): version 2 of the scaffolder shouldn't ship with the additional modules. We should ship the basics, and let people install - * modules for the providers they want to use. - * @public - * @returns A list of actions that can be used in the scaffolder - * - */ -export const createBuiltinActions = ( - options: CreateBuiltInActionsOptions, -): TemplateAction[] => { - const { - reader, - integrations, - catalogClient, - auth, - config, - additionalTemplateFilters, - additionalTemplateGlobals, - } = options; - - const githubCredentialsProvider: GithubCredentialsProvider = - DefaultGithubCredentialsProvider.fromIntegrations(integrations); - - const actions = [ - createFetchPlainAction({ - reader, - integrations, - }), - createFetchPlainFileAction({ - reader, - integrations, - }), - createFetchTemplateAction({ - integrations, - reader, - additionalTemplateFilters, - additionalTemplateGlobals, - }), - createFetchTemplateFileAction({ - integrations, - reader, - additionalTemplateFilters, - additionalTemplateGlobals, - }), - createPublishGerritAction({ - integrations, - config, - }), - createPublishGerritReviewAction({ - integrations, - config, - }), - createPublishGiteaAction({ - integrations, - config, - }), - createPublishGithubAction({ - integrations, - config, - githubCredentialsProvider, - }), - createPublishGithubPullRequestAction({ - integrations, - githubCredentialsProvider, - config, - }), - createPublishGitlabAction({ - integrations, - config, - }), - createPublishGitlabMergeRequestAction({ - integrations, - }), - createGitlabRepoPushAction({ - integrations, - }), - createPublishBitbucketAction({ - integrations, - config, - }), - createPublishBitbucketCloudAction({ - integrations, - config, - }), - createPublishBitbucketCloudPullRequestAction({ integrations, config }), - createPublishBitbucketServerAction({ - integrations, - config, - }), - createPublishBitbucketServerPullRequestAction({ - integrations, - config, - }), - createPublishAzureAction({ - integrations, - config, - }), - createDebugLogAction(), - createWaitAction(), - createCatalogRegisterAction({ catalogClient, integrations, auth }), - createFetchCatalogEntityAction({ catalogClient, auth }), - createCatalogWriteAction(), - createFilesystemDeleteAction(), - createFilesystemReadDirAction(), - createFilesystemRenameAction(), - createGithubActionsDispatchAction({ - integrations, - githubCredentialsProvider, - }), - createGithubWebhookAction({ - integrations, - githubCredentialsProvider, - }), - createGithubIssuesLabelAction({ - integrations, - githubCredentialsProvider, - }), - createGithubRepoCreateAction({ - integrations, - githubCredentialsProvider, - }), - createGithubRepoPushAction({ - integrations, - config, - githubCredentialsProvider, - }), - createGithubEnvironmentAction({ - integrations, - catalogClient, - }), - createGithubDeployKeyAction({ - integrations, - }), - createGithubAutolinksAction({ - integrations, - githubCredentialsProvider, - }), - createBitbucketPipelinesRunAction({ - integrations, - }), - ]; - - return actions as TemplateAction[]; -}; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts index 2ff64e1025..6eae1794f8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts @@ -19,7 +19,6 @@ import { join, relative } from 'path'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { examples } from './log.examples'; import fs from 'fs'; -import { z } from 'zod'; const id = 'debug:log'; @@ -34,24 +33,23 @@ const id = 'debug:log'; * @public */ export function createDebugLogAction() { - return createTemplateAction<{ - message?: string; - listWorkspace?: boolean | 'with-filenames' | 'with-contents'; - }>({ + return createTemplateAction({ id, description: 'Writes a message into the log and/or lists all files in the workspace.', examples, schema: { - input: z.object({ - message: z.string({ description: 'Message to output.' }).optional(), - listWorkspace: z - .union([z.boolean(), z.enum(['with-filenames', 'with-contents'])], { - description: - 'List all files in the workspace. If used with "with-contents", also the file contents are listed.', - }) - .optional(), - }), + input: { + message: z => + z.string({ description: 'Message to output.' }).optional(), + listWorkspace: z => + z + .union([z.boolean(), z.enum(['with-filenames', 'with-contents'])], { + description: + 'List all files in the workspace. If used with "with-contents", also the file contents are listed.', + }) + .optional(), + }, }, supportsDryRun: true, async handler(ctx) { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.examples.test.ts index 8bc29a74c9..6ffd338a11 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.examples.test.ts @@ -15,7 +15,6 @@ */ import { createWaitAction } from './wait'; -import { Writable } from 'stream'; import { examples } from './wait.examples'; import yaml from 'yaml'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; @@ -23,13 +22,7 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test- describe('debug:wait examples', () => { const action = createWaitAction(); - const logStream = { - write: jest.fn(), - } as jest.Mocked> as jest.Mocked; - - const mockContext = createMockActionContext({ - logStream, - }); + const mockContext = createMockActionContext(); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.test.ts index fc688eee44..919cb9faee 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.test.ts @@ -15,19 +15,12 @@ */ import { createWaitAction } from './wait'; -import { Writable } from 'stream'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; describe('debug:wait', () => { const action = createWaitAction(); - const logStream = { - write: jest.fn(), - } as jest.Mocked> as jest.Mocked; - - const mockContext = createMockActionContext({ - logStream, - }); + const mockContext = createMockActionContext(); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts index fd4a77754b..e048720f29 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts @@ -48,27 +48,30 @@ export function createWaitAction(options?: { return Duration.fromISOTime(MAX_WAIT_TIME_IN_ISO); }; - return createTemplateAction({ + return createTemplateAction({ id, description: 'Waits for a certain period of time.', examples, schema: { input: { - type: 'object', - properties: { - minutes: { - title: 'Waiting period in minutes.', - type: 'number', - }, - seconds: { - title: 'Waiting period in seconds.', - type: 'number', - }, - milliseconds: { - title: 'Waiting period in milliseconds.', - type: 'number', - }, - }, + minutes: z => + z + .number({ + description: 'Waiting period in minutes.', + }) + .optional(), + seconds: z => + z + .number({ + description: 'Waiting period in seconds.', + }) + .optional(), + milliseconds: z => + z + .number({ + description: 'Waiting period in milliseconds.', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts index a4f795dd54..c0bed9bea7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts @@ -39,39 +39,32 @@ export function createFetchPlainAction(options: { }) { const { reader, integrations } = options; - return createTemplateAction<{ - url: string; - targetPath?: string; - token?: string; - }>({ + return createTemplateAction({ id: ACTION_ID, examples, description: 'Downloads content and places it in the workspace, or optionally in a subdirectory specified by the `targetPath` input option.', schema: { input: { - type: 'object', - required: ['url'], - properties: { - url: { - title: 'Fetch URL', + url: z => + z.string({ description: 'Relative path or absolute URL pointing to the directory tree to fetch', - type: 'string', - }, - targetPath: { - title: 'Target Path', - description: - 'Target path within the working directory to download the contents to.', - type: 'string', - }, - token: { - title: 'Token', - description: - 'An optional token to use for authentication when reading the resources.', - type: 'string', - }, - }, + }), + targetPath: z => + z + .string({ + description: + 'Target path within the working directory to download the contents to.', + }) + .optional(), + token: z => + z + .string({ + description: + 'An optional token to use for authentication when reading the resources.', + }) + .optional(), }, }, supportsDryRun: true, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts index a07c9bfc6b..c132214c10 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts @@ -14,18 +14,20 @@ * limitations under the License. */ -import { UrlReaderService } from '@backstage/backend-plugin-api'; -import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; +import { + resolveSafeChildPath, + UrlReaderService, +} from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; import { examples } from './plainFile.examples'; + import { createTemplateAction, fetchFile, } from '@backstage/plugin-scaffolder-node'; /** - * Downloads content and places it in the workspace, or optionally - * in a subdirectory specified by the 'targetPath' input option. + * Downloads a single file and places it in the workspace. * @public */ export function createFetchPlainFileAction(options: { @@ -34,38 +36,29 @@ export function createFetchPlainFileAction(options: { }) { const { reader, integrations } = options; - return createTemplateAction<{ - url: string; - targetPath: string; - token?: string; - }>({ + return createTemplateAction({ id: 'fetch:plain:file', description: 'Downloads single file and places it in the workspace.', examples, schema: { input: { - type: 'object', - required: ['url', 'targetPath'], - properties: { - url: { - title: 'Fetch URL', + url: z => + z.string({ description: 'Relative path or absolute URL pointing to the single file to fetch.', - type: 'string', - }, - targetPath: { - title: 'Target Path', + }), + targetPath: z => + z.string({ description: 'Target path within the working directory to download the file as.', - type: 'string', - }, - token: { - title: 'Token', - description: - 'An optional token to use for authentication when reading the resources.', - type: 'string', - }, - }, + }), + token: z => + z + .string({ + description: + 'An optional token to use for authentication when reading the resources.', + }) + .optional(), }, }, supportsDryRun: true, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts index 61918e8450..6494e562d2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts @@ -98,42 +98,6 @@ describe('fetch:template', () => { ); }); - it('throws if copyWithoutRender parameter is not an array', async () => { - await expect(() => - action.handler( - mockContext({ copyWithoutRender: 'abc' as unknown as string[] }), - ), - ).rejects.toThrow( - /copyWithoutRender\/copyWithoutTemplating must be an array/i, - ); - }); - - it('throws if both copyWithoutRender and copyWithoutTemplating are used', async () => { - await expect(() => - action.handler( - mockContext({ - copyWithoutRender: 'abc' as unknown as string[], - copyWithoutTemplating: 'def' as unknown as string[], - }), - ), - ).rejects.toThrow( - /copyWithoutRender and copyWithoutTemplating can not be used at the same time/i, - ); - }); - - it('throws if copyWithoutRender is used with extension', async () => { - await expect(() => - action.handler( - mockContext({ - copyWithoutRender: ['abc'], - templateFileExtension: true, - }), - ), - ).rejects.toThrow( - /input extension incompatible with copyWithoutRender\/copyWithoutTemplating and cookiecutterCompat/, - ); - }); - it('throws if cookiecutterCompat is used with extension', async () => { await expect(() => action.handler( @@ -143,7 +107,7 @@ describe('fetch:template', () => { }), ), ).rejects.toThrow( - /input extension incompatible with copyWithoutRender\/copyWithoutTemplating and cookiecutterCompat/, + /input extension incompatible with copyWithoutTemplating and cookiecutterCompat/, ); }); @@ -378,55 +342,6 @@ describe('fetch:template', () => { }); }); - describe('copyWithoutRender', () => { - let context: ActionContext; - - beforeEach(async () => { - context = mockContext({ - values: { - name: 'test-project', - count: 1234, - }, - copyWithoutRender: ['.unprocessed'], - }); - - mockFetchContents.mockImplementation(({ outputPath }) => { - mockDir.setContent({ - [outputPath]: { - processed: { - 'templated-content-${{ values.name }}.txt': '${{ values.count }}', - }, - '.unprocessed': { - 'templated-content-${{ values.name }}.txt': '${{ values.count }}', - }, - }, - }); - - return Promise.resolve(); - }); - - await action.handler(context); - }); - - it('ignores template syntax in files matched in copyWithoutRender', async () => { - await expect( - fs.readFile( - `${workspacePath}/target/.unprocessed/templated-content-\${{ values.name }}.txt`, - 'utf-8', - ), - ).resolves.toEqual('${{ values.count }}'); - }); - - it('processes files not matched in copyWithoutRender', async () => { - await expect( - fs.readFile( - `${workspacePath}/target/processed/templated-content-test-project.txt`, - 'utf-8', - ), - ).resolves.toEqual('1234'); - }); - }); - describe('copyWithoutTemplating', () => { let context: ActionContext; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index 4a80cd208d..159bd1c4a2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -41,93 +41,87 @@ export function createFetchTemplateAction(options: { additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; }) { - return createTemplateAction<{ - url: string; - targetPath?: string; - values: any; - templateFileExtension?: string | boolean; - - // Cookiecutter compat options - /** - * @deprecated This field is deprecated in favor of copyWithoutTemplating. - */ - copyWithoutRender?: string[]; - copyWithoutTemplating?: string[]; - cookiecutterCompat?: boolean; - replace?: boolean; - trimBlocks?: boolean; - lstripBlocks?: boolean; - token?: string; - }>({ + return createTemplateAction({ id: 'fetch:template', description: 'Downloads a skeleton, templates variables into file and directory names and content, and places the result in the workspace, or optionally in a subdirectory specified by the `targetPath` input option.', examples, schema: { input: { - type: 'object', - required: ['url'], - properties: { - url: { - title: 'Fetch URL', + url: z => + z.string({ description: 'Relative path or absolute URL pointing to the directory tree to fetch', - type: 'string', - }, - targetPath: { - title: 'Target Path', - description: - 'Target path within the working directory to download the contents to. Defaults to the working directory root.', - type: 'string', - }, - values: { - title: 'Template Values', - description: 'Values to pass on to the templating engine', - type: 'object', - }, - copyWithoutRender: { - title: '[Deprecated] Copy Without Render', - description: - 'An array of glob patterns. Any files or directories which match are copied without being processed as templates.', - type: 'array', - items: { - type: 'string', - }, - }, - copyWithoutTemplating: { - title: 'Copy Without Templating', - description: - 'An array of glob patterns. Contents of matched files or directories are copied without being processed, but paths are subject to rendering.', - type: 'array', - items: { - type: 'string', - }, - }, - cookiecutterCompat: { - title: 'Cookiecutter compatibility mode', - description: - 'Enable features to maximise compatibility with templates built for fetch:cookiecutter', - type: 'boolean', - }, - templateFileExtension: { - title: 'Template File Extension', - description: - 'If set, only files with the given extension will be templated. If set to `true`, the default extension `.njk` is used.', - type: ['string', 'boolean'], - }, - replace: { - title: 'Replace files', - description: - 'If set, replace files in targetPath instead of skipping existing ones.', - type: 'boolean', - }, - token: { - title: 'Token', - description: - 'An optional token to use for authentication when reading the resources.', - type: 'string', - }, - }, + }), + targetPath: z => + z + .string({ + description: + 'Target path within the working directory to download the contents to. Defaults to the working directory root.', + }) + .optional(), + values: z => + z + .record(z.any(), { + description: 'Values to pass on to the templating engine', + }) + .optional(), + copyWithoutRender: z => + z + .array(z.string(), { + description: + 'An array of glob patterns. Any files or directories which match are copied without being processed as templates.', + }) + .optional(), + copyWithoutTemplating: z => + z + .array(z.string(), { + description: + 'An array of glob patterns. Contents of matched files or directories are copied without being processed, but paths are subject to rendering.', + }) + .optional(), + cookiecutterCompat: z => + z + .boolean({ + description: + 'Enable features to maximise compatibility with templates built for fetch:cookiecutter', + }) + .optional(), + templateFileExtension: z => + z + .union([z.string(), z.boolean()], { + description: + 'If set, only files with the given extension will be templated. If set to `true`, the default extension `.njk` is used.', + }) + .optional(), + replace: z => + z + .boolean({ + description: + 'If set, replace files in targetPath instead of skipping existing ones.', + }) + .optional(), + trimBlocks: z => + z + .boolean({ + description: + 'If set, the first newline after a block is removed (block, not variable tag).', + }) + .optional(), + lstripBlocks: z => + z + .boolean({ + description: + 'If set, leading spaces and tabs are stripped from the start of a line to a block.', + }) + .optional(), + token: z => + z + .string({ + description: + 'An optional token to use for authentication when reading the resources.', + }) + .optional(), }, }, supportsDryRun: true, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateActionHandler.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateActionHandler.ts index f280841bfa..e436616651 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateActionHandler.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateActionHandler.ts @@ -36,12 +36,6 @@ export type TemplateActionInput = { targetPath?: string; values: any; templateFileExtension?: string | boolean; - - // Cookiecutter compat options - /** - * @deprecated This field is deprecated in favor of copyWithoutTemplating. - */ - copyWithoutRender?: string[]; copyWithoutTemplating?: string[]; cookiecutterCompat?: boolean; replace?: boolean; @@ -206,26 +200,12 @@ function resolveTemplateActionSettings( const targetPath = ctx.input.targetPath ?? './'; const outputDir = resolveSafeChildPath(ctx.workspacePath, targetPath); - if (ctx.input.copyWithoutRender && ctx.input.copyWithoutTemplating) { - throw new InputError( - 'Fetch action input copyWithoutRender and copyWithoutTemplating can not be used at the same time', - ); - } - let copyOnlyPatterns: string[] | undefined; - let renderFilename: boolean; - if (ctx.input.copyWithoutRender) { - ctx.logger.warn( - '[Deprecated] copyWithoutRender is deprecated Please use copyWithoutTemplating instead.', - ); - copyOnlyPatterns = ctx.input.copyWithoutRender; - renderFilename = false; - } else { - copyOnlyPatterns = ctx.input.copyWithoutTemplating; - renderFilename = true; - } + const copyOnlyPatterns = ctx.input.copyWithoutTemplating; + const renderFilename = true; + if (copyOnlyPatterns && !Array.isArray(copyOnlyPatterns)) { throw new InputError( - 'Fetch action input copyWithoutRender/copyWithoutTemplating must be an Array', + 'Fetch action input copyWithoutTemplating must be an Array', ); } if ( @@ -233,7 +213,7 @@ function resolveTemplateActionSettings( (copyOnlyPatterns || ctx.input.cookiecutterCompat) ) { throw new InputError( - 'Fetch action input extension incompatible with copyWithoutRender/copyWithoutTemplating and cookiecutterCompat', + 'Fetch action input extension incompatible with copyWithoutTemplating and cookiecutterCompat', ); } let extension: string | false = false; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts index e70e9435c5..2cfe16f900 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts @@ -38,60 +38,63 @@ export function createFetchTemplateFileAction(options: { additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; }) { - return createTemplateAction<{ - url: string; - targetPath: string; - values: any; - cookiecutterCompat?: boolean; - replace?: boolean; - trimBlocks?: boolean; - lstripBlocks?: boolean; - token?: string; - }>({ + return createTemplateAction({ id: 'fetch:template:file', description: 'Downloads single file and places it in the workspace.', examples, schema: { input: { - type: 'object', - required: ['url', 'targetPath'], - properties: { - url: { - title: 'Fetch URL', + url: z => + z.string({ description: 'Relative path or absolute URL pointing to the single file to fetch.', - type: 'string', - }, - targetPath: { - title: 'Target Path', + }), + targetPath: z => + z.string({ description: 'Target path within the working directory to download the file as.', - type: 'string', - }, - values: { - title: 'Template Values', - description: 'Values to pass on to the templating engine', - type: 'object', - }, - cookiecutterCompat: { - title: 'Cookiecutter compatibility mode', - description: - 'Enable features to maximise compatibility with templates built for fetch:cookiecutter', - type: 'boolean', - }, - replace: { - title: 'Replace file', - description: - 'If set, replace file in targetPath instead of overwriting existing one.', - type: 'boolean', - }, - token: { - title: 'Token', - description: - 'An optional token to use for authentication when reading the resources.', - type: 'string', - }, - }, + }), + values: z => + z + .record(z.any(), { + description: 'Values to pass on to the templating engine', + }) + .optional(), + cookiecutterCompat: z => + z + .boolean({ + description: + 'Enable features to maximise compatibility with templates built for fetch:cookiecutter', + }) + .optional(), + replace: z => + z + .boolean({ + description: + 'If set, replace file in targetPath instead of overwriting existing one.', + }) + .optional(), + trimBlocks: z => + z + .boolean({ + description: + 'If set, the first newline after a block is removed (block, not variable tag).', + }) + .optional(), + lstripBlocks: z => + z + .boolean({ + description: + 'If set, leading spaces and tabs are stripped from the start of a line to a block.', + }) + .optional(), + token: z => + z + .string({ + description: + 'An optional token to use for authentication when reading the resources.', + }) + .optional(), }, }, supportsDryRun: true, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/workspaceTemplate.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/workspaceTemplate.test.ts index 92b17aa2c9..2a9b4ef68e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/workspaceTemplate.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/workspaceTemplate.test.ts @@ -92,7 +92,7 @@ describe('workspace:template', () => { }), ), ).rejects.toThrow( - /input extension incompatible with copyWithoutRender\/copyWithoutTemplating and cookiecutterCompat/, + /input extension incompatible with copyWithoutTemplating and cookiecutterCompat/, ); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts index 5f894a4b3e..0290414323 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts @@ -26,24 +26,16 @@ import { examples } from './delete.examples'; * @public */ export const createFilesystemDeleteAction = () => { - return createTemplateAction<{ files: string[] }>({ + return createTemplateAction({ id: 'fs:delete', description: 'Deletes files and directories from the workspace', examples, schema: { input: { - required: ['files'], - type: 'object', - properties: { - files: { - title: 'Files', + files: z => + z.array(z.string(), { description: 'A list of files and directories that will be deleted', - type: 'array', - items: { - type: 'string', - }, - }, - }, + }), }, }, supportsDryRun: true, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/read.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/read.ts index 0351e7cb0e..c41e6f1aac 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 './rename.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; +const contentSchema = (z: typeof zod) => + z.object({ + name: z.string().describe('Name of the file or directory'), + path: z + .string() + .describe('path to the file or directory relative to the workspace'), + fullPath: z.string().describe('full path to the file or directory'), + }); + +type Content = zod.infer>; /** * Creates new action that enables reading directories in the workspace. @@ -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..9d4494c536 100644 --- a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts @@ -17,6 +17,7 @@ import { AuditorService, BackstageCredentials, + LoggerService, } from '@backstage/backend-plugin-api'; import type { UserEntity } from '@backstage/catalog-model'; import { ScmIntegrations } from '@backstage/integration'; @@ -36,7 +37,6 @@ import fs from 'fs-extra'; import path from 'path'; import { fileURLToPath } from 'url'; import { v4 as uuid } from 'uuid'; -import { Logger } from 'winston'; import { TemplateActionRegistry } from '../actions'; import { NunjucksWorkflowRunner } from '../tasks/NunjucksWorkflowRunner'; import { DecoratedActionsRegistry } from './DecoratedActionsRegistry'; @@ -61,7 +61,7 @@ interface DryRunResult { /** @internal */ export type TemplateTesterCreateOptions = { - logger: Logger; + logger: LoggerService; auditor?: AuditorService; integrations: ScmIntegrations; actionRegistry: TemplateActionRegistry; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index 51a30a20ec..f81a79a5a0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -76,7 +76,7 @@ export type RawDbTaskEventRow = { /** * DatabaseTaskStore - * + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. * @public */ export type DatabaseTaskStoreOptions = { @@ -86,9 +86,7 @@ export type DatabaseTaskStoreOptions = { /** * Type guard to help DatabaseTaskStore understand when database is DatabaseService vs. when database is a Knex instance. - * - * * @public - */ + * */ function isDatabaseService( opt: DatabaseService | Knex, ): opt is DatabaseService { @@ -111,7 +109,7 @@ const parseSqlDateToIsoString = (input: T): T | string => { /** * DatabaseTaskStore - * + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. * @public */ export class DatabaseTaskStore implements TaskStore { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 2d6ed2d57c..79a847c46d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -23,11 +23,9 @@ import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { createTemplateAction, TaskSecrets, - TemplateAction, TaskContext, } from '@backstage/plugin-scaffolder-node'; import { UserEntity } from '@backstage/catalog-model'; -import { z } from 'zod'; import { AuthorizeResult, PermissionEvaluator, @@ -38,17 +36,15 @@ import { mockCredentials, mockServices, } from '@backstage/backend-test-utils'; -import { LoggerService } from '@backstage/backend-plugin-api'; -import { loggerToWinstonLogger } from '../../util/loggerToWinstonLogger'; describe('NunjucksWorkflowRunner', () => { - let logger: LoggerService; - let actionRegistry = new TemplateActionRegistry(); + let actionRegistry: TemplateActionRegistry; let runner: NunjucksWorkflowRunner; let fakeActionHandler: jest.Mock; let fakeTaskLog: jest.Mock; let stripAnsi: typeof import('strip-ansi').default; + const logger = mockServices.logger.mock(); const mockDir = createMockDirectory(); const mockedPermissionApi: jest.Mocked = { @@ -71,11 +67,21 @@ describe('NunjucksWorkflowRunner', () => { }); const createMockTaskWithSpec = ( - spec: TaskSpec, + { + apiVersion = 'scaffolder.backstage.io/v1beta3', + output = {}, + parameters = {}, + ...spec + }: Partial, secrets?: TaskSecrets, isDryRun?: boolean, ): TaskContext => ({ - spec, + spec: { + apiVersion, + output, + parameters, + ...spec, + } as TaskSpec, secrets, isDryRun, complete: async () => {}, @@ -98,8 +104,6 @@ describe('NunjucksWorkflowRunner', () => { // This one is ESM-only stripAnsi = await import('strip-ansi').then(m => m.default); - jest.resetAllMocks(); - logger = mockServices.logger.mock(); actionRegistry = new TemplateActionRegistry(); fakeActionHandler = jest.fn(); fakeTaskLog = jest.fn(); @@ -120,32 +124,12 @@ describe('NunjucksWorkflowRunner', () => { handler: fakeActionHandler, schema: { input: { - type: 'object', - required: ['foo'], - properties: { - foo: { - type: 'number', - }, - }, + foo: z => z.number(), }, }, }), ); - actionRegistry.register( - createTemplateAction({ - id: 'jest-legacy-zod-validated-action', - description: 'Mock action for testing', - handler: fakeActionHandler, - supportsDryRun: true, - schema: { - input: z.object({ - foo: z.number(), - }), - }, - }) as TemplateAction, - ); - actionRegistry.register( createTemplateAction({ id: 'jest-zod-validated-action', @@ -179,13 +163,14 @@ describe('NunjucksWorkflowRunner', () => { id: 'checkpoints-action', description: 'Mock action with checkpoints', schema: { - output: z.object({ - key1: z.string(), - key2: z.string(), - key3: z.string(), - key4: z.string(), - key5: z.string(), - }), + output: z => + z.object({ + key1: z.string(), + key2: z.string(), + key3: z.string(), + key4: z.string(), + key5: z.string(), + }), }, handler: async ctx => { const key1 = await ctx.checkpoint({ @@ -215,7 +200,9 @@ describe('NunjucksWorkflowRunner', () => { ctx.output('key2', key2); ctx.output('key3', key3); + // @ts-expect-error - not valid output type ctx.output('key4', key4); + // @ts-expect-error - not valid output type ctx.output('key5', key5); }, }), @@ -229,16 +216,19 @@ describe('NunjucksWorkflowRunner', () => { actionRegistry, integrations, workingDirectory: mockDir.path, - logger: loggerToWinstonLogger(logger), + logger, permissions: mockedPermissionApi, }); }); + afterEach(() => { + mockDir.clear(); + + jest.resetAllMocks(); + }); + it('should throw an error if the action does not exist', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [{ id: 'test', name: 'name', action: 'does-not-exist' }], }); @@ -250,9 +240,6 @@ describe('NunjucksWorkflowRunner', () => { describe('validation', () => { it('should throw an error if the action has a schema and the input does not match', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [{ id: 'test', name: 'name', action: 'jest-validated-action' }], }); @@ -263,9 +250,6 @@ describe('NunjucksWorkflowRunner', () => { it('should throw an error if the action has a zod schema and the input does not match', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', name: 'name', action: 'jest-zod-validated-action' }, ], @@ -276,30 +260,8 @@ describe('NunjucksWorkflowRunner', () => { ); }); - it('should throw an error if the action has legacy zod schema and the input does not match', async () => { - const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, - steps: [ - { - id: 'test', - name: 'name', - action: 'jest-legacy-zod-validated-action', - }, - ], - }); - - await expect(runner.execute(task)).rejects.toThrow( - /Invalid input passed to action jest-legacy-zod-validated-action, instance requires property \"foo\"/, - ); - }); - it('should run the action when the zod validation passes', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -315,31 +277,8 @@ describe('NunjucksWorkflowRunner', () => { expect(fakeActionHandler).toHaveBeenCalledTimes(1); }); - it('should run the action when the zod validation passes with legacy zod', async () => { - const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, - steps: [ - { - id: 'test', - name: 'name', - action: 'jest-legacy-zod-validated-action', - input: { foo: 1 }, - }, - ], - }); - - await runner.execute(task); - - expect(fakeActionHandler).toHaveBeenCalledTimes(1); - }); - it('should run the action when the validation passes', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -373,9 +312,6 @@ describe('NunjucksWorkflowRunner', () => { }; const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -404,9 +340,6 @@ describe('NunjucksWorkflowRunner', () => { it('should pass token through', async () => { const task = createMockTaskWithSpec( { - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -433,7 +366,6 @@ describe('NunjucksWorkflowRunner', () => { describe('conditionals', () => { it('should execute steps conditionally', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', name: 'test', action: 'output-action' }, { @@ -446,7 +378,6 @@ describe('NunjucksWorkflowRunner', () => { output: { result: '${{ steps.conditional.output.mock }}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -456,7 +387,6 @@ describe('NunjucksWorkflowRunner', () => { it('should skips steps conditionally', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', name: 'test', action: 'output-action' }, { @@ -469,7 +399,6 @@ describe('NunjucksWorkflowRunner', () => { output: { result: '${{ steps.conditional.output.mock }}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -479,7 +408,6 @@ describe('NunjucksWorkflowRunner', () => { it('should skips steps using the negating equals operator', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', name: 'test', action: 'output-action' }, { @@ -492,7 +420,6 @@ describe('NunjucksWorkflowRunner', () => { output: { result: '${{ steps.conditional.output.mock }}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -502,7 +429,6 @@ describe('NunjucksWorkflowRunner', () => { describe('should apply boolean step conditions', () => { it('executes when true', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'conditional', @@ -514,7 +440,6 @@ describe('NunjucksWorkflowRunner', () => { output: { result: '${{ steps.conditional.output.mock }}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -522,7 +447,6 @@ describe('NunjucksWorkflowRunner', () => { }); it('skips when false', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'conditional', @@ -534,7 +458,6 @@ describe('NunjucksWorkflowRunner', () => { output: { result: '${{ steps.conditional.output.mock }}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -546,7 +469,6 @@ describe('NunjucksWorkflowRunner', () => { describe('templating', () => { it('should template the input to an action', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -557,7 +479,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { input: 'BACKSTAGE', }, @@ -573,7 +494,6 @@ describe('NunjucksWorkflowRunner', () => { it('should not try and parse something that is not parsable', async () => { jest.spyOn(logger, 'error'); const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -584,7 +504,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { input: 'BACKSTAGE', }, @@ -597,7 +516,6 @@ describe('NunjucksWorkflowRunner', () => { it('should keep the original types for the input and not parse things that are not meant to be parsed', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -609,7 +527,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { number: 0, string: '1', @@ -625,7 +542,6 @@ describe('NunjucksWorkflowRunner', () => { it('should template complex values into the action', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -636,7 +552,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { complex: { bar: 'BACKSTAGE' }, }, @@ -651,7 +566,6 @@ describe('NunjucksWorkflowRunner', () => { it('supports really complex structures', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -662,7 +576,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { complex: { bar: 'BACKSTAGE', @@ -680,7 +593,6 @@ describe('NunjucksWorkflowRunner', () => { it('supports numbers as first class too', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -691,7 +603,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { complex: { bar: 'BACKSTAGE', @@ -710,8 +621,6 @@ describe('NunjucksWorkflowRunner', () => { it('should deal with checkpoints', async () => { const task = { ...createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, steps: [ { id: 'test', @@ -762,7 +671,6 @@ describe('NunjucksWorkflowRunner', () => { it('should template the output from simple actions', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -774,7 +682,6 @@ describe('NunjucksWorkflowRunner', () => { output: { foo: '${{steps.test.output.mock | upper}}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -784,7 +691,6 @@ describe('NunjucksWorkflowRunner', () => { it('should include task ID in the templated context', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -797,8 +703,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, - parameters: {}, }); await runner.execute(task); @@ -836,9 +740,6 @@ describe('NunjucksWorkflowRunner', () => { const task = createMockTaskWithSpec( { - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -882,9 +783,6 @@ describe('NunjucksWorkflowRunner', () => { const task = createMockTaskWithSpec( { - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -909,7 +807,6 @@ describe('NunjucksWorkflowRunner', () => { it('should run a step repeatedly - flat values', async () => { const colors = ['blue', 'green', 'red']; const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -919,7 +816,6 @@ describe('NunjucksWorkflowRunner', () => { input: { color: '${{each.value}}' }, }, ], - output: {}, parameters: { colors, }, @@ -936,9 +832,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 +882,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { settings: [{ color: 'blue' }], }, @@ -968,13 +898,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 +953,6 @@ describe('NunjucksWorkflowRunner', () => { input: { key: '${{each.key}}', value: '${{each.value}}' }, }, ], - output: {}, parameters: { settings, }, @@ -1006,7 +974,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 +983,6 @@ describe('NunjucksWorkflowRunner', () => { input: { foo: '${{each.value}}' }, }, ], - output: {}, parameters: { numbers, }, @@ -1037,7 +1003,6 @@ describe('NunjucksWorkflowRunner', () => { it('should validate each action iteration', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1047,7 +1012,6 @@ describe('NunjucksWorkflowRunner', () => { input: { foo: '${{each.value.foo}}' }, }, ], - output: {}, parameters: { data: [ { @@ -1065,7 +1029,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 +1038,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 +1050,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 +1058,6 @@ describe('NunjucksWorkflowRunner', () => { input: {}, }, ], - output: {}, - parameters: {}, }, { foo: 'bar' }, ); @@ -1114,7 +1072,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 +1082,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, - parameters: {}, }, { foo: 'bar' }, ); @@ -1141,7 +1096,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 +1109,6 @@ describe('NunjucksWorkflowRunner', () => { output: { b: '${{ secrets.foo }}', }, - parameters: {}, }, { foo: 'bar' }, ); @@ -1169,7 +1122,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 +1151,6 @@ describe('NunjucksWorkflowRunner', () => { describe('filters', () => { it('provides the parseRepoUrl filter', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1228,7 +1179,6 @@ describe('NunjucksWorkflowRunner', () => { describe('parseEntityRef', () => { it('parses entity ref', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1256,7 +1206,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 +1233,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 +1260,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 +1289,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 +1317,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 +1343,6 @@ describe('NunjucksWorkflowRunner', () => { it('provides the pick filter', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1422,7 +1366,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 +1398,6 @@ describe('NunjucksWorkflowRunner', () => { it('sets isDryRun flag correctly', async () => { const task = createMockTaskWithSpec( { - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -1481,13 +1421,10 @@ describe('NunjucksWorkflowRunner', () => { it('should have metadata in action context during dry run', async () => { const task = createMockTaskWithSpec( { - apiVersion: 'scaffolder.backstage.io/v1beta3', templateInfo: { entityRef: 'dryRun-Entity', entity: { metadata: { name: 'test-template' } }, }, - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -1519,9 +1456,6 @@ describe('NunjucksWorkflowRunner', () => { ]); const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -1560,9 +1494,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..7802a7ab8d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -28,7 +28,6 @@ import fs from 'fs-extra'; import { validate as validateJsonSchema } from 'jsonschema'; import nunjucks from 'nunjucks'; import path from 'path'; -import { PassThrough } from 'stream'; import * as winston from 'winston'; import { SecureTemplater, @@ -40,6 +39,7 @@ import { TaskTrackType, WorkflowResponse, WorkflowRunner } from './types'; import type { AuditorService, + LoggerService, PermissionsService, } from '@backstage/backend-plugin-api'; import { UserEntity } from '@backstage/catalog-model'; @@ -59,14 +59,13 @@ import { createDefaultFilters } from '../../lib/templating/filters/createDefault import { scaffolderActionRules } from '../../service/rules'; import { createCounterMetric, createHistogramMetric } from '../../util/metrics'; import { BackstageLoggerTransport, WinstonLogger } from './logger'; -import { loggerToWinstonLogger } from '../../util/loggerToWinstonLogger'; import { convertFiltersToRecord } from '../../util/templating'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; actionRegistry: TemplateActionRegistry; integrations: ScmIntegrations; - logger: winston.Logger; + logger: LoggerService; auditor?: AuditorService; additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; @@ -113,7 +112,7 @@ const createStepLogger = ({ }: { task: TaskContext; step: TaskStep; - rootLogger: winston.Logger; + rootLogger: LoggerService; }) => { const taskLogger = WinstonLogger.create({ level: process.env.LOG_LEVEL || 'info', @@ -126,22 +125,7 @@ const createStepLogger = ({ taskLogger.addRedactions(Object.values(task.secrets ?? {})); - // This stream logger should be deprecated. We're going to replace it with - // just using the logger directly, as all those logs get written to step logs - // using the stepLogStream above. - // Initially this stream used to be the only way to write to the client logs, but that - // has changed over time, there's not really a need for this anymore. - // You can just create a simple wrapper like the below in your action to write to the main logger. - // This way we also get redactions for free. - const streamLogger = new PassThrough(); - streamLogger.on('data', async data => { - const message = data.toString().trim(); - if (message?.length > 1) { - taskLogger.info(message); - } - }); - - return { taskLogger, streamLogger }; + return { taskLogger }; }; const isActionAuthorized = createConditionAuthorizer( @@ -268,7 +252,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { } const action: TemplateAction = this.options.actionRegistry.get(step.action); - const { taskLogger, streamLogger } = createStepLogger({ + const { taskLogger } = createStepLogger({ task, step, rootLogger: this.options.logger, @@ -315,7 +299,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,9 +382,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { id: await task.getWorkspaceName(), }, secrets: task.secrets ?? {}, - // TODO(blam): move to LoggerService and away from Winston - logger: loggerToWinstonLogger(taskLogger), - logStream: streamLogger, + logger: taskLogger, workspacePath, async checkpoint(opts: { key?: string; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 27a4499772..86f9f7fcf7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -18,6 +18,7 @@ import { AuditorService, AuthService, BackstageCredentials, + LoggerService, } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; @@ -38,7 +39,6 @@ import { Observable, createDeferred, } from '@backstage/types'; -import { Logger } from 'winston'; import ObservableImpl from 'zen-observable'; import { DefaultWorkspaceService, WorkspaceService } from './WorkspaceService'; import { readDuration } from './helper'; @@ -59,7 +59,7 @@ type TaskState = { }; /** * TaskManager - * + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. * @public */ export class TaskManager implements TaskContext { @@ -71,7 +71,7 @@ export class TaskManager implements TaskContext { task: CurrentClaimedTask, storage: TaskStore, abortSignal: AbortSignal, - logger: Logger, + logger: LoggerService, auth?: AuthService, config?: Config, additionalWorkspaceProviders?: Record, @@ -100,7 +100,7 @@ export class TaskManager implements TaskContext { private readonly task: CurrentClaimedTask, private readonly storage: TaskStore, private readonly signal: AbortSignal, - private readonly logger: Logger, + private readonly logger: LoggerService, private readonly workspaceService: WorkspaceService, private readonly auth?: AuthService, ) {} @@ -269,7 +269,7 @@ export interface CurrentClaimedTask { export class StorageTaskBroker implements TaskBroker { constructor( private readonly storage: TaskStore, - private readonly logger: Logger, + private readonly logger: LoggerService, private readonly config?: Config, private readonly auth?: AuthService, private readonly additionalWorkspaceProviders?: Record< diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 4441a2f0ca..b0c3166ff6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { AuditorService } from '@backstage/backend-plugin-api'; +import { AuditorService, LoggerService } from '@backstage/backend-plugin-api'; import { assertError, stringifyError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; @@ -25,7 +25,6 @@ import { TemplateGlobal, } from '@backstage/plugin-scaffolder-node'; import PQueue from 'p-queue'; -import { Logger } from 'winston'; import { TemplateActionRegistry } from '../actions'; import { NunjucksWorkflowRunner } from './NunjucksWorkflowRunner'; import { WorkflowRunner } from './types'; @@ -33,7 +32,7 @@ import { setTimeout } from 'timers/promises'; /** * TaskWorkerOptions - * + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. * @public */ export type TaskWorkerOptions = { @@ -43,14 +42,14 @@ export type TaskWorkerOptions = { }; concurrentTasksLimit: number; permissions?: PermissionEvaluator; - logger?: Logger; + logger?: LoggerService; auditor?: AuditorService; gracefulShutdown?: boolean; }; /** * CreateWorkerOptions - * + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. * @public */ export type CreateWorkerOptions = { @@ -58,7 +57,7 @@ export type CreateWorkerOptions = { actionRegistry: TemplateActionRegistry; integrations: ScmIntegrations; workingDirectory: string; - logger: Logger; + logger: LoggerService; auditor?: AuditorService; additionalTemplateFilters?: Record; /** @@ -81,12 +80,12 @@ export type CreateWorkerOptions = { /** * TaskWorker - * + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. * @public */ export class TaskWorker { private taskQueue: PQueue; - private logger: Logger | undefined; + private logger: LoggerService | undefined; private auditor: AuditorService | undefined; private stopWorkers: boolean; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts index 9d231d7e7c..265151d73b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts @@ -21,19 +21,10 @@ export type { CurrentClaimedTask } from './StorageTaskBroker'; export { TaskWorker } from './TaskWorker'; export type { CreateWorkerOptions } from './TaskWorker'; export type { - TaskCompletionState, TaskStoreEmitOptions, TaskStoreListEventsOptions, TaskStoreShutDownTaskOptions, - SerializedTask, - SerializedTaskEvent, - TaskStatus, - TaskEventType, - TaskBroker, - TaskContext, TaskStore, - TaskBrokerDispatchResult, - TaskBrokerDispatchOptions, TaskStoreCreateTaskOptions, TaskStoreRecoverTaskOptions, TaskStoreCreateTaskResult, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 1556c4838c..a3d9e3b741 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -17,96 +17,19 @@ import { HumanDuration, JsonObject, JsonValue } from '@backstage/types'; import { TaskSpec, TaskStep } from '@backstage/plugin-scaffolder-common'; import { - SerializedTask as _SerializedTask, - SerializedTaskEvent as _SerializedTaskEvent, - TaskBroker as _TaskBroker, - TaskBrokerDispatchOptions as _TaskBrokerDispatchOptions, - TaskBrokerDispatchResult as _TaskBrokerDispatchResult, - TaskCompletionState as _TaskCompletionState, - TaskContext as _TaskContext, - TaskEventType as _TaskEventType, TaskSecrets, - TaskStatus as _TaskStatus, TemplateAction, + TaskContext, + SerializedTaskEvent, + SerializedTask, + TaskStatus, } from '@backstage/plugin-scaffolder-node'; -/** - * The status of each step of the Task - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TaskStatus = _TaskStatus; - -/** - * The state of a completed task. - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TaskCompletionState = _TaskCompletionState; - -/** - * SerializedTask - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type SerializedTask = _SerializedTask; - -/** - * TaskEventType - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TaskEventType = _TaskEventType; - -/** - * SerializedTaskEvent - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type SerializedTaskEvent = _SerializedTaskEvent; - -/** - * The result of `TaskBroker.dispatch`. - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TaskBrokerDispatchResult = _TaskBrokerDispatchResult; - -/** - * The options passed to `TaskBroker.dispatch`. - * Currently a spec and optional secrets - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TaskBrokerDispatchOptions = _TaskBrokerDispatchOptions; - -/** - * Task - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TaskContext = _TaskContext; - -/** - * TaskBroker - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TaskBroker = _TaskBroker; - /** * TaskStoreEmitOptions * * @public + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. */ export type TaskStoreEmitOptions = { taskId: string; @@ -117,6 +40,7 @@ export type TaskStoreEmitOptions = { * TaskStoreListEventsOptions * * @public + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. */ export type TaskStoreListEventsOptions = { isTaskRecoverable?: boolean; @@ -128,6 +52,7 @@ export type TaskStoreListEventsOptions = { * TaskStoreShutDownTaskOptions * * @public + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. */ export type TaskStoreShutDownTaskOptions = { taskId: string; @@ -136,6 +61,7 @@ export type TaskStoreShutDownTaskOptions = { /** * The options passed to {@link TaskStore.createTask} * @public + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. */ export type TaskStoreCreateTaskOptions = { spec: TaskSpec; @@ -146,6 +72,7 @@ export type TaskStoreCreateTaskOptions = { /** * The options passed to {@link TaskStore.recoverTasks} * @public + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. */ export type TaskStoreRecoverTaskOptions = { timeout: HumanDuration; @@ -154,6 +81,7 @@ export type TaskStoreRecoverTaskOptions = { /** * The response from {@link TaskStore.createTask} * @public + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. */ export type TaskStoreCreateTaskResult = { taskId: string; @@ -163,6 +91,7 @@ export type TaskStoreCreateTaskResult = { * TaskStore * * @public + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. */ export interface TaskStore { cancelTask?(options: TaskStoreEmitOptions): Promise; diff --git a/plugins/scaffolder-backend/src/service/conditionExports.ts b/plugins/scaffolder-backend/src/service/alpha.ts similarity index 100% rename from plugins/scaffolder-backend/src/service/conditionExports.ts rename to plugins/scaffolder-backend/src/service/alpha.ts diff --git a/plugins/scaffolder-backend/src/service/helpers.ts b/plugins/scaffolder-backend/src/service/helpers.ts index cd55068d98..e29eec4d64 100644 --- a/plugins/scaffolder-backend/src/service/helpers.ts +++ b/plugins/scaffolder-backend/src/service/helpers.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { CatalogApi } from '@backstage/catalog-client'; +import { + BackstageCredentials, + LoggerService, +} from '@backstage/backend-plugin-api'; import { ANNOTATION_LOCATION, ANNOTATION_SOURCE_LOCATION, @@ -25,14 +28,14 @@ import { } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { assertError, InputError, NotFoundError } from '@backstage/errors'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import fs from 'fs-extra'; import os from 'os'; -import { Logger } from 'winston'; export async function getWorkingDirectory( config: Config, - logger: Logger, + logger: LoggerService, ): Promise { if (!config.has('backend.workingDirectory')) { return os.tmpdir(); @@ -89,16 +92,18 @@ export function getEntityBaseUrl(entity: Entity): string | undefined { */ export async function findTemplate(options: { entityRef: CompoundEntityRef; - token?: string; - catalogApi: CatalogApi; + catalog: CatalogService; + credentials: BackstageCredentials; }): Promise { - const { entityRef, token, catalogApi } = options; + const { entityRef, catalog, credentials } = options; if (entityRef.kind.toLocaleLowerCase('en-US') !== 'template') { throw new InputError(`Invalid kind, only 'Template' kind is supported`); } - const template = await catalogApi.getEntityByRef(entityRef, { token }); + const template = await catalog.getEntityByRef(entityRef, { + credentials, + }); if (!template) { throw new NotFoundError( `Template ${stringifyEntityRef(entityRef)} not found`, diff --git a/plugins/scaffolder-backend/src/service/permissions.ts b/plugins/scaffolder-backend/src/service/permissions.ts new file mode 100644 index 0000000000..9b12e115b9 --- /dev/null +++ b/plugins/scaffolder-backend/src/service/permissions.ts @@ -0,0 +1,61 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PermissionRule } from '@backstage/plugin-permission-node'; +import { + TemplateEntityStepV1beta3, + TemplateParametersV1beta3, +} from '@backstage/plugin-scaffolder-common'; +import { + RESOURCE_TYPE_SCAFFOLDER_ACTION, + RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, +} from '@backstage/plugin-scaffolder-common/alpha'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; + +/** + * @public + */ +export type TemplatePermissionRuleInput< + TParams extends PermissionRuleParams = PermissionRuleParams, +> = PermissionRule< + TemplateEntityStepV1beta3 | TemplateParametersV1beta3, + {}, + typeof RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, + TParams +>; +export function isTemplatePermissionRuleInput( + permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput, +): permissionRule is TemplatePermissionRuleInput { + return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_TEMPLATE; +} + +/** + * + * @public + */ +export type ActionPermissionRuleInput< + TParams extends PermissionRuleParams = PermissionRuleParams, +> = PermissionRule< + TemplateEntityStepV1beta3 | TemplateParametersV1beta3, + {}, + typeof RESOURCE_TYPE_SCAFFOLDER_ACTION, + TParams +>; +export function isActionPermissionRuleInput( + permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput, +): permissionRule is ActionPermissionRuleInput { + return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_ACTION; +} diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index d26cc955b3..679ac44640 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -16,8 +16,6 @@ import { DatabaseManager } from '@backstage/backend-common'; 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 +30,17 @@ 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 { mockCredentials, mockErrorHandler, @@ -50,15 +48,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 { ScmIntegrations } from '@backstage/integration'; import { extractFilterMetadata, @@ -66,21 +64,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( @@ -95,1617 +80,1219 @@ function createDatabase(): DatabaseService { ).forPlugin('scaffolder'); } -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, - }, - { - 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, - }, - { - 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 + | CreatedTemplateFilter[]; + additionalTemplateGlobals?: + | Record + | CreatedTemplateGlobal[]; + autocompleteHandlers?: Record; + } = {}, +) => { + 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 () => {}, + }), + ], + }); + + router.use(mockErrorHandler()); + return { 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(1); + }); + }); + + 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, + }); + + 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, + }); + + 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['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['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>['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>['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['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; - ( - taskBroker.event$ as jest.Mocked['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; - ( - taskBroker.event$ as jest.Mocked['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; - ( - taskBroker.event$ as jest.Mocked['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; - ( - taskBroker.event$ as jest.Mocked['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['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['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['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['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>['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>['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['dispatch']; + + broker.mockResolvedValue({ + taskId: 'a-random-id', }); - describe('GET /v2/tasks/:taskId', () => { - it('does not divulge secrets', async () => { - (taskBroker.get as jest.Mocked['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; - ( - taskBroker.event$ as jest.Mocked['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; - ( - taskBroker.event$ as jest.Mocked['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['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; - ( - taskBroker.event$ as jest.Mocked['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; - ( - taskBroker.event$ as jest.Mocked['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', + }, + }); + + console.log(status, body); + 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['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['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>['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>['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' }], + }); + }); + }); + + describe('GET /v2/tasks/:taskId', () => { + it('does not divulge secrets', async () => { + const { router, taskBroker } = await createTestRouter(); + (taskBroker.get as jest.Mocked['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(); + }); + }); + + describe('GET /v2/tasks/:taskId/eventstream', () => { + it('should return log messages', async () => { + const { router, taskBroker } = await createTestRouter(); + let subscriber: ZenObservable.SubscriptionObserver<{ + events: SerializedTaskEvent[]; + }>; + ( + taskBroker.event$ as jest.Mocked['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 = {}; + 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; + }); + + 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 { router, taskBroker } = await createTestRouter(); + let subscriber: ZenObservable.SubscriptionObserver<{ + events: SerializedTaskEvent[]; + }>; + ( + taskBroker.event$ as jest.Mocked['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 = {}; + + 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; + }); + + 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(); + let subscriber: ZenObservable.SubscriptionObserver<{ + events: SerializedTaskEvent[]; + }>; + ( + taskBroker.event$ as jest.Mocked['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(); + let subscriber: ZenObservable.SubscriptionObserver<{ + events: SerializedTaskEvent[]; + }>; + ( + taskBroker.event$ as jest.Mocked['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); + }); + }); + + 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(), + ); + }); + }); + + 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: [{ 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: [{ 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 f2e323e831..0ea6293f36 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -14,21 +14,18 @@ * limitations under the License. */ -import { createLegacyAuthAdapters } from '@backstage/backend-common'; import { AuditorService, AuthService, BackstageCredentials, DatabaseService, - DiscoveryService, HttpAuthService, LifecycleService, + LoggerService, PermissionsService, resolveSafeChildPath, SchedulerService, - UrlReaderService, } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; import { CompoundEntityRef, Entity, @@ -37,25 +34,19 @@ import { UserEntity, } from '@backstage/catalog-model'; import { Config, readDurationFromConfig } from '@backstage/config'; -import { InputError, NotFoundError, stringifyError } from '@backstage/errors'; +import { InputError, NotFoundError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; -import { - IdentityApi, - IdentityApiGetIdentityRequest, -} from '@backstage/plugin-auth-node'; + import { EventsService } from '@backstage/plugin-events-node'; -import { PermissionRuleParams } from '@backstage/plugin-permission-common'; + import { createConditionAuthorizer, createPermissionIntegrationRouter, - PermissionRule, } from '@backstage/plugin-permission-node'; import { TaskSpec, - TemplateEntityStepV1beta3, TemplateEntityV1beta3, templateEntityV1beta3Validator, - TemplateParametersV1beta3, } from '@backstage/plugin-scaffolder-common'; import { RESOURCE_TYPE_SCAFFOLDER_ACTION, @@ -82,17 +73,15 @@ import { CreatedTemplateGlobal, WorkspaceProvider, } from '@backstage/plugin-scaffolder-node/alpha'; -import { HumanDuration, JsonObject, JsonValue } from '@backstage/types'; +import { HumanDuration, JsonObject } from '@backstage/types'; import express from 'express'; import Router from 'express-promise-router'; import { validate } from 'jsonschema'; import { Duration } from 'luxon'; import { pathToFileURL } from 'url'; import { v4 as uuid } from 'uuid'; -import { Logger } from 'winston'; import { z } from 'zod'; import { - createBuiltinActions, DatabaseTaskStore, TaskWorker, TemplateActionRegistry, @@ -109,7 +98,6 @@ import { parseStringsParam, } from './helpers'; import { scaffolderActionRules, scaffolderTemplateRules } from './rules'; -import { HostDiscovery } from '@backstage/backend-defaults/discovery'; import { convertFiltersToRecord, convertGlobalsToRecord, @@ -118,63 +106,25 @@ import { extractGlobalValueMetadata, } from '../util/templating'; import { createDefaultFilters } from '../lib/templating/filters/createDefaultFilters'; - -/** - * - * @public - */ -export type TemplatePermissionRuleInput< - TParams extends PermissionRuleParams = PermissionRuleParams, -> = PermissionRule< - TemplateEntityStepV1beta3 | TemplateParametersV1beta3, - {}, - typeof RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, - TParams ->; -function isTemplatePermissionRuleInput( - permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput, -): permissionRule is TemplatePermissionRuleInput { - return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_TEMPLATE; -} - -/** - * - * @public - */ -export type ActionPermissionRuleInput< - TParams extends PermissionRuleParams = PermissionRuleParams, -> = PermissionRule< - TemplateEntityStepV1beta3 | TemplateParametersV1beta3, - {}, - typeof RESOURCE_TYPE_SCAFFOLDER_ACTION, - TParams ->; -function isActionPermissionRuleInput( - permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput, -): permissionRule is ActionPermissionRuleInput { - return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_ACTION; -} +import { + ActionPermissionRuleInput, + isActionPermissionRuleInput, + isTemplatePermissionRuleInput, + TemplatePermissionRuleInput, +} from './permissions'; +import { CatalogService } from '@backstage/plugin-catalog-node'; /** * RouterOptions - * - * @public - * @deprecated Please migrate to the new backend system as this will be removed in the future. */ export interface RouterOptions { - logger: Logger; + logger: LoggerService; config: Config; - reader: UrlReaderService; lifecycle?: LifecycleService; database: DatabaseService; - catalogClient: CatalogApi; + catalog: CatalogService; scheduler?: SchedulerService; actions?: TemplateAction[]; - /** - * @deprecated taskWorkers is deprecated in favor of concurrentTasksLimit option with a single TaskWorker - * @defaultValue 1 - */ - taskWorkers?: number; /** * Sets the number of concurrent tasks that can be run at any given time on the TaskWorker * @defaultValue 10 @@ -192,10 +142,8 @@ export interface RouterOptions { permissionRules?: Array< TemplatePermissionRuleInput | ActionPermissionRuleInput >; - auth?: AuthService; - httpAuth?: HttpAuthService; - identity?: IdentityApi; - discovery?: DiscoveryService; + auth: AuthService; + httpAuth: HttpAuthService; events?: EventsService; auditor?: AuditorService; autocompleteHandlers?: Record; @@ -205,70 +153,6 @@ function isSupportedTemplate(entity: TemplateEntityV1beta3) { return entity.apiVersion === 'scaffolder.backstage.io/v1beta3'; } -/* - * @deprecated This function remains as the DefaultIdentityClient behaves slightly differently to the pre-existing - * scaffolder behaviour. Specifically if the token fails to parse, the DefaultIdentityClient will raise an error. - * The scaffolder did not raise an error in this case. As such we chose to allow it to behave as it did previously - * until someone explicitly passes an IdentityApi. When we have reasonable confidence that most backstage deployments - * are using the IdentityApi, we can remove this function. - */ -function buildDefaultIdentityClient(options: RouterOptions): IdentityApi { - return { - getIdentity: async ({ request }: IdentityApiGetIdentityRequest) => { - const header = request.headers.authorization; - const { logger } = options; - - if (!header) { - return undefined; - } - - try { - const token = header.match(/^Bearer\s(\S+\.\S+\.\S+)$/i)?.[1]; - if (!token) { - throw new TypeError('Expected Bearer with JWT'); - } - - const [_header, rawPayload, _signature] = token.split('.'); - const payload: JsonValue = JSON.parse( - Buffer.from(rawPayload, 'base64').toString(), - ); - - if ( - typeof payload !== 'object' || - payload === null || - Array.isArray(payload) - ) { - throw new TypeError('Malformed JWT payload'); - } - - const sub = payload.sub; - if (typeof sub !== 'string') { - throw new TypeError('Expected string sub claim'); - } - - if (sub === 'backstage-server') { - return undefined; - } - - // Check that it's a valid ref, otherwise this will throw. - parseEntityRef(sub); - - return { - identity: { - userEntityRef: sub, - ownershipEntityRefs: [], - type: 'user', - }, - token, - }; - } catch (e) { - logger.error(`Invalid authorization header: ${stringifyError(e)}`); - return undefined; - } - }, - }; -} - const readDuration = ( config: Config, key: string, @@ -282,8 +166,6 @@ const readDuration = ( /** * A method to create a router for the scaffolder backend plugin. - * @public - * @deprecated Please migrate to the new backend system as this will be removed in the future. */ export async function createRouter( options: RouterOptions, @@ -295,30 +177,22 @@ export async function createRouter( const { logger: parentLogger, config, - reader, database, - catalogClient, + catalog, actions, - taskWorkers, scheduler, additionalTemplateFilters, additionalTemplateGlobals, additionalWorkspaceProviders, permissions, permissionRules, - discovery = HostDiscovery.fromConfig(config), - identity = buildDefaultIdentityClient(options), autocompleteHandlers = {}, events: eventsService, + auth, + httpAuth, auditor, } = options; - const { auth, httpAuth } = createLegacyAuthAdapters({ - ...options, - identity, - discovery, - }); - const concurrentTasksLimit = options.concurrentTasksLimit ?? options.config.getOptionalNumber('scaffolder.concurrentTasksLimit'); @@ -391,35 +265,23 @@ export async function createRouter( 'scaffolder.EXPERIMENTAL_gracefulShutdown', ); - for (let i = 0; i < (taskWorkers || 1); i++) { - const worker = await TaskWorker.create({ - taskBroker, - actionRegistry, - integrations, - logger, - auditor, - workingDirectory, - concurrentTasksLimit, - permissions, - gracefulShutdown, - ...templateExtensions, - }); - workers.push(worker); - } + const worker = await TaskWorker.create({ + taskBroker, + actionRegistry, + integrations, + logger, + auditor, + workingDirectory, + concurrentTasksLimit, + permissions, + gracefulShutdown, + ...templateExtensions, + }); + + workers.push(worker); } - const actionsToRegister = Array.isArray(actions) - ? actions - : createBuiltinActions({ - integrations, - catalogClient, - reader, - config, - auth, - ...templateExtensions, - }); - - actionsToRegister.forEach(action => actionRegistry.register(action)); + actions?.forEach(action => actionRegistry.register(action)); const launchWorkers = () => workers.forEach(worker => worker.start()); @@ -493,16 +355,7 @@ export async function createRouter( try { const credentials = await httpAuth.credentials(req); - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - - const template = await authorizeTemplate( - req.params, - token, - credentials, - ); + const template = await authorizeTemplate(req.params, credentials); const parameters = [template.spec.parameters ?? []].flat(); @@ -581,17 +434,12 @@ export async function createRouter( permissionService: permissions, }); - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - const userEntityRef = auth.isPrincipal(credentials, 'user') ? credentials.principal.userEntityRef : undefined; const userEntity = userEntityRef - ? await catalogClient.getEntityByRef(userEntityRef, { token }) + ? await catalog.getEntityByRef(userEntityRef, { credentials }) : undefined; let auditLog = `Scaffolding task for ${templateRef}`; @@ -604,7 +452,6 @@ export async function createRouter( const template = await authorizeTemplate( { kind, namespace, name }, - token, credentials, ); @@ -652,7 +499,7 @@ export async function createRouter( const secrets: InternalTaskSecrets = { ...req.body.secrets, - backstageToken: token, + backstageToken: (credentials as any).token, __initiatorCredentials: JSON.stringify({ ...credentials, // credentials.token is nonenumerable and will not be serialized, so we need to add it explicitly @@ -1010,17 +857,12 @@ export async function createRouter( throw new InputError('Input template is not a template'); } - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - const userEntityRef = auth.isPrincipal(credentials, 'user') ? credentials.principal.userEntityRef : undefined; const userEntity = userEntityRef - ? await catalogClient.getEntityByRef(userEntityRef, { token }) + ? await catalog.getEntityByRef(userEntityRef, { credentials }) : undefined; const templateRef: string = `${template.kind}:${ @@ -1086,7 +928,7 @@ export async function createRouter( })), secrets: { ...body.secrets, - ...(token && { backstageToken: token }), + backstageToken: (credentials as any).token, }, credentials, }); @@ -1149,13 +991,12 @@ export async function createRouter( async function authorizeTemplate( entityRef: CompoundEntityRef, - token: string | undefined, credentials: BackstageCredentials, ) { const template = await findTemplate({ - catalogApi: catalogClient, + catalog, entityRef, - token, + credentials, }); if (!isSupportedTemplate(template)) { diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index 85dc444a53..5f1ed60113 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 + ## 1.5.11-next.0 ### Patch Changes diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index 487b5a92a1..92e044898f 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.5.11", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", "backstage": { "role": "common-library", diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md index e867e35a94..eff8276f2b 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,53 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index 87874d23c4..22fff7c3b5 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.3", + "version": "0.3.0-next.2", "backstage": { "role": "node-library", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-node-test-utils/report.api.md b/plugins/scaffolder-node-test-utils/report.api.md index b95d4cef0c..2780baf1d5 100644 --- a/plugins/scaffolder-node-test-utils/report.api.md +++ b/plugins/scaffolder-node-test-utils/report.api.md @@ -7,12 +7,12 @@ import { ActionContext } from '@backstage/plugin-scaffolder-node'; import { JsonObject } from '@backstage/types'; // @public -export const createMockActionContext: < +export function createMockActionContext< TActionInput extends JsonObject = JsonObject, - TActionOutput extends JsonObject = JsonObject, + TActionOutput extends JsonObject = any, >( options?: Partial>, -) => ActionContext; +): ActionContext; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts index bb6e3917f1..a6703a12d1 100644 --- a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts +++ b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts @@ -30,13 +30,14 @@ import { loggerToWinstonLogger } from './loggerToWinstonLogger'; * @public * @param options - optional parameters to override default mock context */ -export const createMockActionContext = < +export function createMockActionContext< TActionInput extends JsonObject = JsonObject, - TActionOutput extends JsonObject = JsonObject, + TActionOutput extends JsonObject = any, >( options?: Partial>, -): ActionContext => { +): ActionContext { const credentials = mockCredentials.user(); + const defaultContext = { logger: loggerToWinstonLogger(mockServices.logger.mock()), logStream: new PassThrough(), @@ -66,16 +67,8 @@ export const createMockActionContext = < }; } - const { - input, - logger, - logStream, - secrets, - templateInfo, - workspacePath, - task, - user, - } = options; + const { input, logger, secrets, templateInfo, workspacePath, task, user } = + options; return { ...defaultContext, @@ -84,11 +77,10 @@ export const createMockActionContext = < createTemporaryDirectory: jest.fn().mockResolvedValue(workspacePath), }), ...(logger && { logger }), - ...(logStream && { logStream }), ...(input && { input }), ...(secrets && { secrets }), ...(task && { task }), ...(user && { user }), templateInfo, }; -}; +} diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index c978c94944..b8e0aa5413 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,112 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 61557aa4a8..ca5cd3be75 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.3", + "version": "0.9.0-next.2", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library", diff --git a/plugins/scaffolder-node/report.api.md b/plugins/scaffolder-node/report.api.md index f2e8501527..a23a9137b1 100644 --- a/plugins/scaffolder-node/report.api.md +++ b/plugins/scaffolder-node/report.api.md @@ -7,7 +7,6 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { Expand } from '@backstage/types'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; -import { Logger } from 'winston'; import { LoggerService } from '@backstage/backend-plugin-api'; import { Observable } from '@backstage/types'; import { Schema } from 'jsonschema'; @@ -25,63 +24,34 @@ import { z } from 'zod'; export type ActionContext< TActionInput extends JsonObject, TActionOutput extends JsonObject = JsonObject, - TSchemaType extends 'v1' | 'v2' = 'v1', -> = TSchemaType extends 'v2' - ? { - logger: LoggerService; - secrets?: TaskSecrets; - workspacePath: string; - input: TActionInput; - checkpoint(opts: { - key: string; - fn: () => Promise | T; - }): Promise; - output( - name: keyof TActionOutput, - value: TActionOutput[keyof TActionOutput], - ): void; - createTemporaryDirectory(): Promise; - getInitiatorCredentials(): Promise; - task: { - id: string; - }; - templateInfo?: TemplateInfo; - isDryRun?: boolean; - user?: { - entity?: UserEntity; - ref?: string; - }; - signal?: AbortSignal; - each?: JsonObject; - } - : { - logger: Logger; - logStream: Writable; - secrets?: TaskSecrets; - workspacePath: string; - input: TActionInput; - checkpoint(opts: { - key: string; - fn: () => Promise | T; - }): Promise; - output( - name: keyof TActionOutput, - value: TActionOutput[keyof TActionOutput], - ): void; - createTemporaryDirectory(): Promise; - getInitiatorCredentials(): Promise; - task: { - id: string; - }; - templateInfo?: TemplateInfo; - isDryRun?: boolean; - user?: { - entity?: UserEntity; - ref?: string; - }; - signal?: AbortSignal; - each?: JsonObject; - }; + _TSchemaType extends 'v2' = 'v2', +> = { + logger: LoggerService; + secrets?: TaskSecrets; + workspacePath: string; + input: TActionInput; + checkpoint(opts: { + key: string; + fn: () => Promise | T; + }): Promise; + output( + name: keyof TActionOutput, + value: TActionOutput[keyof TActionOutput], + ): void; + createTemporaryDirectory(): Promise; + getInitiatorCredentials(): Promise; + task: { + id: string; + }; + templateInfo?: TemplateInfo; + isDryRun?: boolean; + user?: { + entity?: UserEntity; + ref?: string; + }; + signal?: AbortSignal; + each?: JsonObject; +}; // @public (undocumented) export function addFiles(options: { @@ -95,7 +65,7 @@ export function addFiles(options: { | { token: string; }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; }): Promise; // @public (undocumented) @@ -110,7 +80,7 @@ export function cloneRepo(options: { | { token: string; }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; ref?: string | undefined; depth?: number | undefined; noCheckout?: boolean | undefined; @@ -127,7 +97,7 @@ export function commitAndPushBranch(options: { | { token: string; }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; commitMessage: string; gitAuthorInfo?: { name?: string; @@ -152,7 +122,7 @@ export function commitAndPushRepo(input: { | { token: string; }; - logger: Logger; + logger: LoggerService; commitMessage: string; gitAuthorInfo?: { name?: string; @@ -177,72 +147,70 @@ export function createBranch(options: { | { token: string; }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; }): Promise; -// @public @deprecated (undocumented) -export function createTemplateAction< - TInputParams extends JsonObject = JsonObject, - TOutputParams extends JsonObject = JsonObject, - TInputSchema extends JsonObject = JsonObject, - TOutputSchema extends JsonObject = JsonObject, - TActionInput extends JsonObject = TInputParams, - TActionOutput extends JsonObject = TOutputParams, ->( - action: TemplateActionOptions< - TActionInput, - TActionOutput, - TInputSchema, - TOutputSchema, - 'v1' - >, -): TemplateAction; - -// @public @deprecated (undocumented) -export function createTemplateAction< - TInputParams extends JsonObject = JsonObject, - TOutputParams extends JsonObject = JsonObject, - TInputSchema extends z.ZodType = z.ZodType, - TOutputSchema extends z.ZodType = z.ZodType, - TActionInput extends JsonObject = z.infer, - TActionOutput extends JsonObject = z.infer, ->( - action: TemplateActionOptions< - TActionInput, - TActionOutput, - TInputSchema, - TOutputSchema, - 'v1' - >, -): TemplateAction; - // @public export function createTemplateAction< - TInputSchema extends { - [key in string]: (zImpl: typeof z) => z.ZodType; - }, - TOutputSchema extends { - [key in string]: (zImpl: typeof z) => z.ZodType; - }, + TInputSchema extends + | { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + | ((zImpl: typeof z) => z.ZodType), + TOutputSchema extends + | { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + | ((zImpl: typeof z) => z.ZodType), >( action: TemplateActionOptions< - { - [key in keyof TInputSchema]: z.infer>; - }, - { - [key in keyof TOutputSchema]: z.infer>; - }, + TInputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + ? { + [key in keyof TInputSchema]: z.infer>; + } + : TInputSchema extends (zImpl: typeof z) => z.ZodType + ? z.infer> + : never, + TOutputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + ? { + [key in keyof TOutputSchema]: z.infer>; + } + : TOutputSchema extends (zImpl: typeof z) => z.ZodType + ? z.infer> + : never, TInputSchema, TOutputSchema, 'v2' >, ): TemplateAction< - FlattenOptionalProperties<{ - [key in keyof TInputSchema]: z.output>; - }>, - FlattenOptionalProperties<{ - [key in keyof TOutputSchema]: z.output>; - }>, + FlattenOptionalProperties< + TInputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + ? { + [key in keyof TInputSchema]: z.output>; + } + : TInputSchema extends (zImpl: typeof z) => z.ZodType + ? z.output> + : never + >, + FlattenOptionalProperties< + TOutputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + ? { + [key in keyof TOutputSchema]: z.output< + ReturnType + >; + } + : TOutputSchema extends (zImpl: typeof z) => z.ZodType + ? z.output> + : never + >, 'v2' >; @@ -304,7 +272,7 @@ export function initRepoAndPush(input: { | { token: string; }; - logger: Logger; + logger: LoggerService; defaultBranch?: string; commitMessage?: string; gitAuthorInfo?: { @@ -316,6 +284,9 @@ export function initRepoAndPush(input: { commitHash: string; }>; +// @public +export function isNotGitDirectoryOrContents(path: string): boolean; + // @public (undocumented) export const parseRepoUrl: ( repoUrl: string, @@ -510,7 +481,7 @@ export type TaskStatus = export type TemplateAction< TActionInput extends JsonObject = JsonObject, TActionOutput extends JsonObject = JsonObject, - TSchemaType extends 'v1' | 'v2' = 'v1', + TSchemaType extends 'v2' = 'v2', > = { id: string; description?: string; @@ -533,18 +504,20 @@ export type TemplateActionOptions< TActionInput extends JsonObject = {}, TActionOutput extends JsonObject = {}, TInputSchema extends - | JsonObject - | z.ZodType | { [key in string]: (zImpl: typeof z) => z.ZodType; - } = JsonObject, + } + | ((zImpl: typeof z) => z.ZodType) = { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, TOutputSchema extends - | JsonObject - | z.ZodType | { [key in string]: (zImpl: typeof z) => z.ZodType; - } = JsonObject, - TSchemaType extends 'v1' | 'v2' = 'v1' | 'v2', + } + | ((zImpl: typeof z) => z.ZodType) = { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + TSchemaType extends 'v2' = 'v2', > = { id: string; description?: string; diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts index 43d5d2f660..b4ed095f03 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts @@ -14,85 +14,8 @@ * limitations under the License. */ import { createTemplateAction } from './createTemplateAction'; -import { z } from 'zod'; describe('createTemplateAction', () => { - it('should allow creating with jsonschema and use the old deprecated types', () => { - const action = createTemplateAction<{ repoUrl: string }, { test: string }>({ - id: 'test', - schema: { - input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: { type: 'string' }, - }, - }, - output: { - type: 'object', - required: ['test'], - properties: { - test: { type: 'string' }, - }, - }, - }, - handler: async ctx => { - // @ts-expect-error - repoUrl is string - const a: number = ctx.input.repoUrl; - - const b: string = ctx.input.repoUrl; - expect(b).toBeDefined(); - - const stream = ctx.logStream; - expect(stream).toBeDefined(); - - ctx.output('test', 'value'); - - // @ts-expect-error - not valid output type - ctx.output('test', 4); - - // @ts-expect-error - not valid output name - ctx.output('test2', 'value'); - }, - }); - - expect(action).toBeDefined(); - }); - - it('should allow creating with zod and use the old deprecated types', () => { - const action = createTemplateAction({ - id: 'test', - schema: { - input: z.object({ - repoUrl: z.string(), - }), - output: z.object({ - test: z.string(), - }), - }, - handler: async ctx => { - // @ts-expect-error - repoUrl is string - const a: number = ctx.input.repoUrl; - - const b: string = ctx.input.repoUrl; - expect(b).toBeDefined(); - - const stream = ctx.logStream; - expect(stream).toBeDefined(); - - ctx.output('test', 'value'); - - // @ts-expect-error - not valid output type - ctx.output('test', 4); - - // @ts-expect-error - not valid output name - ctx.output('test2', 'value'); - }, - }); - - expect(action).toBeDefined(); - }); - it('should allow creating with new first class zod support', () => { const action = createTemplateAction({ id: 'test', @@ -107,14 +30,10 @@ describe('createTemplateAction', () => { handler: async ctx => { // @ts-expect-error - repoUrl is string const a: number = ctx.input.repoUrl; - const b: string = ctx.input.repoUrl; - expect(b).toBeDefined(); - // @ts-expect-error - logStream is not available - const stream = ctx.logStream; - - expect(stream).toBeDefined(); + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + [a, b]; ctx.output('test', 'value'); @@ -128,4 +47,56 @@ describe('createTemplateAction', () => { expect(action).toBeDefined(); }); + + it('should allow creating with a function for input and output schema for more complex types', () => { + const action = createTemplateAction({ + id: 'test', + schema: { + input: z => + z.union([ + z.object({ + repoUrl: z.string(), + }), + z.object({ + numberThing: z.number(), + }), + ]), + output: z => + z.object({ + test: z.string(), + }), + }, + handler: async ctx => { + ctx.output('test', 'value'); + + // @ts-expect-error - not valid output type + ctx.output('test', 4); + + // @ts-expect-error - not valid output name + ctx.output('test2', 'value'); + + if ('repoUrl' in ctx.input) { + // @ts-expect-error - not valid input type + const a: number = ctx.input.repoUrl; + + const b: string = ctx.input.repoUrl; + + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + [a, b]; + } + + if ('numberThing' in ctx.input) { + const a: number = ctx.input.numberThing; + + // @ts-expect-error - not valid input type + const b: string = ctx.input.numberThing; + + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + [a, b]; + } + }, + }); + + expect(action).toBeDefined(); + }); }); diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.ts index ce38f3b4e1..edf3e203b9 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.ts @@ -30,14 +30,18 @@ export type TemplateActionOptions< TActionInput extends JsonObject = {}, TActionOutput extends JsonObject = {}, TInputSchema extends - | JsonObject - | z.ZodType - | { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject, + | { [key in string]: (zImpl: typeof z) => z.ZodType } + | ((zImpl: typeof z) => z.ZodType) = { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, TOutputSchema extends - | JsonObject - | z.ZodType - | { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject, - TSchemaType extends 'v1' | 'v2' = 'v1' | 'v2', + | { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + | ((zImpl: typeof z) => z.ZodType) = { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + TSchemaType extends 'v2' = 'v2', > = { id: string; description?: string; @@ -62,109 +66,82 @@ type FlattenOptionalProperties = Expand< [K in keyof T as undefined extends T[K] ? K : never]?: T[K]; } >; - -/** - * @public - * @deprecated migrate to using the new built in zod schema definitions for schemas - */ -export function createTemplateAction< - TInputParams extends JsonObject = JsonObject, - TOutputParams extends JsonObject = JsonObject, - TInputSchema extends JsonObject = JsonObject, - TOutputSchema extends JsonObject = JsonObject, - TActionInput extends JsonObject = TInputParams, - TActionOutput extends JsonObject = TOutputParams, ->( - action: TemplateActionOptions< - TActionInput, - TActionOutput, - TInputSchema, - TOutputSchema, - 'v1' - >, -): TemplateAction; -/** - * @public - * @deprecated migrate to using the new built in zod schema definitions for schemas - */ -export function createTemplateAction< - TInputParams extends JsonObject = JsonObject, - TOutputParams extends JsonObject = JsonObject, - TInputSchema extends z.ZodType = z.ZodType, - TOutputSchema extends z.ZodType = z.ZodType, - TActionInput extends JsonObject = z.infer, - TActionOutput extends JsonObject = z.infer, ->( - action: TemplateActionOptions< - TActionInput, - TActionOutput, - TInputSchema, - TOutputSchema, - 'v1' - >, -): TemplateAction; /** * This function is used to create new template actions to get type safety. * Will convert zod schemas to json schemas for use throughout the system. * @public */ export function createTemplateAction< - TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, - TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, + TInputSchema extends + | { [key in string]: (zImpl: typeof z) => z.ZodType } + | ((zImpl: typeof z) => z.ZodType), + TOutputSchema extends + | { [key in string]: (zImpl: typeof z) => z.ZodType } + | ((zImpl: typeof z) => z.ZodType), >( action: TemplateActionOptions< - { - [key in keyof TInputSchema]: z.infer>; - }, - { - [key in keyof TOutputSchema]: z.infer>; - }, + TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + ? { + [key in keyof TInputSchema]: z.infer>; + } + : TInputSchema extends (zImpl: typeof z) => z.ZodType + ? z.infer> + : never, + TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + ? { + [key in keyof TOutputSchema]: z.infer>; + } + : TOutputSchema extends (zImpl: typeof z) => z.ZodType + ? z.infer> + : never, TInputSchema, TOutputSchema, 'v2' >, ): TemplateAction< - FlattenOptionalProperties<{ - [key in keyof TInputSchema]: z.output>; - }>, - FlattenOptionalProperties<{ - [key in keyof TOutputSchema]: z.output>; - }>, + FlattenOptionalProperties< + TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + ? { + [key in keyof TInputSchema]: z.output>; + } + : TInputSchema extends (zImpl: typeof z) => z.ZodType + ? z.output> + : never + >, + FlattenOptionalProperties< + TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + ? { + [key in keyof TOutputSchema]: z.output< + ReturnType + >; + } + : TOutputSchema extends (zImpl: typeof z) => z.ZodType + ? z.output> + : never + >, 'v2' >; export function createTemplateAction< - TInputParams extends JsonObject = JsonObject, - TOutputParams extends JsonObject = JsonObject, - TInputSchema extends - | JsonObject - | z.ZodType - | { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject, - TOutputSchema extends - | JsonObject - | z.ZodType - | { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject, - TActionInput extends JsonObject = TInputSchema extends z.ZodType< - any, - any, - infer IReturn - > - ? IReturn - : TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } = { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } = { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + TActionInput extends JsonObject = TInputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + } ? Expand<{ [key in keyof TInputSchema]: z.infer>; }> - : TInputParams, - TActionOutput extends JsonObject = TOutputSchema extends z.ZodType< - any, - any, - infer IReturn - > - ? IReturn - : TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + : never, + TActionOutput extends JsonObject = TOutputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + } ? Expand<{ [key in keyof TOutputSchema]: z.infer>; }> - : TOutputParams, + : never, >( action: TemplateActionOptions< TActionInput, @@ -172,13 +149,7 @@ export function createTemplateAction< TInputSchema, TOutputSchema >, -): TemplateAction< - TActionInput, - TActionOutput, - TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } - ? 'v2' - : 'v1' -> { +): TemplateAction { const { inputSchema, outputSchema } = parseSchemas( action as TemplateActionOptions, ); diff --git a/plugins/scaffolder-node/src/actions/gitHelpers.ts b/plugins/scaffolder-node/src/actions/gitHelpers.ts index 8922975274..49650de92e 100644 --- a/plugins/scaffolder-node/src/actions/gitHelpers.ts +++ b/plugins/scaffolder-node/src/actions/gitHelpers.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { Logger } from 'winston'; import { Git } from '../scm'; +import { LoggerService } from '@backstage/backend-plugin-api'; /** * @public @@ -27,7 +27,7 @@ export async function initRepoAndPush(input: { // it has to be provided as password together with a username // which may be a fixed value defined by the provider. auth: { username: string; password: string } | { token: string }; - logger: Logger; + logger: LoggerService; defaultBranch?: string; commitMessage?: string; gitAuthorInfo?: { name?: string; email?: string }; @@ -87,7 +87,7 @@ export async function commitAndPushRepo(input: { // it has to be provided as password together with a username // which may be a fixed value defined by the provider. auth: { username: string; password: string } | { token: string }; - logger: Logger; + logger: LoggerService; commitMessage: string; gitAuthorInfo?: { name?: string; email?: string }; branch?: string; @@ -147,7 +147,7 @@ export async function cloneRepo(options: { // it has to be provided as password together with a username // which may be a fixed value defined by the provider. auth: { username: string; password: string } | { token: string }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; ref?: string | undefined; depth?: number | undefined; noCheckout?: boolean | undefined; @@ -172,7 +172,7 @@ export async function createBranch(options: { // it has to be provided as password together with a username // which may be a fixed value defined by the provider. auth: { username: string; password: string } | { token: string }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; }): Promise { const { dir, ref, auth, logger } = options; const git = Git.fromAuth({ @@ -193,7 +193,7 @@ export async function addFiles(options: { // it has to be provided as password together with a username // which may be a fixed value defined by the provider. auth: { username: string; password: string } | { token: string }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; }): Promise { const { dir, filepath, auth, logger } = options; const git = Git.fromAuth({ @@ -213,7 +213,7 @@ export async function commitAndPushBranch(options: { // it has to be provided as password together with a username // which may be a fixed value defined by the provider. auth: { username: string; password: string } | { token: string }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; commitMessage: string; gitAuthorInfo?: { name?: string; email?: string }; branch?: string; diff --git a/plugins/scaffolder-node/src/actions/index.ts b/plugins/scaffolder-node/src/actions/index.ts index c1c8551c53..686d89d844 100644 --- a/plugins/scaffolder-node/src/actions/index.ts +++ b/plugins/scaffolder-node/src/actions/index.ts @@ -33,4 +33,8 @@ export { createBranch, cloneRepo, } from './gitHelpers'; -export { parseRepoUrl, getRepoSourceDirectory } from './util'; +export { + parseRepoUrl, + getRepoSourceDirectory, + isNotGitDirectoryOrContents, +} from './util'; diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index 92a2c7464a..f13d256332 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { Logger } from 'winston'; -import { Writable } from 'stream'; import { JsonObject, JsonValue } from '@backstage/types'; import { TaskSecrets } from '../tasks'; import { TemplateInfo } from '@backstage/plugin-scaffolder-common'; @@ -32,143 +30,75 @@ import { export type ActionContext< TActionInput extends JsonObject, TActionOutput extends JsonObject = JsonObject, - TSchemaType extends 'v1' | 'v2' = 'v1', -> = TSchemaType extends 'v2' - ? { - logger: LoggerService; - secrets?: TaskSecrets; - workspacePath: string; - input: TActionInput; - checkpoint(opts: { - key: string; - fn: () => Promise | T; - }): Promise; - output( - name: keyof TActionOutput, - value: TActionOutput[keyof TActionOutput], - ): void; - /** - * Creates a temporary directory for use by the action, which is then cleaned up automatically. - */ - createTemporaryDirectory(): Promise; + _TSchemaType extends 'v2' = 'v2', +> = { + logger: LoggerService; + secrets?: TaskSecrets; + workspacePath: string; + input: TActionInput; + checkpoint(opts: { + key: string; + fn: () => Promise | T; + }): Promise; + output( + name: keyof TActionOutput, + value: TActionOutput[keyof TActionOutput], + ): void; + /** + * Creates a temporary directory for use by the action, which is then cleaned up automatically. + */ + createTemporaryDirectory(): Promise; - /** - * Get the credentials for the current request - */ - getInitiatorCredentials(): Promise; + /** + * Get the credentials for the current request + */ + getInitiatorCredentials(): Promise; - /** - * Task information - */ - task: { - id: string; - }; + /** + * Task information + */ + task: { + id: string; + }; - templateInfo?: TemplateInfo; + templateInfo?: TemplateInfo; - /** - * Whether this action invocation is a dry-run or not. - * This will only ever be true if the actions as marked as supporting dry-runs. - */ - isDryRun?: boolean; + /** + * Whether this action invocation is a dry-run or not. + * This will only ever be true if the actions as marked as supporting dry-runs. + */ + isDryRun?: boolean; - /** - * The user which triggered the action. - */ - user?: { - /** - * The decorated entity from the Catalog - */ - entity?: UserEntity; - /** - * An entity ref for the author of the task - */ - ref?: string; - }; + /** + * The user which triggered the action. + */ + user?: { + /** + * The decorated entity from the Catalog + */ + entity?: UserEntity; + /** + * An entity ref for the author of the task + */ + ref?: string; + }; - /** - * Implement the signal to make your custom step abortable https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal - */ - signal?: AbortSignal; + /** + * Implement the signal to make your custom step abortable https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal + */ + signal?: AbortSignal; - /** - * Optional value of each invocation - */ - each?: JsonObject; - } - : /** @deprecated **/ - { - // TODO(blam): move this to LoggerService - logger: Logger; - /** @deprecated - use `ctx.logger` instead */ - logStream: Writable; - secrets?: TaskSecrets; - workspacePath: string; - input: TActionInput; - checkpoint(opts: { - key: string; - fn: () => Promise | T; - }): Promise; - output( - name: keyof TActionOutput, - value: TActionOutput[keyof TActionOutput], - ): void; - - /** - * Creates a temporary directory for use by the action, which is then cleaned up automatically. - */ - createTemporaryDirectory(): Promise; - - /** - * Get the credentials for the current request - */ - getInitiatorCredentials(): Promise; - - /** - * Task information - */ - task: { - id: string; - }; - - templateInfo?: TemplateInfo; - - /** - * Whether this action invocation is a dry-run or not. - * This will only ever be true if the actions as marked as supporting dry-runs. - */ - isDryRun?: boolean; - - /** - * The user which triggered the action. - */ - user?: { - /** - * The decorated entity from the Catalog - */ - entity?: UserEntity; - /** - * An entity ref for the author of the task - */ - ref?: string; - }; - - /** - * Implement the signal to make your custom step abortable https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal - */ - signal?: AbortSignal; - - /** - * Optional value of each invocation - */ - each?: JsonObject; - }; + /** + * Optional value of each invocation + */ + each?: JsonObject; +}; /** @public */ export type TemplateAction< TActionInput extends JsonObject = JsonObject, TActionOutput extends JsonObject = JsonObject, - TSchemaType extends 'v1' | 'v2' = 'v1', + TSchemaType extends 'v2' = 'v2', > = { id: string; description?: string; diff --git a/plugins/scaffolder-node/src/actions/util.test.ts b/plugins/scaffolder-node/src/actions/util.test.ts index 4b1e787bbc..792e306d00 100644 --- a/plugins/scaffolder-node/src/actions/util.test.ts +++ b/plugins/scaffolder-node/src/actions/util.test.ts @@ -244,4 +244,30 @@ describe('scaffolder action utils', () => { }); }); }); + + describe('isNotGitDirectoryOrContents', () => { + it('should filter .git directory and its contents but keep other files', () => { + // Import the function to test + const { isNotGitDirectoryOrContents } = require('./util'); + + // Should filter out .git directory + expect(isNotGitDirectoryOrContents('.git')).toBe(false); + + // Should filter out .git directory in subdirectories + expect(isNotGitDirectoryOrContents('subdir/.git')).toBe(false); + + // Should filter out files inside .git directory + expect(isNotGitDirectoryOrContents('.git/config')).toBe(false); + expect(isNotGitDirectoryOrContents('subdir/.git/config')).toBe(false); + + // Should keep .gitignore and other non-.git-directory files + expect(isNotGitDirectoryOrContents('.gitignore')).toBe(true); + expect(isNotGitDirectoryOrContents('src/components/GitHubIcon.js')).toBe( + true, + ); + expect(isNotGitDirectoryOrContents('.github/workflows/ci.yml')).toBe( + true, + ); + }); + }); }); diff --git a/plugins/scaffolder-node/src/actions/util.ts b/plugins/scaffolder-node/src/actions/util.ts index 12f73b0c6c..34cc74c811 100644 --- a/plugins/scaffolder-node/src/actions/util.ts +++ b/plugins/scaffolder-node/src/actions/util.ts @@ -130,11 +130,7 @@ function checkRequiredParams(repoUrl: URL, ...params: string[]) { } } -const isZodSchema = (schema: unknown): schema is z.ZodType => { - return typeof schema === 'object' && !!schema && 'safeParseAsync' in schema; -}; - -const isNativeZodSchema = ( +const isKeyValueZodCallback = ( schema: unknown, ): schema is { [key in string]: (zImpl: typeof z) => z.ZodType } => { return ( @@ -144,23 +140,20 @@ const isNativeZodSchema = ( ); }; +const isZodFunctionDefinition = ( + schema: unknown, +): schema is (zImpl: typeof z) => z.ZodType => { + return typeof schema === 'function'; +}; + export const parseSchemas = ( - action: TemplateActionOptions, + action: TemplateActionOptions, ): { inputSchema?: Schema; outputSchema?: Schema } => { if (!action.schema) { return { inputSchema: undefined, outputSchema: undefined }; } - if (isZodSchema(action.schema.input)) { - return { - inputSchema: zodToJsonSchema(action.schema.input) as Schema, - outputSchema: isZodSchema(action.schema.output) - ? (zodToJsonSchema(action.schema.output) as Schema) - : undefined, - }; - } - - if (isNativeZodSchema(action.schema.input)) { + if (isKeyValueZodCallback(action.schema.input)) { const input = z.object( Object.fromEntries( Object.entries(action.schema.input).map(([k, v]) => [k, v(z)]), @@ -169,7 +162,7 @@ export const parseSchemas = ( return { inputSchema: zodToJsonSchema(input) as Schema, - outputSchema: isNativeZodSchema(action.schema.output) + outputSchema: isKeyValueZodCallback(action.schema.output) ? (zodToJsonSchema( z.object( Object.fromEntries( @@ -181,8 +174,26 @@ export const parseSchemas = ( }; } + if (isZodFunctionDefinition(action.schema.input)) { + return { + inputSchema: zodToJsonSchema(action.schema.input(z)) as Schema, + outputSchema: isZodFunctionDefinition(action.schema.output) + ? (zodToJsonSchema(action.schema.output(z)) as Schema) + : undefined, + }; + } + return { - inputSchema: action.schema.input, - outputSchema: action.schema.output, + inputSchema: undefined, + outputSchema: undefined, }; }; + +/** + * Filter function to exclude the .git directory and its contents + * while keeping other files like .gitignore + * @public + */ +export function isNotGitDirectoryOrContents(path: string): boolean { + return !(path.endsWith('.git') || path.includes('.git/')); +} diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index 9cddcfefca..86cceab231 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,70 @@ # @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 + +## 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 diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 81f9dad6dc..5f85d149ed 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.3", + "version": "1.16.1-next.2", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library", 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} {templates.map(({ template, additionalLinks }) => ( - + > + + ))} 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 ; } 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) => { 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 = []; - 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 = []; + 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/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index c315185b92..3f8381027a 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,100 @@ # @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 + +## 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 diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 47e092112f..4aba053eb7 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.31.0-next.3", + "version": "1.32.0-next.2", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin", diff --git a/plugins/scaffolder/report-alpha.api.md b/plugins/scaffolder/report-alpha.api.md index 69fcec11e6..509cfa8497 100644 --- a/plugins/scaffolder/report-alpha.api.md +++ b/plugins/scaffolder/report-alpha.api.md @@ -8,6 +8,8 @@ import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ApiRef } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { Entity } from '@backstage/catalog-model'; +import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; @@ -18,6 +20,7 @@ import type { FormProps as FormProps_2 } from '@rjsf/core'; import { FormProps as FormProps_3 } from '@backstage/plugin-scaffolder-react'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; +import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { LayoutOptions } from '@backstage/plugin-scaffolder-react'; import { PathParams } from '@backstage/core-plugin-api'; @@ -121,6 +124,45 @@ const _default: FrontendPlugin< factory: AnyApiFactory; }; }>; + 'entity-icon-link:scaffolder/launch-template': ExtensionDefinition<{ + kind: 'entity-icon-link'; + name: 'launch-template'; + config: { + label: string | undefined; + title: string | undefined; + filter: EntityPredicate | undefined; + }; + configInput: { + filter?: EntityPredicate | undefined; + label?: string | undefined; + title?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + () => IconLinkVerticalProps, + 'entity-icon-link-props', + {} + >; + inputs: {}; + params: { + useProps: () => Omit; + filter?: EntityPredicate | ((entity: Entity) => boolean); + }; + }>; 'nav-item:scaffolder': ExtensionDefinition<{ kind: 'nav-item'; name: undefined; @@ -325,6 +367,7 @@ export const scaffolderTranslationRef: TranslationRef< readonly 'fields.repoUrlPicker.repository.title': 'Repositories Available'; readonly 'fields.repoUrlPicker.repository.description': 'The name of the repository'; readonly 'fields.repoUrlPicker.repository.inputTitle': 'Repository'; + readonly 'aboutCard.launchTemplate': 'Launch Template'; readonly 'actionsPage.content.emptyState.title': 'No information to display'; readonly 'actionsPage.content.emptyState.description': 'There are no actions installed or there was an issue communicating with backend.'; readonly 'actionsPage.content.searchFieldPlaceholder': 'Search for an action'; @@ -387,6 +430,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/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' }} > - window.open(editUrl, '_blank')}> + diff --git a/plugins/scaffolder/src/alpha/hooks/useScaffolderTemplateIconLinkProps.tsx b/plugins/scaffolder/src/alpha/hooks/useScaffolderTemplateIconLinkProps.tsx new file mode 100644 index 0000000000..87754468fa --- /dev/null +++ b/plugins/scaffolder/src/alpha/hooks/useScaffolderTemplateIconLinkProps.tsx @@ -0,0 +1,53 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DEFAULT_NAMESPACE } from '@backstage/catalog-model'; +import { useApp, useRouteRef } from '@backstage/core-plugin-api'; +import { useEntity } from '@backstage/plugin-catalog-react'; + +import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; +import { taskCreatePermission } from '@backstage/plugin-scaffolder-common/alpha'; +import { usePermission } from '@backstage/plugin-permission-react'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; + +import { scaffolderTranslationRef } from '../../translation'; +import { selectedTemplateRouteRef } from '../../routes'; + +// Note: If you update this hook, please also update the "useScaffolderTemplateIconLinkProps" hook +// in the "plugins/catalog/src/components/AboutCard/AboutCard.tsx" file +/** @alpha */ +export function useScaffolderTemplateIconLinkProps() { + const app = useApp(); + const { entity } = useEntity(); + const templateRoute = useRouteRef(selectedTemplateRouteRef); + const { t } = useTranslationRef(scaffolderTranslationRef); + const Icon = app.getSystemIcon('scaffolder') ?? CreateComponentIcon; + const { allowed: canCreateTemplateTask } = usePermission({ + permission: taskCreatePermission, + }); + + return { + label: t('aboutCard.launchTemplate'), + icon: , + disabled: !templateRoute || !canCreateTemplateTask, + href: + templateRoute && + templateRoute({ + templateName: entity.metadata.name, + namespace: entity.metadata.namespace || DEFAULT_NAMESPACE, + }), + }; +} diff --git a/plugins/scaffolder/src/alpha/plugin.tsx b/plugins/scaffolder/src/alpha/plugin.tsx index aa37f80493..a414c61da5 100644 --- a/plugins/scaffolder/src/alpha/plugin.tsx +++ b/plugins/scaffolder/src/alpha/plugin.tsx @@ -33,12 +33,25 @@ import { scaffolderPage, scaffolderApi, } from './extensions'; +import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { formFieldsApi } from '@backstage/plugin-scaffolder-react/alpha'; import { formDecoratorsApi } from './api'; +import { EntityIconLinkBlueprint } from '@backstage/plugin-catalog-react/alpha'; +import { useScaffolderTemplateIconLinkProps } from './hooks/useScaffolderTemplateIconLinkProps'; + +/** @alpha */ +const scaffolderEntityIconLink = EntityIconLinkBlueprint.make({ + name: 'launch-template', + params: { + filter: isTemplateEntityV1beta3, + useProps: useScaffolderTemplateIconLinkProps, + }, +}); /** @alpha */ export default createFrontendPlugin({ pluginId: 'scaffolder', + info: { packageJson: () => import('../../package.json') }, routes: convertLegacyRouteRefs({ root: rootRouteRef, selectedTemplate: selectedTemplateRouteRef, @@ -56,6 +69,7 @@ export default createFrontendPlugin({ scaffolderApi, scaffolderPage, scaffolderNavItem, + scaffolderEntityIconLink, formDecoratorsApi, formFieldsApi, repoUrlPickerFormField, diff --git a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx index 704d39a8fd..e1a5f37ddc 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'; @@ -50,7 +46,8 @@ type ContextMenuProps = { onStartOver?: () => void; onToggleLogs?: (state: boolean) => void; onToggleButtonBar?: (state: boolean) => void; - taskId?: string; + isCancelButtonDisabled: boolean; + onCancel: () => void; }; const useStyles = makeStyles(() => ({ @@ -70,27 +67,13 @@ export const ContextMenu = (props: ContextMenuProps) => { onStartOver, onToggleLogs, onToggleButtonBar, - taskId, } = props; 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(); 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, }); @@ -171,12 +154,8 @@ export const ContextMenu = (props: ContextMenuProps) => { )} diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx index b718a257b6..c9a119e4a1 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 ( + + + + + + ); +}; + +function OngoingTaskContent(props: { + TemplateOutputsComponent?: ComponentType<{ + output?: ScaffolderTaskOutput; + }>; +}) { const { taskId } = useParams(); const templateRouteRef = useRouteRef(selectedTemplateRouteRef); const navigate = useNavigate(); @@ -183,7 +219,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 +238,11 @@ export const OngoingTask = (props: { const Outputs = props.TemplateOutputsComponent ?? DefaultTemplateOutputs; const cancelEnabled = !(taskStream.cancelled || taskStream.completed); + const isCancelButtonDisabled = + !cancelEnabled || cancelStatus !== 'not-executed' || !canCancelTask; return ( - + <>
@@ -316,6 +355,6 @@ export const OngoingTask = (props: { ) : null} -
+ ); -}; +} diff --git a/plugins/scaffolder/src/components/RenderSchema/RenderSchema.test.tsx b/plugins/scaffolder/src/components/RenderSchema/RenderSchema.test.tsx index 0f1150591d..ec49f414f9 100644 --- a/plugins/scaffolder/src/components/RenderSchema/RenderSchema.test.tsx +++ b/plugins/scaffolder/src/components/RenderSchema/RenderSchema.test.tsx @@ -475,7 +475,7 @@ describe('JSON schema UI rendering', () => { const tr = getByTestId(`properties-row_test.${k}`); expect(tr).toBeInTheDocument(); - const sub = getByTestId(`properties-row_test_sub${i}.${k}`); + const sub = getByTestId(`properties-row_test_oneOf${i}.${k}`); expect(sub).toBeInTheDocument(); expect( @@ -485,7 +485,7 @@ describe('JSON schema UI rendering', () => { ).toBe(true); expect( - getByTestId(`properties-row_test_sub${i}.${k}Flag`), + getByTestId(`properties-row_test_oneOf${i}.${k}Flag`), ).toBeInTheDocument(); } }); @@ -528,10 +528,35 @@ describe('JSON schema UI rendering', () => { for (const i of subs.keys()) { for (const k of Object.keys(subs[i].properties!)) { expect( - rendered.getByTestId(`properties-row_test_sub${i}.${k}`), + rendered.getByTestId(`properties-row_test_oneOf${i}.${k}`), ).toBeInTheDocument(); } } }); + it('property alternatives', async () => { + const schema: JSONSchema7 = { + properties: { + bs: { + anyOf: [ + { + type: 'boolean', + }, + { + type: 'string', + }, + ], + }, + }, + }; + const rendered = await renderInTestApp( + , + ); + expect( + rendered.getByTestId(`root-row_test.bs_anyOf0`), + ).toBeInTheDocument(); + expect( + rendered.getByTestId(`root-row_test.bs_anyOf1`), + ).toBeInTheDocument(); + }); }); }); diff --git a/plugins/scaffolder/src/components/RenderSchema/RenderSchema.tsx b/plugins/scaffolder/src/components/RenderSchema/RenderSchema.tsx index 8f967f8ee2..fc926e6c44 100644 --- a/plugins/scaffolder/src/components/RenderSchema/RenderSchema.tsx +++ b/plugins/scaffolder/src/components/RenderSchema/RenderSchema.tsx @@ -41,37 +41,44 @@ import { JSONSchema7Definition, JSONSchema7Type, } from 'json-schema'; -import { FC, JSX, cloneElement, Fragment } from 'react'; +import { FC, JSX, cloneElement, Fragment, ReactElement } from 'react'; import { scaffolderTranslationRef } from '../../translation'; import { SchemaRenderContext, SchemaRenderStrategy } from './types'; import { TranslationMessages } from '../TemplatingExtensionsPage/types'; -const getTypes = (properties: JSONSchema7) => { - if (!properties.type) { +const compositeSchemaProperties = ['allOf', 'anyOf', 'not', 'oneOf'] as const; + +type subSchemasType = { + [K in (typeof compositeSchemaProperties)[number]]?: JSONSchema7Definition[]; +}; + +const getTypes = (schema: JSONSchema7) => { + if (!schema.type) { + if ( + Object.getOwnPropertyNames(schema).some(p => + compositeSchemaProperties.includes(p as any), + ) + ) { + return undefined; + } return ['unknown']; } - if (properties.type !== 'array') { - return [properties.type].flat(); + if (schema.type !== 'array') { + return [schema.type].flat(); } return [ - `${properties.type}(${ - (properties.items as JSONSchema7 | undefined)?.type ?? 'unknown' + `${schema.type}(${ + (schema.items as JSONSchema7 | undefined)?.type ?? 'unknown' })`, ]; }; -const getSubschemas = ( - schema: JSONSchema7Definition, -): Record => { +const getSubschemas = (schema: JSONSchema7Definition): subSchemasType => { if (typeof schema === 'boolean') { return {}; } const base: Omit = {}; - const compositeSchemaProperties = ['allOf', 'anyOf', 'not', 'oneOf'] as const; - type subSchemasType = { - [K in (typeof compositeSchemaProperties)[number]]?: JSONSchema7Definition[]; - }; const subschemas: subSchemasType = {}; for (const [key, value] of Object.entries(schema) as [ @@ -224,7 +231,10 @@ const inspectSchema = ( return { canSubschema: false, hasEnum: false }; } return { - canSubschema: getTypes(schema).some(t => t.includes('object')), + canSubschema: + Object.getOwnPropertyNames(schema).some(p => + compositeSchemaProperties.includes(p as any), + ) || getTypes(schema)!.some(t => t.includes('object')), hasEnum: !!enumFrom(schema), }; }; @@ -242,8 +252,8 @@ const typeColumn = { const info = inspectSchema(element.schema); return ( <> - {types.map((type, index) => - type.includes('object') || (info.hasEnum && index === 0) ? ( + {types?.map((type, index) => + info.canSubschema || (info.hasEnum && index === 0) ? ( { if (typeof schema === 'object') { - const subschemas = - strategy === 'root' || !context.parent ? getSubschemas(schema) : {}; + const subschemas = getSubschemas(schema); let columns: Column[] | undefined; let elements: SchemaRenderElement[] | undefined; if (strategy === 'root') { @@ -393,90 +402,100 @@ export const RenderSchema = ({ {elements.map(el => { const id = generateId(el, context); const info = inspectSchema(el.schema); - return ( - - - {columns!.map(col => ( - + {columns!.map(col => ( + + {col.render(el, context)} + + ))} + , + ]; + if ( + typeof el.schema !== 'boolean' && + (info.canSubschema || info.hasEnum) + ) { + let details: ReactElement = ( + + {info.canSubschema && ( + - {col.render(el, context)} - - ))} - - {typeof el.schema !== 'boolean' && - (info.canSubschema || info.hasEnum) && ( - - - - - {info.canSubschema && ( - - )} - {info.hasEnum && ( - <> - {cloneElement( - context.headings[0], - {}, - 'Valid values:', - )} - - - )} - - - - + /> )} - - ); + {info.hasEnum && ( + <> + {cloneElement( + context.headings[0], + {}, + 'Valid values:', + )} + + + )} + + ); + if (getTypes(el.schema)) { + details = ( + + {details} + + ); + } + rows.push( + + + {details} + + , + ); + } + return {rows}; })} )} - {Object.keys(subschemas).map(sk => ( + {(Object.keys(subschemas) as Array).map(sk => ( {cloneElement(context.headings[0], {}, sk)} - {subschemas[sk].map((sub, index) => ( + {subschemas[sk]!.map((sub, index) => ( ], }; return ( - + {filter.description && } @@ -84,7 +84,7 @@ const FilterDetailContent = ({ const [argSchema, required] = inspectFunctionArgSchema(arg); return ( - +
@@ -101,7 +101,7 @@ const FilterDetailContent = ({ }} schema={argSchema} /> - + ); })} @@ -133,7 +133,7 @@ const FilterDetailContent = ({ )} - + ); }; @@ -178,7 +178,7 @@ export const TemplateFilters = ({ > {name} - {React.cloneElement(baseLink, { + {cloneElement(baseLink, { to: `${baseLink.props.to}#${fragment}`, })} 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({}); if (!Object.keys(fn).length) { return ( - - {t('templatingExtensions.content.functions.notAvailable')} + + {t('templatingExtensions.content.functions.metadataAbsent')} ); } @@ -60,7 +63,7 @@ const FunctionDetailContent = ({ headings: [], }; return ( - + {fn.description && } {schema?.arguments?.length && ( @@ -71,7 +74,7 @@ const FunctionDetailContent = ({ const [argSchema, required] = inspectFunctionArgSchema(arg); return ( - +
@@ -89,7 +92,7 @@ const FunctionDetailContent = ({ }} schema={argSchema} /> - + ); })} @@ -121,7 +124,7 @@ const FunctionDetailContent = ({ )} - + ); }; @@ -166,7 +169,7 @@ export const TemplateGlobalFunctions = ({ > {name} - {React.cloneElement(baseLink, { + {cloneElement(baseLink, { to: `${baseLink.props.to}#${fragment}`, })} @@ -218,7 +221,7 @@ export const TemplateGlobalValues = ({ > {name} - {React.cloneElement(baseLink, { + {cloneElement(baseLink, { to: `${baseLink.props.to}#${fragment}`, })} {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/translation.ts b/plugins/scaffolder/src/translation.ts index 2354410924..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', @@ -223,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 fa2816ea35..71c11e8986 100644 --- a/plugins/search-backend-module-catalog/CHANGELOG.md +++ b/plugins/search-backend-module-catalog/CHANGELOG.md @@ -1,5 +1,46 @@ # @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 + +## 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 diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index d3f2942ee0..12f3a1001c 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.2", + "version": "0.3.5-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 b2d00455d0..b51ab568bd 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,37 @@ # @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 + +## 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 diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 8f4df74367..a989e08cd9 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.3", + "version": "1.7.3-next.1", "description": "A module for the search backend that implements search using ElasticSearch", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-explore/CHANGELOG.md b/plugins/search-backend-module-explore/CHANGELOG.md index fc0f405a24..2b34facfe7 100644 --- a/plugins/search-backend-module-explore/CHANGELOG.md +++ b/plugins/search-backend-module-explore/CHANGELOG.md @@ -1,5 +1,33 @@ # @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 + +## 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 diff --git a/plugins/search-backend-module-explore/package.json b/plugins/search-backend-module-explore/package.json index 92ef1a18e7..b6c1f5aae3 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.2", + "version": "0.3.3-next.1", "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 47c2a65de4..8334585cf9 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,33 @@ # @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 + +## 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 diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 0686bac80d..25533d1ad2 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.2", + "version": "0.5.45-next.1", "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 99ee569733..f0a9051be9 100644 --- a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md +++ b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md @@ -1,5 +1,33 @@ # @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 + +## 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 diff --git a/plugins/search-backend-module-stack-overflow-collator/package.json b/plugins/search-backend-module-stack-overflow-collator/package.json index b5d0258dcf..01f1389f35 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.2", + "version": "0.3.10-next.1", "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 62d6f6a1d8..1f72c806e9 100644 --- a/plugins/search-backend-module-techdocs/CHANGELOG.md +++ b/plugins/search-backend-module-techdocs/CHANGELOG.md @@ -1,5 +1,48 @@ # @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 + +## 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 diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index 22683c1179..3cec11b059 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.3", + "version": "0.4.3-next.1", "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 7cd55d993c..fe9bcce037 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,34 @@ # @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 + +## 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 diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index cd16c32b8b..b54fc5b403 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.2", + "version": "1.3.12-next.1", "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 1a9875b526..b62ddb9077 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,64 @@ # @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 + +## 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 diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index a45345bbe5..4b1d775e05 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.3", + "version": "2.0.3-next.2", "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-common/CHANGELOG.md b/plugins/search-common/CHANGELOG.md index 62f058ccc5..e6f08e2183 100644 --- a/plugins/search-common/CHANGELOG.md +++ b/plugins/search-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-search-common +## 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..1c68dd7762 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.18", "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 1551669adc..66ef26a91f 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,45 @@ # @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 + +## 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 diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index 3e4e7ba821..3765093f89 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.2", + "version": "1.9.1-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 d3763bfcbf..861958a8ea 100644 --- a/plugins/search-react/report-alpha.api.md +++ b/plugins/search-react/report-alpha.api.md @@ -101,6 +101,7 @@ export const searchReactTranslationRef: TranslationRef< { 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}}'; diff --git a/plugins/search-react/src/components/SearchBar/SearchBar.tsx b/plugins/search-react/src/components/SearchBar/SearchBar.tsx index d2a1d08283..41074a22fb 100644 --- a/plugins/search-react/src/components/SearchBar/SearchBar.tsx +++ b/plugins/search-react/src/components/SearchBar/SearchBar.tsx @@ -154,7 +154,7 @@ export const SearchBarBase = forwardRef((props: SearchBarBaseProps, ref) => { const clearButtonEndAdornment = ( ); diff --git a/plugins/search-react/src/translation.ts b/plugins/search-react/src/translation.ts index 6897911949..e4c394d952 100644 --- a/plugins/search-react/src/translation.ts +++ b/plugins/search-react/src/translation.ts @@ -25,6 +25,7 @@ export const searchReactTranslationRef = createTranslationRef({ searchBar: { title: 'Search', placeholder: 'Search in {{org}}', + clearButtonTitle: 'Clear', }, searchFilter: { allOptionTitle: 'All', diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 66b7f98985..96d1bc3eb0 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,66 @@ # @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 + +## 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 diff --git a/plugins/search/package.json b/plugins/search/package.json index c9bff12da7..c1ca483621 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "1.4.26-next.3", + "version": "1.4.27-next.2", "description": "The Backstage plugin that provides your backstage app with search", "backstage": { "role": "frontend-plugin", diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx index 5f55b46028..136f95f39e 100644 --- a/plugins/search/src/alpha.tsx +++ b/plugins/search/src/alpha.tsx @@ -279,6 +279,7 @@ export const searchNavItem = NavItemBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'search', + info: { packageJson: () => import('../package.json') }, extensions: [searchApi, searchPage, searchNavItem], routes: convertLegacyRouteRefs({ root: rootRouteRef, diff --git a/plugins/signals-backend/CHANGELOG.md b/plugins/signals-backend/CHANGELOG.md index d53dc508e8..3f09417863 100644 --- a/plugins/signals-backend/CHANGELOG.md +++ b/plugins/signals-backend/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-signals-backend +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-signals-node@0.1.21-next.1 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + - @backstage/plugin-signals-node@0.1.21-next.0 + +## 0.3.4 + +### Patch Changes + +- 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 diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index 65d7e3ab4a..cb9146c9c4 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-backend", - "version": "0.3.4-next.2", + "version": "0.3.5-next.1", "backstage": { "role": "backend-plugin", "pluginId": "signals", diff --git a/plugins/signals-node/CHANGELOG.md b/plugins/signals-node/CHANGELOG.md index bebb00934c..8e753081d8 100644 --- a/plugins/signals-node/CHANGELOG.md +++ b/plugins/signals-node/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-signals-node +## 0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## 0.1.20 + +### Patch Changes + +- 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 diff --git a/plugins/signals-node/package.json b/plugins/signals-node/package.json index 1b1f235cf7..4cb5c08f79 100644 --- a/plugins/signals-node/package.json +++ b/plugins/signals-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-node", - "version": "0.1.20-next.2", + "version": "0.1.21-next.1", "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..43aee22b12 100644 --- a/plugins/signals-react/CHANGELOG.md +++ b/plugins/signals-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-signals-react +## 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..d1fa5f9392 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.13", "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..846d0254eb 100644 --- a/plugins/signals/CHANGELOG.md +++ b/plugins/signals/CHANGELOG.md @@ -1,5 +1,38 @@ # @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 + +## 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..931f4744d1 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.20-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "signals", diff --git a/plugins/signals/src/alpha.tsx b/plugins/signals/src/alpha.tsx index ba7f72f420..90b78b05fd 100644 --- a/plugins/signals/src/alpha.tsx +++ b/plugins/signals/src/alpha.tsx @@ -45,5 +45,6 @@ const api = ApiBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'signals', + info: { packageJson: () => import('../package.json') }, extensions: [api], }); diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index 2dc7e1ba49..d12d7decd6 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,62 @@ # @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 + +## 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 diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index aaa9864a08..62ce0f7a34 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.3", + "version": "1.0.49-next.2", "backstage": { "role": "web-library", "pluginId": "techdocs-addons", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 8d1dd79d28..c8f2e882e2 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,75 @@ # @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 + +## 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 diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index ab2f3dd17f..76e3aa0a0d 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.3", + "version": "2.0.3-next.2", "description": "The Backstage backend plugin that renders technical documentation for your components", "backstage": { "role": "backend-plugin", diff --git a/plugins/techdocs-common/CHANGELOG.md b/plugins/techdocs-common/CHANGELOG.md index 619884e4ec..bf2a3300ea 100644 --- a/plugins/techdocs-common/CHANGELOG.md +++ b/plugins/techdocs-common/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-techdocs-common +## 0.1.1-next.0 + +### Patch Changes + +- ec7b35d: Introduced `backstage.io/techdocs-entity-path` annotation which allows deep linking into another entities TechDocs in conjunction with `backstage.io/techdocs-entity`. + ## 0.1.0 ### Minor Changes diff --git a/plugins/techdocs-common/package.json b/plugins/techdocs-common/package.json index 001d1106a3..ef0a6c613e 100644 --- a/plugins/techdocs-common/package.json +++ b/plugins/techdocs-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-common", - "version": "0.1.0", + "version": "0.1.1-next.0", "description": "Common functionality for TechDocs", "backstage": { "role": "common-library", diff --git a/plugins/techdocs-common/report.api.md b/plugins/techdocs-common/report.api.md index c7e1829a28..56a86778a8 100644 --- a/plugins/techdocs-common/report.api.md +++ b/plugins/techdocs-common/report.api.md @@ -8,4 +8,8 @@ export const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref'; // @public (undocumented) export const TECHDOCS_EXTERNAL_ANNOTATION = 'backstage.io/techdocs-entity'; + +// @public (undocumented) +export const TECHDOCS_EXTERNAL_PATH_ANNOTATION = + 'backstage.io/techdocs-entity-path'; ``` diff --git a/plugins/techdocs-common/src/constants.ts b/plugins/techdocs-common/src/constants.ts index 40187f41ce..a31951204e 100644 --- a/plugins/techdocs-common/src/constants.ts +++ b/plugins/techdocs-common/src/constants.ts @@ -18,3 +18,6 @@ export const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref'; /** @public */ export const TECHDOCS_EXTERNAL_ANNOTATION = 'backstage.io/techdocs-entity'; +/** @public */ +export const TECHDOCS_EXTERNAL_PATH_ANNOTATION = + 'backstage.io/techdocs-entity-path'; diff --git a/plugins/techdocs-module-addons-contrib/CHANGELOG.md b/plugins/techdocs-module-addons-contrib/CHANGELOG.md index cc6d00815d..2ca19f1fbc 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,51 @@ # @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 + +## 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 diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 14d5280b55..ad75ffb59d 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.3", + "version": "1.1.25-next.2", "description": "Plugin module for contributed TechDocs Addons", "backstage": { "role": "frontend-plugin-module", diff --git a/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.tsx b/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.tsx index 5e5bd939fc..83e34f411a 100644 --- a/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.tsx +++ b/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.tsx @@ -92,16 +92,52 @@ export const ExpandableNavigationAddon = () => { }, [expanded], ); - + const handleKeyPass = ( + event: React.KeyboardEvent, + toggleAction: () => void, + ) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + toggleAction(); + } + }; useEffect(() => { // There is no nested navs if (!checkboxToggles?.length) return; setHasNavSubLevels(true); checkboxToggles.forEach(item => { - if (shouldToggle(item)) item.click(); + item.tabIndex = 0; + const toggleAction = () => { + if (shouldToggle(item)) { + item.click(); + } + }; + // Add keyboard event listener + const keydownHandler = (event: KeyboardEvent) => { + handleKeyPass( + event as unknown as React.KeyboardEvent, + toggleAction, + ); + }; + item.addEventListener('keydown', keydownHandler); + item.addEventListener('click', toggleAction); + + // Clean up event listener or unmount + return () => { + item.removeEventListener('keydown', keydownHandler); + item.removeEventListener('click', toggleAction); + }; }); - }, [expanded, shouldToggle, checkboxToggles]); + }, [checkboxToggles, shouldToggle]); + useEffect(() => { + if (!checkboxToggles?.length) return; + checkboxToggles.forEach(item => { + if (shouldToggle(item)) { + item.click(); + } + }); + }, [expanded, checkboxToggles, shouldToggle]); const handleState = () => { setExpanded(prevState => ({ @@ -115,6 +151,9 @@ export const ExpandableNavigationAddon = () => { handleKeyPass(event, handleState)} + tabIndex={0} // Ensuring keyboard focus + aria-expanded={expanded?.expandAllNestedNavs} // Accessibility aria-label={ expanded?.expandAllNestedNavs ? 'collapse-nav' : 'expand-nav' } diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index 76b9836cd3..85a11b495c 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-techdocs-node +## 1.13.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/integration-aws-node@0.1.16 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + +## 1.13.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + +## 1.13.3 + +### Patch Changes + +- 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 diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index ff5986a5d4..25aaa92014 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-node", - "version": "1.13.3-next.3", + "version": "1.13.4-next.1", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", "backstage": { "role": "node-library", diff --git a/plugins/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index 1713b0f271..99dd4b09ae 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,43 @@ # @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 + +## 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..f045b74c58 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.0-next.1", "description": "Shared frontend utilities for TechDocs and Addons", "backstage": { "role": "web-library", @@ -63,6 +63,7 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", + "@backstage/plugin-techdocs-common": "workspace:^", "@backstage/version-bridge": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/styles": "^4.11.0", diff --git a/plugins/techdocs-react/report.api.md b/plugins/techdocs-react/report.api.md index 23a9c9faaf..62ac6375fa 100644 --- a/plugins/techdocs-react/report.api.md +++ b/plugins/techdocs-react/report.api.md @@ -15,8 +15,21 @@ import { JSX as JSX_2 } from 'react/jsx-runtime'; import { MemoExoticComponent } from 'react'; import { PropsWithChildren } from 'react'; import { ReactNode } from 'react'; +import { RouteFunc } from '@backstage/core-plugin-api'; import { SetStateAction } from 'react'; +// @public +export const buildTechDocsURL: ( + entity: Entity, + routeFunc: + | RouteFunc<{ + namespace: string; + kind: string; + name: string; + }> + | undefined, +) => string | undefined; + // @public export function createTechDocsAddonExtension( options: TechDocsAddonOptions, @@ -27,6 +40,9 @@ export function createTechDocsAddonExtension( options: TechDocsAddonOptions, ): Extension<(props: TComponentProps) => JSX.Element | null>; +// @public +export function getEntityRootTechDocsPath(entity: Entity): string; + // @public export const SHADOW_DOM_STYLE_LOAD_EVENT = 'TECH_DOCS_SHADOW_DOM_STYLE_LOAD'; diff --git a/plugins/techdocs-react/src/helpers.ts b/plugins/techdocs-react/src/helpers.ts index 3f10fff2ba..e6daf99b26 100644 --- a/plugins/techdocs-react/src/helpers.ts +++ b/plugins/techdocs-react/src/helpers.ts @@ -15,7 +15,17 @@ */ import { Config } from '@backstage/config'; -import { CompoundEntityRef } from '@backstage/catalog-model'; +import { + CompoundEntityRef, + Entity, + getCompoundEntityRef, + parseEntityRef, +} from '@backstage/catalog-model'; +import { + TECHDOCS_EXTERNAL_ANNOTATION, + TECHDOCS_EXTERNAL_PATH_ANNOTATION, +} from '@backstage/plugin-techdocs-common'; +import { RouteFunc } from '@backstage/core-plugin-api'; /** * Lower-case entity triplets by default, but allow override. @@ -35,3 +45,67 @@ export function toLowercaseEntityRefMaybe( return entityRef; } + +/** + * Get the entity path annotation from the given entity and ensure it starts with a slash. + * + * @public + */ +export function getEntityRootTechDocsPath(entity: Entity): string { + let path = entity.metadata.annotations?.[TECHDOCS_EXTERNAL_PATH_ANNOTATION]; + if (!path) { + return ''; + } + if (!path.startsWith('/')) { + path = `/${path}`; + } + return path; +} + +/** + * Build the TechDocs URL for the given entity. This helper should be used anywhere there + * is a link to an entities TechDocs. + * + * @public + */ +export const buildTechDocsURL = ( + entity: Entity, + routeFunc: + | RouteFunc<{ + namespace: string; + kind: string; + name: string; + }> + | undefined, +) => { + if (!routeFunc) { + return undefined; + } + + let { namespace, kind, name } = getCompoundEntityRef(entity); + + if (entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION]) { + try { + const techdocsRef = parseEntityRef( + entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION], + ); + namespace = techdocsRef.namespace; + kind = techdocsRef.kind; + name = techdocsRef.name; + } catch { + // not a fan of this but we don't care if the parseEntityRef fails + } + } + + const url = routeFunc({ + namespace, + kind, + name, + }); + + // Add on the external entity path to the url if one exists. This allows deep linking into another + // entities TechDocs. + const path = getEntityRootTechDocsPath(entity); + + return `${url}${path}`; +}; diff --git a/plugins/techdocs-react/src/index.ts b/plugins/techdocs-react/src/index.ts index 5cbab1a326..7c4f316c1e 100644 --- a/plugins/techdocs-react/src/index.ts +++ b/plugins/techdocs-react/src/index.ts @@ -52,4 +52,8 @@ export { useShadowRootElements, useShadowRootSelection, } from './hooks'; -export { toLowercaseEntityRefMaybe } from './helpers'; +export { + toLowercaseEntityRefMaybe, + getEntityRootTechDocsPath, + buildTechDocsURL, +} from './helpers'; diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 747eccd17b..c1c1b8d2b6 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,102 @@ # @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 + +## 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 diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index a488863a25..0c98b9e89a 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "1.12.6-next.3", + "version": "1.13.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..ce1d10a00c 100644 --- a/plugins/techdocs/report-alpha.api.md +++ b/plugins/techdocs/report-alpha.api.md @@ -14,6 +14,7 @@ import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; +import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; @@ -173,6 +174,45 @@ const _default: FrontendPlugin< filter?: string | EntityPredicate | ((entity: Entity) => boolean); }; }>; + 'entity-icon-link:techdocs/read-docs': ExtensionDefinition<{ + kind: 'entity-icon-link'; + name: 'read-docs'; + config: { + label: string | undefined; + title: string | undefined; + filter: EntityPredicate | undefined; + }; + configInput: { + filter?: EntityPredicate | undefined; + label?: string | undefined; + title?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + () => IconLinkVerticalProps, + 'entity-icon-link-props', + {} + >; + inputs: {}; + params: { + useProps: () => Omit; + filter?: EntityPredicate | ((entity: Entity) => boolean); + }; + }>; 'nav-item:techdocs': ExtensionDefinition<{ kind: 'nav-item'; name: undefined; diff --git a/plugins/techdocs/report.api.md b/plugins/techdocs/report.api.md index bb7ff9e5a5..2369466674 100644 --- a/plugins/techdocs/report.api.md +++ b/plugins/techdocs/report.api.md @@ -431,6 +431,7 @@ export const TechDocsReaderPageContent: ( // @public export type TechDocsReaderPageContentProps = { entityRef?: CompoundEntityRef; + defaultPath?: string; withSearch?: boolean; searchResultUrlMapper?: (url: string) => string; onReady?: () => void; diff --git a/plugins/techdocs/src/EntityPageDocs.tsx b/plugins/techdocs/src/EntityPageDocs.tsx index 68c26cd1c3..14dcb00d29 100644 --- a/plugins/techdocs/src/EntityPageDocs.tsx +++ b/plugins/techdocs/src/EntityPageDocs.tsx @@ -20,6 +20,7 @@ import { parseEntityRef, } from '@backstage/catalog-model'; import { TECHDOCS_EXTERNAL_ANNOTATION } from '@backstage/plugin-techdocs-common'; +import { getEntityRootTechDocsPath } from '@backstage/plugin-techdocs-react'; import { TechDocsReaderPage } from './plugin'; import { TechDocsReaderPageContent } from './reader/components/TechDocsReaderPageContent'; @@ -52,12 +53,15 @@ export const EntityPageDocs = ({ } } + const defaultPath = getEntityRootTechDocsPath(entity); + return ( ); diff --git a/plugins/techdocs/src/alpha/hooks/useTechdocsReaderIconLinkProps.tsx b/plugins/techdocs/src/alpha/hooks/useTechdocsReaderIconLinkProps.tsx new file mode 100644 index 0000000000..0cd2cf9ca1 --- /dev/null +++ b/plugins/techdocs/src/alpha/hooks/useTechdocsReaderIconLinkProps.tsx @@ -0,0 +1,51 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import DocsIcon from '@material-ui/icons/Description'; + +import { useRouteRef } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; + +import { + TECHDOCS_ANNOTATION, + TECHDOCS_EXTERNAL_ANNOTATION, +} from '@backstage/plugin-techdocs-common'; +import { buildTechDocsURL } from '@backstage/plugin-techdocs-react'; + +import { useEntity } from '@backstage/plugin-catalog-react'; + +import { techdocsTranslationRef } from '../../translation'; +import { rootDocsRouteRef } from '../../routes'; + +// Note: If you update this hook, please also update the "useTechdocsReaderIconLinkProps" hook +// in the "plugins/catalog/src/components/AboutCard/AboutCard.tsx" file +/** @alpha */ +export function useTechdocsReaderIconLinkProps() { + const { entity } = useEntity(); + const viewTechdocLink = useRouteRef(rootDocsRouteRef); + const { t } = useTranslationRef(techdocsTranslationRef); + + return { + label: t('aboutCard.viewTechdocs'), + disabled: + !( + entity.metadata.annotations?.[TECHDOCS_ANNOTATION] || + entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION] + ) || !viewTechdocLink, + icon: , + href: buildTechDocsURL(entity, viewTechdocLink), + }; +} diff --git a/plugins/techdocs/src/alpha.tsx b/plugins/techdocs/src/alpha/index.tsx similarity index 88% rename from plugins/techdocs/src/alpha.tsx rename to plugins/techdocs/src/alpha/index.tsx index b2bf794e73..44205a480a 100644 --- a/plugins/techdocs/src/alpha.tsx +++ b/plugins/techdocs/src/alpha/index.tsx @@ -35,16 +35,19 @@ import { convertLegacyRouteRef, convertLegacyRouteRefs, } from '@backstage/core-compat-api'; -import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; +import { + EntityContentBlueprint, + EntityIconLinkBlueprint, +} from '@backstage/plugin-catalog-react/alpha'; import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha'; import { AddonBlueprint } from '@backstage/plugin-techdocs-react/alpha'; -import { TechDocsClient, TechDocsStorageClient } from './client'; +import { TechDocsClient, TechDocsStorageClient } from '../client'; import { rootCatalogDocsRouteRef, rootDocsRouteRef, rootRouteRef, -} from './routes'; -import { TechDocsReaderLayout } from './reader'; +} from '../routes'; +import { TechDocsReaderLayout } from '../reader'; import { attachTechDocsAddonComponentData } from '@backstage/plugin-techdocs-react/alpha'; import { TechDocsAddons, @@ -52,6 +55,16 @@ import { techdocsStorageApiRef, } from '@backstage/plugin-techdocs-react'; +import { useTechdocsReaderIconLinkProps } from './hooks/useTechdocsReaderIconLinkProps'; + +/** @alpha */ +const techdocsEntityIconLink = EntityIconLinkBlueprint.make({ + name: 'read-docs', + params: { + useProps: useTechdocsReaderIconLinkProps, + }, +}); + /** @alpha */ const techDocsStorageApi = ApiBlueprint.make({ name: 'storage', @@ -109,7 +122,7 @@ export const techDocsSearchResultListItemExtension = predicate: result => result.type === 'techdocs', component: async () => { const { TechDocsSearchResultListItem } = await import( - './search/components/TechDocsSearchResultListItem' + '../search/components/TechDocsSearchResultListItem' ); return props => compatWrapper( @@ -130,7 +143,7 @@ const techDocsPage = PageBlueprint.make({ defaultPath: '/docs', routeRef: convertLegacyRouteRef(rootRouteRef), loader: () => - import('./home/components/TechDocsIndexPage').then(m => + import('../home/components/TechDocsIndexPage').then(m => compatWrapper(), ), }, @@ -158,7 +171,7 @@ const techDocsReaderPage = PageBlueprint.makeWithOverrides({ defaultPath: '/docs/:namespace/:kind/:name', routeRef: convertLegacyRouteRef(rootDocsRouteRef), loader: async () => - await import('./Router').then(({ TechDocsReaderRouter }) => { + await import('../Router').then(({ TechDocsReaderRouter }) => { return compatWrapper( @@ -193,7 +206,7 @@ const techDocsEntityContent = EntityContentBlueprint.makeWithOverrides({ defaultTitle: 'TechDocs', routeRef: convertLegacyRouteRef(rootCatalogDocsRouteRef), loader: () => - import('./Router').then(({ EmbeddedDocsRouter }) => { + import('../Router').then(({ EmbeddedDocsRouter }) => { const addons = context.inputs.addons.map(output => { const options = output.get(AddonBlueprint.dataRefs.addon); const Addon = options.component; @@ -236,12 +249,14 @@ const techDocsNavItem = NavItemBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'techdocs', + info: { packageJson: () => import('../../package.json') }, extensions: [ techDocsClientApi, techDocsStorageApi, techDocsNavItem, techDocsPage, techDocsReaderPage, + techdocsEntityIconLink, techDocsEntityContent, techDocsEntityContentEmptyState, techDocsSearchResultListItemExtension, diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.test.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.test.tsx index a8e7ae7287..77bb3d134f 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.test.tsx @@ -16,7 +16,10 @@ import { ReactNode } from 'react'; import { waitFor } from '@testing-library/react'; -import { CompoundEntityRef } from '@backstage/catalog-model'; +import { + CompoundEntityRef, + getCompoundEntityRef, +} from '@backstage/catalog-model'; import { techdocsApiRef, TechDocsReaderPageProvider, @@ -121,6 +124,33 @@ describe('', () => { }); }); + it('should render techdocs page content with default path', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + useTechDocsReaderDom.mockReturnValue(document.createElement('html')); + useReaderState.mockReturnValue({ state: 'cached' }); + + const defaultPath = '/some/path'; + + const rendered = await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect( + rendered.getByTestId('techdocs-native-shadowroot'), + ).toBeInTheDocument(); + }); + + const entityRef = getCompoundEntityRef(mockEntityMetadata); + expect(useTechDocsReaderDom).toHaveBeenCalledWith(entityRef, defaultPath); + }); + it('should not render techdocs content if entity metadata is missing', async () => { getEntityMetadata.mockResolvedValue(undefined); useTechDocsReaderDom.mockReturnValue(document.createElement('html')); diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx index 1db97a82b8..fe72b09073 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx @@ -61,6 +61,11 @@ export type TechDocsReaderPageContentProps = { * @deprecated No need to pass down entityRef as property anymore. Consumes the entityName from `TechDocsReaderPageContext`. Use the {@link @backstage/plugin-techdocs-react#useTechDocsReaderPage} hook for custom reader page content. */ entityRef?: CompoundEntityRef; + /** + * Path in the docs to render by default. This should be used when rendering docs for an entity that specifies the + * "backstage.io/techdocs-entity-path" annotation for deep linking into another entities docs. + */ + defaultPath?: string; /** * Show or hide the search bar, defaults to true. */ @@ -93,7 +98,7 @@ export const TechDocsReaderPageContent = withTechDocsReaderProvider( setShadowRoot, } = useTechDocsReaderPage(); const { state } = useTechDocsReader(); - const dom = useTechDocsReaderDom(entityRef); + const dom = useTechDocsReaderDom(entityRef, props.defaultPath); const path = window.location.pathname; const hash = window.location.hash; const isStyleLoading = useShadowDomStylesLoading(dom); diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx index 9dbb9745d3..0c4d6dee3e 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx @@ -14,7 +14,13 @@ * limitations under the License. */ -import { useCallback, useEffect, useState } from 'react'; +import { + useCallback, + useEffect, + useLayoutEffect, + // useRef, + useState, +} from 'react'; import useMediaQuery from '@material-ui/core/useMediaQuery'; import { useTheme } from '@material-ui/core/styles'; @@ -45,12 +51,30 @@ import { useSanitizerTransformer, useStylesTransformer, handleMetaRedirects, + addNavLinkKeyboardToggle, } from '../../transformers'; import { useNavigateUrl } from './useNavigateUrl'; -import { useParams } from 'react-router-dom'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; const MOBILE_MEDIA_QUERY = 'screen and (max-width: 76.1875em)'; +// If a defaultPath is specified then we should navigate to that path replacing the +// current location in the history. This should only happen on the initial load so +// navigating to the root of the docs doesn't also redirect. +const useInitialRedirect = (defaultPath?: string) => { + // const hasRun = useRef(false); + + const location = useLocation(); + const navigate = useNavigate(); + const { '*': currPath = '' } = useParams(); + + useLayoutEffect(() => { + if (currPath === '' && defaultPath !== '') { + navigate(`${location.pathname}${defaultPath}`, { replace: true }); + } + }, []); // eslint-disable-line react-hooks/exhaustive-deps +}; + /** * Hook that encapsulates the behavior of getting raw HTML and applying * transforms to it in order to make it function at a basic level in the @@ -58,6 +82,7 @@ const MOBILE_MEDIA_QUERY = 'screen and (max-width: 76.1875em)'; */ export const useTechDocsReaderDom = ( entityRef: CompoundEntityRef, + defaultPath?: string, ): Element | null => { const navigate = useNavigateUrl(); const theme = useTheme(); @@ -76,6 +101,8 @@ export const useTechDocsReaderDom = ( const [dom, setDom] = useState(null); const isStyleLoading = useShadowDomStylesLoading(dom); + useInitialRedirect(defaultPath); + const updateSidebarPositionAndHeight = useCallback(() => { if (!dom) return; @@ -267,6 +294,7 @@ export const useTechDocsReaderDom = ( }, onLoaded: () => {}, }), + addNavLinkKeyboardToggle(), ]), [theme, navigate, analytics, entityRef.name, configApi], ); diff --git a/plugins/techdocs/src/reader/transformers/addNavLinkKeyboardToggle.test.ts b/plugins/techdocs/src/reader/transformers/addNavLinkKeyboardToggle.test.ts new file mode 100644 index 0000000000..3af6151164 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/addNavLinkKeyboardToggle.test.ts @@ -0,0 +1,77 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { addNavLinkKeyboardToggle } from './addNavLinkKeyboardToggle'; +import { createTestShadowDom } from '../../test-utils'; +import { act } from '@testing-library/react'; + +describe('addNavLinkKeyboardToggle', () => { + it('adds tabindex and toggles checkbox on Enter/Space', async () => { + let shadowDom: ShadowRoot; + await act(async () => { + shadowDom = await createTestShadowDom( + ` + + + `, + { + preTransformers: [], + postTransformers: [addNavLinkKeyboardToggle()], + }, + ); + }); + const label = shadowDom!.querySelector('label.md-nav__link')!; + const checkbox = shadowDom!.querySelector('#cb1') as HTMLInputElement; + expect(label.getAttribute('tabIndex')).toBe('0'); + + // Simulate keydown: Enter + await act(async () => { + label.dispatchEvent( + new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }), + ); + }); + expect(checkbox.checked).toBe(true); + + // Simulate keydown: space + await act(async () => { + label.dispatchEvent( + new KeyboardEvent('keydown', { key: ' ', bubbles: true }), + ); + }); + expect(checkbox.checked).toBe(false); + }); + + it('does nothing if no for attribute or checkbox', async () => { + let shadowDom: ShadowRoot; + await act(async () => { + shadowDom = await createTestShadowDom( + ``, + { + preTransformers: [], + postTransformers: [addNavLinkKeyboardToggle()], + }, + ); + }); + const label = shadowDom!.querySelector('label.md-nav__link')!; + await act(async () => { + expect(() => + label.dispatchEvent( + new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }), + ), + ).not.toThrow(); + }); + }); +}); diff --git a/plugins/techdocs/src/reader/transformers/addNavLinkKeyboardToggle.ts b/plugins/techdocs/src/reader/transformers/addNavLinkKeyboardToggle.ts new file mode 100644 index 0000000000..9f29df1576 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/addNavLinkKeyboardToggle.ts @@ -0,0 +1,41 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function addNavLinkKeyboardToggle() { + return (element: Element) => { + const navLabels = element.querySelectorAll('label.md-nav__link[for]'); + navLabels.forEach(label => { + label.setAttribute('tabIndex', '0'); + label.addEventListener('keydown', event => { + const keyboardEvent = event as KeyboardEvent; + if (keyboardEvent.key === 'Enter' || keyboardEvent.key === ' ') { + const forId = label.getAttribute('for'); + if (!forId) return; + const checkbox = element.querySelector( + `#${forId}`, + ) as HTMLInputElement | null; + if (checkbox && checkbox.type === 'checkbox') { + checkbox.checked = !checkbox.checked; + checkbox.dispatchEvent(new Event('change', { bubbles: true })); + event.preventDefault(); + event.stopPropagation(); + } + } + }); + }); + return element; + }; +} diff --git a/plugins/techdocs/src/reader/transformers/index.ts b/plugins/techdocs/src/reader/transformers/index.ts index 7fdf47b009..571725ed25 100644 --- a/plugins/techdocs/src/reader/transformers/index.ts +++ b/plugins/techdocs/src/reader/transformers/index.ts @@ -28,3 +28,4 @@ export * from './onCssReady'; export * from './scrollIntoNavigation'; export * from './transformer'; export * from './handleMetaRedirects'; +export * from './addNavLinkKeyboardToggle'; diff --git a/plugins/techdocs/src/translation.ts b/plugins/techdocs/src/translation.ts new file mode 100644 index 0000000000..aa62246788 --- /dev/null +++ b/plugins/techdocs/src/translation.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; + +/** @alpha */ +export const techdocsTranslationRef = createTranslationRef({ + id: 'techdocs', + messages: { + aboutCard: { + viewTechdocs: 'View TechDocs', + }, + }, +}); diff --git a/plugins/user-settings-backend/CHANGELOG.md b/plugins/user-settings-backend/CHANGELOG.md index 7920484835..a6a5b22ba5 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,54 @@ # @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 + +## 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 diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 70c77f740b..b78a2a32ca 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.3", + "version": "0.3.3-next.2", "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 1d2d5aa0dc..dbfc2fc931 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,73 @@ # @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 + +## 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 diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 66d768d102..2c16df4877 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.3", + "version": "0.8.23-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..036ff2ff58 100644 --- a/plugins/user-settings/report-alpha.api.md +++ b/plugins/user-settings/report-alpha.api.md @@ -154,6 +154,8 @@ export const userSettingsTranslationRef: TranslationRef< readonly 'defaultSettingsPage.tabsTitle.general': 'General'; readonly 'settingsLayout.title': 'Settings'; readonly sidebarTitle: 'Settings'; + readonly 'profileCard.title': 'Profile'; + readonly 'appearanceCard.title': 'Appearance'; } >; diff --git a/plugins/user-settings/src/alpha.tsx b/plugins/user-settings/src/alpha.tsx index 228c6a23d2..f351dad40a 100644 --- a/plugins/user-settings/src/alpha.tsx +++ b/plugins/user-settings/src/alpha.tsx @@ -69,6 +69,7 @@ export const settingsNavItem = NavItemBlueprint.make({ */ export default createFrontendPlugin({ pluginId: 'user-settings', + info: { packageJson: () => import('../package.json') }, extensions: [userSettingsPage, settingsNavItem], routes: convertLegacyRouteRefs({ root: settingsRouteRef, diff --git a/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx b/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx index ca7378235d..0fa07eb5fa 100644 --- a/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx +++ b/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx @@ -19,13 +19,16 @@ import List from '@material-ui/core/List'; import { UserSettingsPinToggle } from './UserSettingsPinToggle'; import { UserSettingsThemeToggle } from './UserSettingsThemeToggle'; import { UserSettingsLanguageToggle } from './UserSettingsLanguageToggle'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { userSettingsTranslationRef } from '../../translation'; /** @public */ export const UserSettingsAppearanceCard = () => { const { isMobile } = useSidebarPinState(); + const { t } = useTranslationRef(userSettingsTranslationRef); return ( - + diff --git a/plugins/user-settings/src/components/General/UserSettingsProfileCard.tsx b/plugins/user-settings/src/components/General/UserSettingsProfileCard.tsx index aa9301e379..ac871941dd 100644 --- a/plugins/user-settings/src/components/General/UserSettingsProfileCard.tsx +++ b/plugins/user-settings/src/components/General/UserSettingsProfileCard.tsx @@ -20,13 +20,16 @@ import { UserSettingsSignInAvatar } from './UserSettingsSignInAvatar'; import { UserSettingsMenu } from './UserSettingsMenu'; import { useUserProfile } from '../useUserProfileInfo'; import { InfoCard } from '@backstage/core-components'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { userSettingsTranslationRef } from '../../translation'; /** @public */ export const UserSettingsProfileCard = () => { const { profile, displayName } = useUserProfile(); + const { t } = useTranslationRef(userSettingsTranslationRef); return ( - + diff --git a/plugins/user-settings/src/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/storybook/yarn.lock b/storybook/yarn.lock index 96cf1e99bb..d7e83584d1 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -1552,12 +1552,12 @@ __metadata: linkType: hard "@types/react@npm:^18.0.0": - version: 18.3.21 - resolution: "@types/react@npm:18.3.21" + version: 18.3.23 + resolution: "@types/react@npm:18.3.23" dependencies: "@types/prop-types": "npm:*" csstype: "npm:^3.0.2" - checksum: 10/89adc2fe391fd63b620db0aaa10b2be89fad338039415fd748e60c962e47bf2b0f1897cb5226f181eda963f37a1c8ac1bf0b3b29a0e35313bacd0355e5d6c736 + checksum: 10/4b965dffe34a1f8aac8e2d7e976f113373f38134f9e37239f7e75d7ac6b3c2e1333a8df21febf1fe7749640f8de5708f7668cdfc70bffebda1cc4d3346724fd5 languageName: node linkType: hard diff --git a/yarn.lock b/yarn.lock index 539b89f70c..25a18fed22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -324,25 +324,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 +352,7 @@ __metadata: peerDependencies: react: ">=18.0.0" react-dom: ">=18.0.0" - checksum: 10/590b9e8e4326a803149bd5c99662cd75acf4e469cb3c829588f2b8a2b2b85bf3d342a81daccf3f974d9fbdb89e30a6bfed2d870826abaf7366b1017389dbd5c5 + checksum: 10/7105385f8f806200638f10b799ff1a5d1838041d20d14c6e64b1e1a411933727b91cd3957c2057bc7946524be01c8ab7bb03946886534b15f4767c277da38445 languageName: node linkType: hard @@ -3636,6 +3636,7 @@ __metadata: yauzl: "npm:^3.0.0" yn: "npm:^4.0.0" zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.20.4" peerDependencies: "@google-cloud/cloud-sql-connector": ^1.4.0 peerDependenciesMeta: @@ -3721,22 +3722,22 @@ __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" +"@backstage/backend-plugin-api@npm:^1.0.0, @backstage/backend-plugin-api@npm:^1.1.1, @backstage/backend-plugin-api@npm:^1.3.1": + version: 1.3.1 + resolution: "@backstage/backend-plugin-api@npm:1.3.1" dependencies: "@backstage/cli-common": "npm:^0.1.15" "@backstage/config": "npm:^1.3.2" "@backstage/errors": "npm:^1.2.7" - "@backstage/plugin-auth-node": "npm:^0.6.2" - "@backstage/plugin-permission-common": "npm:^0.8.4" - "@backstage/plugin-permission-node": "npm:^0.9.1" + "@backstage/plugin-auth-node": "npm:^0.6.3" + "@backstage/plugin-permission-common": "npm:^0.9.0" + "@backstage/plugin-permission-node": "npm:^0.10.0" "@backstage/types": "npm:^1.2.1" "@types/express": "npm:^4.17.6" "@types/luxon": "npm:^3.0.0" knex: "npm:^3.0.0" luxon: "npm:^3.0.0" - checksum: 10/0c657159bf2d988a09fe066dba811fa1c9237162dffdbbded09a108d906ea580099343b86d1d5e675bf5f59ee09e292f23dd44c6a594c2f70ead8035f512b8b0 + checksum: 10/c78a0cd73c9e2ab3f611e62b1570b83fd496f501616caf00877c5191a7c431671f66f77d6e7fd2a35982915f8cb1b69a85cf7d26e3f236a28f709a6ae7af8eaf languageName: node linkType: hard @@ -3754,9 +3755,12 @@ __metadata: "@backstage/plugin-permission-node": "workspace:^" "@backstage/types": "workspace:^" "@types/express": "npm:^4.17.6" + "@types/json-schema": "npm:^7.0.6" "@types/luxon": "npm:^3.0.0" + json-schema: "npm:^0.4.0" knex: "npm:^3.0.0" luxon: "npm:^3.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -3772,6 +3776,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" @@ -3826,6 +3831,7 @@ __metadata: 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" @@ -3840,15 +3846,15 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-client@npm:^1.9.1": - version: 1.9.1 - resolution: "@backstage/catalog-client@npm:1.9.1" +"@backstage/catalog-client@npm:^1.10.0, @backstage/catalog-client@npm:^1.9.1": + version: 1.10.0 + resolution: "@backstage/catalog-client@npm:1.10.0" dependencies: - "@backstage/catalog-model": "npm:^1.7.3" + "@backstage/catalog-model": "npm:^1.7.4" "@backstage/errors": "npm:^1.2.7" cross-fetch: "npm:^4.0.0" uri-template: "npm:^2.0.0" - checksum: 10/4e29bb4b6addf4330e298e08500efda6d7e9a416102abdd65dfbedb5e139d8911449f6bb91a90b2d571ddca53cb908c77a4a894fd34689f740e779e66020e13f + checksum: 10/565512931ef340c272ebbffcac7c5f54348ed67a73be80cdecb8768c547a1da41c72b02c0830f3036a06edbc8c77b9720ee5f1f498b2308f799fd409bb4bbbe1 languageName: node linkType: hard @@ -3865,7 +3871,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-model@npm:^1.4.3, @backstage/catalog-model@npm:^1.7.3, @backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": +"@backstage/catalog-model@npm:^1.4.3, @backstage/catalog-model@npm:^1.7.3, @backstage/catalog-model@npm:^1.7.4, @backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": version: 0.0.0-use.local resolution: "@backstage/catalog-model@workspace:packages/catalog-model" dependencies: @@ -4074,7 +4080,6 @@ __metadata: optional: true bin: backstage-cli: bin/backstage-cli - backstage-cli-alpha: bin/backstage-cli-alpha languageName: unknown linkType: soft @@ -4095,30 +4100,7 @@ __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": +"@backstage/config-loader@npm:^1.9.1, @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" dependencies: @@ -4158,35 +4140,7 @@ __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": +"@backstage/core-app-api@npm:^1.17.0, @backstage/core-app-api@workspace:^, @backstage/core-app-api@workspace:packages/core-app-api": version: 0.0.0-use.local resolution: "@backstage/core-app-api@workspace:packages/core-app-api" dependencies: @@ -4230,13 +4184,13 @@ __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" +"@backstage/core-compat-api@npm:^0.4.2": + version: 0.4.2 + resolution: "@backstage/core-compat-api@npm:0.4.2" dependencies: - "@backstage/core-plugin-api": "npm:^1.10.6" - "@backstage/frontend-plugin-api": "npm:^0.10.1" - "@backstage/plugin-catalog-react": "npm:^1.17.0" + "@backstage/core-plugin-api": "npm:^1.10.7" + "@backstage/frontend-plugin-api": "npm:^0.10.2" + "@backstage/plugin-catalog-react": "npm:^1.18.0" "@backstage/version-bridge": "npm:^1.0.11" lodash: "npm:^4.17.21" peerDependencies: @@ -4247,7 +4201,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/4c91cc4f4a06d6420a4bed63e540e46a34a7a1ecb831fd503960c9e9ae8817f015a0b6d39d37ac4588233caf2a21a93c9acadbe15d11b59706275166567a4581 + checksum: 10/7e93c3ff7a9979ee8cb0ab582e66bd0febb5995c91cc0545473e9e86fc75c1ebbb0a24a901e120d7e4ecf122bbb31de1b4f8fc78c7da5f6d84daa9d6b9194035 languageName: node linkType: hard @@ -4391,14 +4345,14 @@ __metadata: languageName: node linkType: hard -"@backstage/core-components@npm:^0.17.1": - version: 0.17.1 - resolution: "@backstage/core-components@npm:0.17.1" +"@backstage/core-components@npm:^0.17.1, @backstage/core-components@npm:^0.17.2": + version: 0.17.2 + resolution: "@backstage/core-components@npm:0.17.2" dependencies: "@backstage/config": "npm:^1.3.2" - "@backstage/core-plugin-api": "npm:^1.10.6" + "@backstage/core-plugin-api": "npm:^1.10.7" "@backstage/errors": "npm:^1.2.7" - "@backstage/theme": "npm:^0.6.5" + "@backstage/theme": "npm:^0.6.6" "@backstage/version-bridge": "npm:^1.0.11" "@dagrejs/dagre": "npm:^1.1.4" "@date-io/core": "npm:^1.3.13" @@ -4441,7 +4395,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/12e8f0675c2dd6bb871d77ceb833d2078cac41278d4122139669ffaadea09df7b3c8ca53c166e2c664c275e381f9c96f8383a5a1faaeb956ab0f1e4ee72ec1b7 + checksum: 10/0b790f3448001e571e50a89c6bbcd08d0d08b7e098b262e09ad19e475ffdf12f4b0c31b0880e64a61a1096b5091d6767e06e32874a2dc6ebc98d79bc0b82107f languageName: node linkType: hard @@ -4523,28 +4477,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-plugin-api@npm:^1.10.0, @backstage/core-plugin-api@npm:^1.10.6, @backstage/core-plugin-api@npm:^1.8.2": - 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": +"@backstage/core-plugin-api@npm:^1.10.0, @backstage/core-plugin-api@npm:^1.10.6, @backstage/core-plugin-api@npm:^1.10.7, @backstage/core-plugin-api@npm:^1.8.2, @backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": version: 0.0.0-use.local resolution: "@backstage/core-plugin-api@workspace:packages/core-plugin-api" dependencies: @@ -4677,16 +4610,16 @@ __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" +"@backstage/frontend-app-api@npm:^0.11.2": + version: 0.11.2 + resolution: "@backstage/frontend-app-api@npm:0.11.2" dependencies: "@backstage/config": "npm:^1.3.2" - "@backstage/core-app-api": "npm:^1.16.1" - "@backstage/core-plugin-api": "npm:^1.10.6" + "@backstage/core-app-api": "npm:^1.17.0" + "@backstage/core-plugin-api": "npm:^1.10.7" "@backstage/errors": "npm:^1.2.7" - "@backstage/frontend-defaults": "npm:^0.2.1" - "@backstage/frontend-plugin-api": "npm:^0.10.1" + "@backstage/frontend-defaults": "npm:^0.2.2" + "@backstage/frontend-plugin-api": "npm:^0.10.2" "@backstage/types": "npm:^1.2.1" "@backstage/version-bridge": "npm:^1.0.11" lodash: "npm:^4.17.21" @@ -4699,7 +4632,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/fdf5b7dddeba9320e8ec54751396a3e81897605c26520af60eb61a76f17dd78b6b786f5cfa9f4bad3beef0d3408868bfe8331f6773b54034d11dc170a35e60e7 + checksum: 10/06cf128df60b0322c07c14853146da5096625d58a344555571a65ead221f03dc1f4e2fe7da364b2afec010be13a4d4a4574060a3f65c023bdcdc93d20d09c33d languageName: node linkType: hard @@ -4737,15 +4670,15 @@ __metadata: languageName: unknown linkType: soft -"@backstage/frontend-defaults@npm:^0.2.1": - version: 0.2.1 - resolution: "@backstage/frontend-defaults@npm:0.2.1" +"@backstage/frontend-defaults@npm:^0.2.2": + version: 0.2.2 + resolution: "@backstage/frontend-defaults@npm:0.2.2" dependencies: "@backstage/config": "npm:^1.3.2" "@backstage/errors": "npm:^1.2.7" - "@backstage/frontend-app-api": "npm:^0.11.1" - "@backstage/frontend-plugin-api": "npm:^0.10.1" - "@backstage/plugin-app": "npm:^0.1.8" + "@backstage/frontend-app-api": "npm:^0.11.2" + "@backstage/frontend-plugin-api": "npm:^0.10.2" + "@backstage/plugin-app": "npm:^0.1.9" "@react-hookz/web": "npm:^24.0.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 @@ -4755,7 +4688,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/3bd178ca78db5d467378272025d7a3fa7995a37063f39c2ec2c9a81b2bb6aa4890f888e99f1e5c4abb442614b978a490d8dbcaa09e54a94e47364caab618725c + checksum: 10/23e6156314cb80d9cc1128ee3ad2d3afee3e2a6e88c049853b19133fc95e75f4361e735f26903e44fa62b29d39df5381c53200799a8cc3b3fd0943682a5e51f5 languageName: node linkType: hard @@ -4820,12 +4753,12 @@ __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" +"@backstage/frontend-plugin-api@npm:^0.10.2": + version: 0.10.2 + resolution: "@backstage/frontend-plugin-api@npm:0.10.2" dependencies: - "@backstage/core-components": "npm:^0.17.1" - "@backstage/core-plugin-api": "npm:^1.10.6" + "@backstage/core-components": "npm:^0.17.2" + "@backstage/core-plugin-api": "npm:^1.10.7" "@backstage/types": "npm:^1.2.1" "@backstage/version-bridge": "npm:^1.0.11" "@material-ui/core": "npm:^4.12.4" @@ -4840,7 +4773,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/5a1b69d7a1b1db584f4f0c4934ac98dc678f53730c250959da47e20479032b02b1f02785bcb51989af899e8b1166599e23e03ce024469e5efb5b49f8c792e315 + checksum: 10/4f01cf2ae8352dd8b5f7ff66a97739c6f9f8b19b182ae7cb05db542d02fa0cdee805712c0d355068de3c7cd851404fe6e424c5b7a0ffbcec686c42bc78030510 languageName: node linkType: hard @@ -4878,15 +4811,15 @@ __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" +"@backstage/frontend-test-utils@npm:^0.3.2": + version: 0.3.2 + resolution: "@backstage/frontend-test-utils@npm:0.3.2" dependencies: "@backstage/config": "npm:^1.3.2" - "@backstage/frontend-app-api": "npm:^0.11.1" - "@backstage/frontend-plugin-api": "npm:^0.10.1" - "@backstage/plugin-app": "npm:^0.1.8" - "@backstage/test-utils": "npm:^1.7.7" + "@backstage/frontend-app-api": "npm:^0.11.2" + "@backstage/frontend-plugin-api": "npm:^0.10.2" + "@backstage/plugin-app": "npm:^0.1.9" + "@backstage/test-utils": "npm:^1.7.8" "@backstage/types": "npm:^1.2.1" "@backstage/version-bridge": "npm:^1.0.11" zod: "npm:^3.22.4" @@ -4899,7 +4832,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/b46c7e660ffbfecae9d0dd9fbc471c0c6324550d768f2749ff5726b4acd737aef4075290f1c2e8b73ce967b664997cc1f47f933254209d41d2fb080799c3dba9 + checksum: 10/3b33d0165e2bda8460e433649fec04d7594c7d987b7cdc59b46a955208266481d15df0f6d529a0bfbece7050d6510c0e320bf77fc81296579747e9a469d8e7d0 languageName: node linkType: hard @@ -4933,22 +4866,7 @@ __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": +"@backstage/integration-aws-node@npm:^0.1.12, @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" dependencies: @@ -4967,28 +4885,7 @@ __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": +"@backstage/integration-react@npm:^1.1.24, @backstage/integration-react@npm:^1.2.7, @backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": version: 0.0.0-use.local resolution: "@backstage/integration-react@workspace:packages/integration-react" dependencies: @@ -5019,25 +4916,7 @@ __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": +"@backstage/integration@npm:^1.15.0, @backstage/integration@npm:^1.9.0, @backstage/integration@workspace:^, @backstage/integration@workspace:packages/integration": version: 0.0.0-use.local resolution: "@backstage/integration@workspace:packages/integration" dependencies: @@ -5198,16 +5077,16 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-app@npm:^0.1.8": - version: 0.1.8 - resolution: "@backstage/plugin-app@npm:0.1.8" +"@backstage/plugin-app@npm:^0.1.9": + version: 0.1.9 + resolution: "@backstage/plugin-app@npm:0.1.9" dependencies: - "@backstage/core-components": "npm:^0.17.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/core-components": "npm:^0.17.2" + "@backstage/core-plugin-api": "npm:^1.10.7" + "@backstage/frontend-plugin-api": "npm:^0.10.2" + "@backstage/integration-react": "npm:^1.2.7" + "@backstage/plugin-permission-react": "npm:^0.4.34" + "@backstage/theme": "npm:^0.6.6" "@backstage/types": "npm:^1.2.1" "@material-ui/core": "npm:^4.9.13" "@material-ui/icons": "npm:^4.9.1" @@ -5221,7 +5100,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/3634ce0bb0ac0f86050c45a4d24f728975232dcd04106b48b1ffdaec4e2ce9ef1ad17220afd0351fb75d6e10bfaacd45289064397332b5c08dc1c046d21466cf + checksum: 10/9c357605a8427f8bf5099968c612f90015d34d757a9494ea22d487bec20593cd80a7a50326e46344fc877ed163a14f2675d33530c1d756411316b0d373096aab languageName: node linkType: hard @@ -5577,7 +5456,7 @@ __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.6" express: "npm:^4.18.2" passport: "npm:^0.7.0" supertest: "npm:^7.0.0" @@ -5712,13 +5591,13 @@ __metadata: 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" +"@backstage/plugin-auth-node@npm:^0.6.3": + version: 0.6.3 + resolution: "@backstage/plugin-auth-node@npm:0.6.3" dependencies: - "@backstage/backend-plugin-api": "npm:^1.3.0" - "@backstage/catalog-client": "npm:^1.9.1" - "@backstage/catalog-model": "npm:^1.7.3" + "@backstage/backend-plugin-api": "npm:^1.3.1" + "@backstage/catalog-client": "npm:^1.10.0" + "@backstage/catalog-model": "npm:^1.7.4" "@backstage/config": "npm:^1.3.2" "@backstage/errors": "npm:^1.2.7" "@backstage/types": "npm:^1.2.1" @@ -5731,7 +5610,7 @@ __metadata: zod: "npm:^3.22.4" zod-to-json-schema: "npm:^3.21.4" zod-validation-error: "npm:^3.4.0" - checksum: 10/831b07fa41df68855f9a107a78c70d34986ddc7e12eac70cd3cdecb42b1297a65f0e6061549b12653704966887a5cd0199c7d69588e48a461dd74559d890aaa7 + checksum: 10/7f7fd4c1072512c8db6902e7a3e75319f73e2e487da0d202f3fde446eb340a34e7b1c0ce92d16f4dc2a5525aecd1df78979cdcfc650a48a41b2441f221fc0579 languageName: node linkType: hard @@ -6268,18 +6147,7 @@ __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": +"@backstage/plugin-catalog-common@npm:^1.0.20, @backstage/plugin-catalog-common@npm:^1.1.4, @backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-common@workspace:plugins/catalog-common" dependencies: @@ -6405,22 +6273,22 @@ __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" +"@backstage/plugin-catalog-react@npm:^1.17.0, @backstage/plugin-catalog-react@npm:^1.18.0, @backstage/plugin-catalog-react@npm:^1.9.3": + version: 1.18.0 + resolution: "@backstage/plugin-catalog-react@npm:1.18.0" dependencies: - "@backstage/catalog-client": "npm:^1.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/catalog-client": "npm:^1.10.0" + "@backstage/catalog-model": "npm:^1.7.4" + "@backstage/core-compat-api": "npm:^0.4.2" + "@backstage/core-components": "npm:^0.17.2" + "@backstage/core-plugin-api": "npm:^1.10.7" "@backstage/errors": "npm:^1.2.7" - "@backstage/frontend-plugin-api": "npm:^0.10.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/frontend-plugin-api": "npm:^0.10.2" + "@backstage/frontend-test-utils": "npm:^0.3.2" + "@backstage/integration-react": "npm:^1.2.7" + "@backstage/plugin-catalog-common": "npm:^1.1.4" + "@backstage/plugin-permission-common": "npm:^0.9.0" + "@backstage/plugin-permission-react": "npm:^0.4.34" "@backstage/types": "npm:^1.2.1" "@backstage/version-bridge": "npm:^1.0.11" "@material-ui/core": "npm:^4.12.2" @@ -6442,7 +6310,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/025ed43087462b021c13eac7fc762ad7d52d8ebb593da0093130e6379a79ac1b428fa9024eb21ace9bdf1d43ecafcab39226dbbf2b44ba35a6c93a9ddb04f1da + checksum: 10/1dec452c7bb1bd3538839a3a99943f5a5b50e5f414e31c679461e4810e5ea0e623cd41de6f08922fe577f3855459903f999c0ee0635982e1dd6bffbb46eceb08 languageName: node linkType: hard @@ -6567,6 +6435,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:^" @@ -6832,6 +6702,22 @@ __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" + luxon: "npm:^3.0.0" + 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" @@ -6912,6 +6798,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" @@ -7137,6 +7024,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" @@ -7148,9 +7038,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 @@ -7186,6 +7073,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" @@ -7194,9 +7084,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 @@ -7267,7 +7154,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:^" @@ -7492,22 +7378,7 @@ __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": +"@backstage/plugin-permission-common@npm:^0.9.0, @backstage/plugin-permission-common@workspace:^, @backstage/plugin-permission-common@workspace:plugins/permission-common": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-common@workspace:plugins/permission-common" dependencies: @@ -7523,21 +7394,21 @@ __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" +"@backstage/plugin-permission-node@npm:^0.10.0": + version: 0.10.0 + resolution: "@backstage/plugin-permission-node@npm:0.10.0" dependencies: - "@backstage/backend-plugin-api": "npm:^1.3.0" + "@backstage/backend-plugin-api": "npm:^1.3.1" "@backstage/config": "npm:^1.3.2" "@backstage/errors": "npm:^1.2.7" - "@backstage/plugin-auth-node": "npm:^0.6.2" - "@backstage/plugin-permission-common": "npm:^0.8.4" + "@backstage/plugin-auth-node": "npm:^0.6.3" + "@backstage/plugin-permission-common": "npm:^0.9.0" "@types/express": "npm:^4.17.6" express: "npm:^4.17.1" express-promise-router: "npm:^4.1.0" zod: "npm:^3.22.4" zod-to-json-schema: "npm:^3.20.4" - checksum: 10/3292951e8cdcc4341ba53e67bcc580d77da14e1e3224b9323671704b7329a3a9e45905e3cc5afb8bff33462178ddf7c3a510392c048ce125f374910622e91bd4 + checksum: 10/ffd6efc8625c36c5afbde08cf31aeac56a64c3b05547c8b2ba77b6301044652d2c5c4145f8445694ec8b1155173100472f21df1a4ec80b95d454485feb35231b languageName: node linkType: hard @@ -7564,27 +7435,7 @@ __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": +"@backstage/plugin-permission-react@npm:^0.4.34, @backstage/plugin-permission-react@workspace:^, @backstage/plugin-permission-react@workspace:plugins/permission-react": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-react@workspace:plugins/permission-react" dependencies: @@ -7679,6 +7530,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 @@ -7697,6 +7549,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 @@ -7820,12 +7673,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:^" @@ -7837,6 +7690,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 @@ -7871,7 +7725,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 @@ -7940,7 +7793,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:^" @@ -8378,16 +8230,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" @@ -8760,6 +8602,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:^" @@ -8944,7 +8787,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" @@ -8967,6 +8810,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" @@ -8981,6 +8825,7 @@ __metadata: ts-morph: "npm:^24.0.0" typedoc: "npm:^0.28.0" yaml-diff-patch: "npm:^2.0.0" + zod: "npm:^3.22.4" peerDependencies: "@microsoft/api-extractor-model": "*" "@microsoft/tsdoc": "*" @@ -8999,36 +8844,7 @@ __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": +"@backstage/test-utils@npm:^1.7.8, @backstage/test-utils@workspace:^, @backstage/test-utils@workspace:packages/test-utils": version: 0.0.0-use.local resolution: "@backstage/test-utils@workspace:packages/test-utils" dependencies: @@ -9080,27 +8896,7 @@ __metadata: 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": +"@backstage/theme@npm:^0.6.0, @backstage/theme@npm:^0.6.6, @backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": version: 0.0.0-use.local resolution: "@backstage/theme@workspace:packages/theme" dependencies: @@ -9494,8 +9290,8 @@ __metadata: linkType: hard "@codemirror/language@npm:^6.0.0": - version: 6.11.0 - resolution: "@codemirror/language@npm:6.11.0" + version: 6.11.1 + resolution: "@codemirror/language@npm:6.11.1" dependencies: "@codemirror/state": "npm:^6.0.0" "@codemirror/view": "npm:^6.23.0" @@ -9503,7 +9299,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/024969113d61ccb5d497b75a8a9875d43e1bfc8466de2819bb5db23f01b200937366800a812e8d33eb5e34e3a5d2aa22d0ff8205f34a5e8aeb4cd1d221bcaa78 languageName: node linkType: hard @@ -9560,13 +9356,14 @@ __metadata: linkType: hard "@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.23.0": - version: 6.36.8 - resolution: "@codemirror/view@npm:6.36.8" + version: 6.37.2 + resolution: "@codemirror/view@npm:6.37.2" 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/cb2cbfb8a41ee1d6cfb17c2ea91b9763e692f85d523b55276cc46ab7fda3f6ba0bcc03e2194ad6cf6b413ec34d309ab408300c19b62d5d6d6f9e2071fb99d489 + checksum: 10/410ef68c5bd3395c556f5a1298afd6a37eecd88b825435dc19ce7143b0eb5c4114486b444e56fd156371fcc329ad9425635c0703322384ff582a75a4d61727a7 languageName: node linkType: hard @@ -9631,15 +9428,15 @@ __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.6": + version: 0.0.6 + resolution: "@davidzemon/passport-okta-oauth@npm:0.0.6" 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/d29a26459ff387d7507c031a6814e3581eb47e2dc9dda6848667362973455cd125e2a251b30ad6c093dfd6cd8ee7adf48301f395808b212008a6ee702a507354 languageName: node linkType: hard @@ -9666,13 +9463,20 @@ __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.3 + resolution: "@electric-sql/pglite@npm:0.3.3" + checksum: 10/fa2b8698c4edb4716b34efd2e8ef1847e5b0c8059afffb21ad5b7cc21dcdca9d86075ac4f1798f1e83a5b2cc1ee8902e1d322fe63d761e065089c7da494ea089 + languageName: node + linkType: hard + "@emotion/babel-plugin@npm:^11.13.5": version: 11.13.5 resolution: "@emotion/babel-plugin@npm:11.13.5" @@ -9863,9 +9667,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.5": + version: 0.25.5 + resolution: "@esbuild/aix-ppc64@npm:0.25.5" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard @@ -9877,9 +9681,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.5": + version: 0.25.5 + resolution: "@esbuild/android-arm64@npm:0.25.5" conditions: os=android & cpu=arm64 languageName: node linkType: hard @@ -9891,9 +9695,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.5": + version: 0.25.5 + resolution: "@esbuild/android-arm@npm:0.25.5" conditions: os=android & cpu=arm languageName: node linkType: hard @@ -9905,9 +9709,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.5": + version: 0.25.5 + resolution: "@esbuild/android-x64@npm:0.25.5" conditions: os=android & cpu=x64 languageName: node linkType: hard @@ -9919,9 +9723,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.5": + version: 0.25.5 + resolution: "@esbuild/darwin-arm64@npm:0.25.5" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -9933,9 +9737,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.5": + version: 0.25.5 + resolution: "@esbuild/darwin-x64@npm:0.25.5" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -9947,9 +9751,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.5": + version: 0.25.5 + resolution: "@esbuild/freebsd-arm64@npm:0.25.5" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard @@ -9961,9 +9765,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.5": + version: 0.25.5 + resolution: "@esbuild/freebsd-x64@npm:0.25.5" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard @@ -9975,9 +9779,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.5": + version: 0.25.5 + resolution: "@esbuild/linux-arm64@npm:0.25.5" conditions: os=linux & cpu=arm64 languageName: node linkType: hard @@ -9989,9 +9793,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.5": + version: 0.25.5 + resolution: "@esbuild/linux-arm@npm:0.25.5" conditions: os=linux & cpu=arm languageName: node linkType: hard @@ -10003,9 +9807,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.5": + version: 0.25.5 + resolution: "@esbuild/linux-ia32@npm:0.25.5" conditions: os=linux & cpu=ia32 languageName: node linkType: hard @@ -10017,9 +9821,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.5": + version: 0.25.5 + resolution: "@esbuild/linux-loong64@npm:0.25.5" conditions: os=linux & cpu=loong64 languageName: node linkType: hard @@ -10031,9 +9835,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.5": + version: 0.25.5 + resolution: "@esbuild/linux-mips64el@npm:0.25.5" conditions: os=linux & cpu=mips64el languageName: node linkType: hard @@ -10045,9 +9849,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.5": + version: 0.25.5 + resolution: "@esbuild/linux-ppc64@npm:0.25.5" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard @@ -10059,9 +9863,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.5": + version: 0.25.5 + resolution: "@esbuild/linux-riscv64@npm:0.25.5" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard @@ -10073,9 +9877,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.5": + version: 0.25.5 + resolution: "@esbuild/linux-s390x@npm:0.25.5" conditions: os=linux & cpu=s390x languageName: node linkType: hard @@ -10087,16 +9891,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.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.4": - version: 0.25.4 - resolution: "@esbuild/netbsd-arm64@npm:0.25.4" +"@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 @@ -10108,16 +9912,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.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.4": - version: 0.25.4 - resolution: "@esbuild/openbsd-arm64@npm:0.25.4" +"@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 @@ -10129,9 +9933,9 @@ __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.5": + version: 0.25.5 + resolution: "@esbuild/openbsd-x64@npm:0.25.5" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard @@ -10143,9 +9947,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.5": + version: 0.25.5 + resolution: "@esbuild/sunos-x64@npm:0.25.5" conditions: os=sunos & cpu=x64 languageName: node linkType: hard @@ -10157,9 +9961,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.5": + version: 0.25.5 + resolution: "@esbuild/win32-arm64@npm:0.25.5" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -10171,9 +9975,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.5": + version: 0.25.5 + resolution: "@esbuild/win32-ia32@npm:0.25.5" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -10185,9 +9989,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.5": + version: 0.25.5 + resolution: "@esbuild/win32-x64@npm:0.25.5" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -10445,14 +10249,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.1 + resolution: "@google-cloud/cloud-sql-connector@npm:1.8.1" dependencies: - "@googleapis/sqladmin": "npm:^28.0.0" + "@googleapis/sqladmin": "npm:^29.0.0" gaxios: "npm:^6.1.1" google-auth-library: "npm:^9.2.0" p-throttle: "npm:^7.0.0" - checksum: 10/a2cd85beb822a37e1eaabd556e2f540f89c41a1614e520b8c120b8107d7edb3c335dcbf62fc34dd8973ae091a2a06882780ae6510c45f2b702a59df4953ba427 + checksum: 10/e13e2e47993d74c770c73318a73047b017266f6f8102e26ebcf90c484fd78ba6141b0fc60f4eced55d438150655b8514423c19e3f30da73a1aaf1c77baadd0ab languageName: node linkType: hard @@ -10466,15 +10270,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.1 + resolution: "@google-cloud/firestore@npm:7.11.1" 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/c793718c5ecc6c4e2f7affabaeaab42af3d382c40c6b748f17bd3724232583073a4d7236f448b24c6b1a9e8bf5d485fb62d1ffb10588b0fe54a0d105fc2b3f40 languageName: node linkType: hard @@ -10554,12 +10358,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:^29.0.0": + version: 29.0.0 + resolution: "@googleapis/sqladmin@npm:29.0.0" dependencies: googleapis-common: "npm:^7.0.0" - checksum: 10/ee4074b63d45276e0513a75b461bbf3d39abcb894e5eb660f0f28a9d0665c6638079958f04cb171767c69bcbb324ecdd2033199e6ecff61bf10d6a09adecdd16 + checksum: 10/3bf9eea2e6333d394da88404172aeed5ee5fca5fe2ebbba84d427990aab0083281a4cb948c023817ae6dbb618a1e43a296176c7703badd66a60bde695d7c5b3f languageName: node linkType: hard @@ -11178,40 +10982,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 + checksum: 10/38b54817cf125ba88d1136a6bca4fb57c46672d26d21490f838efe928049546800df6d9c8048411696455fc8caacb8ac23c2de2a1b61f2258b1302c1c97cc128 languageName: node linkType: hard @@ -11229,6 +11033,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" @@ -11690,23 +11510,25 @@ __metadata: linkType: hard "@keyv/memcache@npm:^1.3.5": - version: 1.3.7 - resolution: "@keyv/memcache@npm:1.3.7" + version: 1.4.1 + resolution: "@keyv/memcache@npm:1.4.1" dependencies: json-buffer: "npm:^3.0.1" - memjs: "npm:^1.3.1" - checksum: 10/61c836c1b0c264c35281a38dbc380a102b2899eab46226a0e31614d3d7c4708ecdb06d8858f711893212ae5b357a79213c7a7b7a81b6196e56dd889dd1ca498b + memjs: "npm:^1.3.2" + checksum: 10/a3db21549ec445925c28419b19210984875aa03d1ce513792064336c7785c0e6d3ac015e71ca328a2e83f4ed409475f057a37d86c7fb2a11832740b024bcc3ff 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 + peerDependencies: + keyv: ^5.3.4 + checksum: 10/8328bfa4a348d26c1aa1a563561a50114f75288af5fe18e8dc2ce6ff2f7f6aa63c27f8293c5e8b35bc0dcc9740159e56d8ff1b4015fc6a56e18d62850a8a9b83 languageName: node linkType: hard @@ -11720,31 +11542,32 @@ __metadata: linkType: hard "@keyv/redis@npm:^4.0.1": - version: 4.1.0 - resolution: "@keyv/redis@npm:4.1.0" + version: 4.4.0 + resolution: "@keyv/redis@npm:4.4.0" 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.3 + checksum: 10/61ad0a026f1ee8bbd0f09bb1b3c731619b37dc4bba0d5f2111cb5249e643e6a776816786090c53d5ac1fa62a43e1b43d406dae06cfb119abcb47b6ac837eebb0 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.4 + resolution: "@keyv/valkey@npm:1.0.4" dependencies: iovalkey: "npm:^0.3.1" - checksum: 10/ff6ba62e4d19c426e45a1437fe215ed2baddc58e811d97507dd75ead0058c3105d679c8c7c4241ddf732abe56320357c2e568c014620e50d7c0eaef1f2528b88 + checksum: 10/93362c51c14bb8e515a9af8df4a22e028fcec5a09931eb68f854698676cb16cecad691330c75c1dca8b6a986a4763cac9f1b2fd4e5b8216f06648d9379f55cc6 languageName: node linkType: hard @@ -12322,10 +12145,10 @@ __metadata: languageName: node linkType: hard -"@module-federation/error-codes@npm:0.13.1": - version: 0.13.1 - resolution: "@module-federation/error-codes@npm:0.13.1" - checksum: 10/fa9bbc0936ad9d037116c05f741bfb0ff741c3e2eaa682fa0132f4825d66ef0c63da0c20ec931ca16c8e7b660c1fdc60101a24ccab8951f19e07fba642aa1c6a +"@module-federation/error-codes@npm:0.14.3": + version: 0.14.3 + resolution: "@module-federation/error-codes@npm:0.14.3" + checksum: 10/3b6c25940d966648c2531b80edabafd36eddccdd8f68ba6702686a1e4fd2dbaa4a34ef6fb22a4d8a615b907e907e233dd0fd312c49a7369a49669bc1ab414a19 languageName: node linkType: hard @@ -12393,13 +12216,13 @@ __metadata: languageName: node linkType: hard -"@module-federation/runtime-core@npm:0.13.1": - version: 0.13.1 - resolution: "@module-federation/runtime-core@npm:0.13.1" +"@module-federation/runtime-core@npm:0.14.3": + version: 0.14.3 + resolution: "@module-federation/runtime-core@npm:0.14.3" dependencies: - "@module-federation/error-codes": "npm:0.13.1" - "@module-federation/sdk": "npm:0.13.1" - checksum: 10/81b52eb7866f623115922af325bcc1dba6c85f95216dfa2cbf2f777b37ec11ba856846892a4a5ab446105a18837c495b0a05cb5d574f7d2e50f340d1e57413f4 + "@module-federation/error-codes": "npm:0.14.3" + "@module-federation/sdk": "npm:0.14.3" + checksum: 10/7e974a8c051334929e28231fe1552489258ab1b545f0f5ad59644b0288765642f3e908b48c3cc59bc74a326f4f92325788b1a898f89d8f795f8036aa812b2bb6 languageName: node linkType: hard @@ -12413,13 +12236,13 @@ __metadata: languageName: node linkType: hard -"@module-federation/runtime-tools@npm:0.13.1": - version: 0.13.1 - resolution: "@module-federation/runtime-tools@npm:0.13.1" +"@module-federation/runtime-tools@npm:0.14.3": + version: 0.14.3 + resolution: "@module-federation/runtime-tools@npm:0.14.3" dependencies: - "@module-federation/runtime": "npm:0.13.1" - "@module-federation/webpack-bundler-runtime": "npm:0.13.1" - checksum: 10/821f32979ef2235cd043b3ef561fd63f7554211bd86d3af7e17a57dd5c709c6ee2ae57935222318694732115d7771c122cf51fb9f71f879269d452bc5ab0a8d2 + "@module-federation/runtime": "npm:0.14.3" + "@module-federation/webpack-bundler-runtime": "npm:0.14.3" + checksum: 10/09bc9d482ead7d38d5f9f6e1d45c85f257833302315447b2a17c26dc44046f8aa0563ffdb2f5addbddbf5f8f972c13dc8e80932a8a34780738232332bbdf5ce9 languageName: node linkType: hard @@ -12433,14 +12256,14 @@ __metadata: languageName: node linkType: hard -"@module-federation/runtime@npm:0.13.1": - version: 0.13.1 - resolution: "@module-federation/runtime@npm:0.13.1" +"@module-federation/runtime@npm:0.14.3": + version: 0.14.3 + resolution: "@module-federation/runtime@npm:0.14.3" dependencies: - "@module-federation/error-codes": "npm:0.13.1" - "@module-federation/runtime-core": "npm:0.13.1" - "@module-federation/sdk": "npm:0.13.1" - checksum: 10/2536ea17d0704bf4d093fe5791720e61fa3f4f4e83b6927b33f1d2ee132bf0cc9232e960cffefe32a6c79f3aedd864738b1be79b3790ad0c9dbd924f15ef2841 + "@module-federation/error-codes": "npm:0.14.3" + "@module-federation/runtime-core": "npm:0.14.3" + "@module-federation/sdk": "npm:0.14.3" + checksum: 10/b7009953998f2624f5ece3da4174d8427d30b2af3b108154f0fa2365aa6c08d167e23b9771508f2c6038a397f21e0e26b93dd09f3d4b7b90eac6b0ee7f1b9ee2 languageName: node linkType: hard @@ -12455,10 +12278,10 @@ __metadata: languageName: node linkType: hard -"@module-federation/sdk@npm:0.13.1": - version: 0.13.1 - resolution: "@module-federation/sdk@npm:0.13.1" - checksum: 10/7cfc98a6ee09aa338a9d76df146d064b5f4c6163b985daf3aa641e3f0e52d7cc40116b449cd1265b1333a66eeec31ce8bb40e1944f3ac62797d87323ce0e91ec +"@module-federation/sdk@npm:0.14.3": + version: 0.14.3 + resolution: "@module-federation/sdk@npm:0.14.3" + checksum: 10/f359fa15ecebe6e1e80e8fbd4d4969ae2d2d92f0d3e338443c45fba4772327368e086b108adcec585763d974e7462227a9e51b0531826292e2249f2612d0f822 languageName: node linkType: hard @@ -12480,13 +12303,13 @@ __metadata: languageName: node linkType: hard -"@module-federation/webpack-bundler-runtime@npm:0.13.1": - version: 0.13.1 - resolution: "@module-federation/webpack-bundler-runtime@npm:0.13.1" +"@module-federation/webpack-bundler-runtime@npm:0.14.3": + version: 0.14.3 + resolution: "@module-federation/webpack-bundler-runtime@npm:0.14.3" dependencies: - "@module-federation/runtime": "npm:0.13.1" - "@module-federation/sdk": "npm:0.13.1" - checksum: 10/bebccaa4411c00355bb399e2dddb946586967642b90da4d4436554093fecb614fdff6c65dc73cef944c7344f169d2c0ecc22ed0b5b899e21d4535507806145e0 + "@module-federation/runtime": "npm:0.14.3" + "@module-federation/sdk": "npm:0.14.3" + checksum: 10/016d1c51e82e230b2d33f67aba7c9ff2cd9f939306a48dfa3aeb0f3939dd82f797e06b478c009459d7516fadbf43024cd0fc13618555a64c470192d9b87ab926 languageName: node linkType: hard @@ -12781,27 +12604,29 @@ __metadata: languageName: node linkType: hard -"@nestjs/axios@npm:3.1.1": - version: 3.1.1 - resolution: "@nestjs/axios@npm:3.1.1" +"@nestjs/axios@npm:4.0.0": + version: 4.0.0 + resolution: "@nestjs/axios@npm:4.0.0" peerDependencies: - "@nestjs/common": ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + "@nestjs/common": ^10.0.0 || ^11.0.0 axios: ^1.3.1 - rxjs: ^6.0.0 || ^7.0.0 - checksum: 10/2022e63016577f9448837591ec5c700c724bf48763ec3006f41acd3edba4e18d5508bcf5f513dfdb8c0f25eb8c6c84aa6378ed6e3ed6abb4aab3f8b8e851db96 + rxjs: ^7.0.0 + checksum: 10/9a61ac8a2fdbf304961696148945ba9e19c0ed73256767b0a643bbafe77106b2f738cb2f35f2fc63bff09a8abcd18365d2f8c772484e2fdd70b455bceb7b5822 languageName: node linkType: hard -"@nestjs/common@npm:10.4.6": - version: 10.4.6 - resolution: "@nestjs/common@npm:10.4.6" +"@nestjs/common@npm:11.1.1": + version: 11.1.1 + resolution: "@nestjs/common@npm:11.1.1" dependencies: + file-type: "npm:20.5.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: @@ -12809,25 +12634,25 @@ __metadata: optional: true class-validator: optional: true - checksum: 10/bf1887715a8612e207c621a00761c0caed5c32f3fce9f6abd7de6a9c6f0217f825696f4dde218952017967493e47bd6fcc5b85d6f5d7fe82e31be9811179d2f5 + checksum: 10/b58b951984df667b794a6c9cf65dda8ee43fe0c4e552183e045b126c82d99b50cddb1e84ea03b8dd7b04fca512acb107222ffad6b19dba359323f9b813032161 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.1": + version: 11.1.1 + resolution: "@nestjs/core@npm:11.1.1" 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: @@ -12837,7 +12662,7 @@ __metadata: optional: true "@nestjs/websockets": optional: true - checksum: 10/09dd2b2a44a4c3cb546db78027ec76fbd6b12510b3013217d012b670abcbf6f7f021a91b0a59fe65eb5bc0cd53634edb9daebb61341d1d10e44476087759d21a + checksum: 10/88f8a3c52a98c059e6d93c5faf9194daee4667b1a855f88233582acf51e3be9aedb3e3f7f992da46e0e45df31ad4e045e0296e6e35ea14e2245d4069176711c0 languageName: node linkType: hard @@ -13124,6 +12949,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" @@ -13891,30 +13727,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.2 + resolution: "@openapitools/openapi-generator-cli@npm:2.20.2" 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.1" + "@nestjs/core": "npm:11.1.1" "@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" + fs-extra: "npm:11.3.0" glob: "npm:9.3.5" 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/29807b52555e7307207eff6e0c4c1a25b970252915e13d847a6c275d1d638af0732761e51816f5bb5a91830ebabc044b3924a106db8cffa19dda01517a77be17 languageName: node linkType: hard @@ -16001,119 +15837,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 @@ -16156,123 +16802,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 @@ -16583,82 +17882,82 @@ __metadata: languageName: node linkType: hard -"@rspack/binding-darwin-arm64@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-darwin-arm64@npm:1.3.10" +"@rspack/binding-darwin-arm64@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-darwin-arm64@npm:1.3.15" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rspack/binding-darwin-x64@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-darwin-x64@npm:1.3.10" +"@rspack/binding-darwin-x64@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-darwin-x64@npm:1.3.15" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rspack/binding-linux-arm64-gnu@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-linux-arm64-gnu@npm:1.3.10" +"@rspack/binding-linux-arm64-gnu@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-linux-arm64-gnu@npm:1.3.15" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rspack/binding-linux-arm64-musl@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-linux-arm64-musl@npm:1.3.10" +"@rspack/binding-linux-arm64-musl@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-linux-arm64-musl@npm:1.3.15" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rspack/binding-linux-x64-gnu@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-linux-x64-gnu@npm:1.3.10" +"@rspack/binding-linux-x64-gnu@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-linux-x64-gnu@npm:1.3.15" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rspack/binding-linux-x64-musl@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-linux-x64-musl@npm:1.3.10" +"@rspack/binding-linux-x64-musl@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-linux-x64-musl@npm:1.3.15" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rspack/binding-win32-arm64-msvc@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-win32-arm64-msvc@npm:1.3.10" +"@rspack/binding-win32-arm64-msvc@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-win32-arm64-msvc@npm:1.3.15" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rspack/binding-win32-ia32-msvc@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-win32-ia32-msvc@npm:1.3.10" +"@rspack/binding-win32-ia32-msvc@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-win32-ia32-msvc@npm:1.3.15" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rspack/binding-win32-x64-msvc@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-win32-x64-msvc@npm:1.3.10" +"@rspack/binding-win32-x64-msvc@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-win32-x64-msvc@npm:1.3.15" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rspack/binding@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding@npm:1.3.10" +"@rspack/binding@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding@npm:1.3.15" dependencies: - "@rspack/binding-darwin-arm64": "npm:1.3.10" - "@rspack/binding-darwin-x64": "npm:1.3.10" - "@rspack/binding-linux-arm64-gnu": "npm:1.3.10" - "@rspack/binding-linux-arm64-musl": "npm:1.3.10" - "@rspack/binding-linux-x64-gnu": "npm:1.3.10" - "@rspack/binding-linux-x64-musl": "npm:1.3.10" - "@rspack/binding-win32-arm64-msvc": "npm:1.3.10" - "@rspack/binding-win32-ia32-msvc": "npm:1.3.10" - "@rspack/binding-win32-x64-msvc": "npm:1.3.10" + "@rspack/binding-darwin-arm64": "npm:1.3.15" + "@rspack/binding-darwin-x64": "npm:1.3.15" + "@rspack/binding-linux-arm64-gnu": "npm:1.3.15" + "@rspack/binding-linux-arm64-musl": "npm:1.3.15" + "@rspack/binding-linux-x64-gnu": "npm:1.3.15" + "@rspack/binding-linux-x64-musl": "npm:1.3.15" + "@rspack/binding-win32-arm64-msvc": "npm:1.3.15" + "@rspack/binding-win32-ia32-msvc": "npm:1.3.15" + "@rspack/binding-win32-x64-msvc": "npm:1.3.15" dependenciesMeta: "@rspack/binding-darwin-arm64": optional: true @@ -16678,42 +17977,38 @@ __metadata: optional: true "@rspack/binding-win32-x64-msvc": optional: true - checksum: 10/10328e405c6708f7d1eea7e8f83d7f4453bcc3e4ca77b99eb29819d9f66c51e85b8cf46daab797bea59fdafc266dab6091deb9b5e2ec3007482da11ab10dc62c + checksum: 10/d4918ee41eecbc6d27739d21b674fe64ea7d45433e0f038618475a7cfd91a68d470d6e05d68e109ae917b88c541059c368b7734b2dca3791d7be001a5804030e languageName: node linkType: hard "@rspack/core@npm:^1.3.9": - version: 1.3.10 - resolution: "@rspack/core@npm:1.3.10" + version: 1.3.15 + resolution: "@rspack/core@npm:1.3.15" dependencies: - "@module-federation/runtime-tools": "npm:0.13.1" - "@rspack/binding": "npm:1.3.10" + "@module-federation/runtime-tools": "npm:0.14.3" + "@rspack/binding": "npm:1.3.15" "@rspack/lite-tapable": "npm:1.0.1" - caniuse-lite: "npm:^1.0.30001717" peerDependencies: "@swc/helpers": ">=0.5.1" peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/5471ced4f461936c723199006182ce9fc54a5840aa7ba46f56f137d46b1839b994a9f6e4539e1376eaaf2956cbbbe5a7846d06ab61246e38711a805f7dd56df4 + checksum: 10/9708d4b5112f9a1c23f691e544e9b690cdc7275145772932e6ea0683b6c71d3f304c0f21513b23a01f00cebe8607adc54c23e3ed4ac9dd566e8d9d02b35f5f4e languageName: node linkType: hard "@rspack/dev-server@npm:^1.1.1": - version: 1.1.1 - resolution: "@rspack/dev-server@npm:1.1.1" + version: 1.1.3 + resolution: "@rspack/dev-server@npm:1.1.3" 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/31cef80a602acf9468a94c31b1f09239cae9c47cf0ed25c6bcddd057bbaff5a220a1dead068a255d35d0addfea21d81a574069d63ea258c2807d84a02d4fe966 languageName: node linkType: hard @@ -19784,6 +21079,17 @@ __metadata: languageName: node linkType: hard +"@tokenizer/inflate@npm:^0.2.6": + 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" @@ -20145,18 +21451,18 @@ __metadata: 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 @@ -20189,11 +21495,11 @@ __metadata: linkType: hard "@types/cors@npm:^2.8.6": - version: 2.8.18 - resolution: "@types/cors@npm:2.8.18" + version: 2.8.19 + resolution: "@types/cors@npm:2.8.19" dependencies: "@types/node": "npm:*" - checksum: 10/6e49b741345e67834cd19d766228509e4b37d6d5c272355bb059502b4787f5adf58776d9114ac5f0f407966e0347ae8d1f995d7ea41e6a24f716d36b3010401b + checksum: 10/9545cc532c9218754443f48a0c98c1a9ba4af1fe54a3425c95de75ff3158147bb39e666cb7c6bf98cc56a9c6dc7b4ce5b2cbdae6b55d5942e50c81b76ed6b825 languageName: node linkType: hard @@ -20345,9 +21651,9 @@ __metadata: 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 + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10/25a4c16a6752538ffde2826c2cc0c6491d90e69cd6187bef4a006dd2c3c45469f049e643d7e516c515f21484dc3d48fd5c870be158a5beb72f5baf3dc43e4099 languageName: node linkType: hard @@ -20397,11 +21703,11 @@ __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 @@ -20519,9 +21825,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 @@ -20747,9 +22053,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.17 + resolution: "@types/lodash@npm:4.17.17" + checksum: 10/496459a3cb1a0733bb60532de3899ad6297717af0b9b26ad6821154b2005fec86f29ccd47a2e6f4da4a8c7c818bb8ae73901144e8057ea86b7b02a3d7bb9d13f languageName: node linkType: hard @@ -21133,9 +22439,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 @@ -21274,12 +22580,12 @@ __metadata: linkType: hard "@types/react@npm:^18.0.0": - version: 18.3.21 - resolution: "@types/react@npm:18.3.21" + version: 18.3.23 + resolution: "@types/react@npm:18.3.23" dependencies: "@types/prop-types": "npm:*" csstype: "npm:^3.0.2" - checksum: 10/89adc2fe391fd63b620db0aaa10b2be89fad338039415fd748e60c962e47bf2b0f1897cb5226f181eda963f37a1c8ac1bf0b3b29a0e35313bacd0355e5d6c736 + checksum: 10/4b965dffe34a1f8aac8e2d7e976f113373f38134f9e37239f7e75d7ac6b3c2e1333a8df21febf1fe7749640f8de5708f7668cdfc70bffebda1cc4d3346724fd5 languageName: node linkType: hard @@ -22073,24 +23379,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" @@ -22108,15 +23414,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" @@ -22133,7 +23439,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 @@ -22163,8 +23469,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" @@ -22174,10 +23480,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" @@ -22234,34 +23540,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" @@ -22272,7 +23578,7 @@ __metadata: whatwg-mimetype: "npm:^3.0.0" bin: standard-rulesets: build/index.js - checksum: 10/e1e7d59f5cdd953486583cc9cb9a4a06e3821bb2ff21506a27a261c1c3173ed91f35409297a4f5fed102bd084486c36b60358dbacf3a3c45f6cb59dbf143070e + checksum: 10/63f60ac25fb8cccd0ea89097336cf5bc7ad59022ad6ef49fd382e7555f5ea9d88ca144991315eb07646aa309f5f38f3d056c4b7c869ab6b5f66fa9e93d96f4ef languageName: node linkType: hard @@ -22590,6 +23896,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" @@ -23472,7 +24803,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 @@ -24417,18 +25748,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, 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": version: 1.9.0 resolution: "axios@npm:1.9.0" dependencies: @@ -25002,12 +26322,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 @@ -25575,7 +26895,7 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001669, caniuse-lite@npm:^1.0.30001717": +"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 @@ -26612,6 +27932,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" @@ -26957,10 +28284,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 @@ -27055,7 +28382,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: @@ -28141,8 +29468,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" @@ -28151,7 +29478,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 @@ -29000,34 +30327,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, esbuild@npm:^0.25.0": - version: 0.25.4 - resolution: "esbuild@npm:0.25.4" + version: 0.25.5 + resolution: "esbuild@npm:0.25.5" 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.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 @@ -29081,7 +30408,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10/227ffe9b31f0b184a0b0a0210bb9d32b2b115b8c5c9b09f08db2c3928cb470fc55a22dbba3c2894365d3abcc62c2089b85638be96a20691d1234d31990ea01b2 + checksum: 10/0fa4c3b42c6ddf1a008e75a4bb3dcab08ce22ac0b31dd59dc01f7fe8e21380bfaec07a2fe3730a7cf430da5a30142d016714b358666325a4733547afa42be405 languageName: node linkType: hard @@ -30453,6 +31780,13 @@ __metadata: 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" @@ -30471,6 +31805,18 @@ __metadata: languageName: node linkType: hard +"file-type@npm:20.5.0": + version: 20.5.0 + resolution: "file-type@npm:20.5.0" + dependencies: + "@tokenizer/inflate": "npm:^0.2.6" + strtok3: "npm:^10.2.0" + token-types: "npm:^6.0.0" + uint8array-extras: "npm:^1.4.0" + checksum: 10/1cc1ccd7cf76086e10b65cba88c708e0653676fbae900107deeb91c46de011acd1492200bf47e75cddf395de27dbe8584ca042f4cfa4a1efdf933644b7143f1d + languageName: node + linkType: hard + "file-type@npm:3.9.0": version: 3.9.0 resolution: "file-type@npm:3.9.0" @@ -30765,13 +32111,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 + checksum: 10/427b33f997a98073c0424e5c07169264a62cda806d8d2ded159b5b903fdfc8f0a1457e06b5fc35506497acb3f1e353f025edee796300209ac6231e80edece835 languageName: node linkType: hard @@ -31002,14 +32348,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, fs-extra@npm:^11.0.0, fs-extra@npm:^11.1.0, fs-extra@npm:^11.2.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 @@ -31025,14 +32371,14 @@ __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 @@ -31517,18 +32863,18 @@ __metadata: linkType: hard "glob@npm:^11.0.1": - version: 11.0.2 - resolution: "glob@npm:11.0.2" + version: 11.0.3 + resolution: "glob@npm:11.0.3" dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^4.0.1" - minimatch: "npm:^10.0.0" + 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/53501530240150fdceb9ace47ab856acd1e0d598f8101b0760b665fc11dae2160d366563b89232ae4f5df7ddba8f7c92294719268fe932bd3a32d16cc58c3d02 + checksum: 10/2ae536c1360c0266b523b2bfa6aadc10144a8b7e08869b088e37ac3c27cd30774f82e4bfb291cde796776e878f9e13200c7ff44010eb7054e00f46f649397893 languageName: node linkType: hard @@ -32531,7 +33877,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: @@ -32615,13 +33961,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 @@ -34154,12 +35500,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 @@ -35492,6 +36838,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" @@ -35502,11 +36855,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 @@ -36039,6 +37392,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" @@ -37217,7 +38577,7 @@ __metadata: 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 @@ -37678,7 +39038,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.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -37823,12 +39183,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 @@ -38310,8 +39670,8 @@ __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.8.2 + resolution: "msw@npm:2.8.2" dependencies: "@bundled-es-modules/cookie": "npm:^2.0.1" "@bundled-es-modules/statuses": "npm:^1.0.1" @@ -38338,7 +39698,7 @@ __metadata: optional: true bin: msw: cli/index.js - checksum: 10/23f3907b487102b395e2405ab8ae69c8cc74413485805a1039e1a3ab0b54b56947aa77b5ef5a15befad02a69aaa21a204ecfafd2e81d4fb1fd284cb99dfe2c0b + checksum: 10/7579a8dccb8cc8eb0f13d0bf3a232a3d50a478511d95bc2a4b70778e78ffa28fd0949a855fbceb6ec381bbf76f6331a6d36fcb4a3197ff5bf55d0f14a7f3b35c languageName: node linkType: hard @@ -39916,19 +41276,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 @@ -40400,6 +41760,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": + 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" @@ -40407,13 +41774,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" @@ -40469,6 +41829,13 @@ __metadata: languageName: node linkType: hard +"peek-readable@npm:^7.0.0": + version: 7.0.0 + resolution: "peek-readable@npm:7.0.0" + checksum: 10/e14d533c6a43f3991bb98b644101a55631e307f7fe70e26bd0fd0341bde1e6cdd60e205a66ec17c226b32cb8a4ad5c4e76c1a6173d37201f0bf466cbb617485c + languageName: node + linkType: hard + "pend@npm:~1.2.0": version: 1.2.0 resolution: "pend@npm:1.2.0" @@ -41765,19 +43132,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 @@ -42127,6 +43494,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" @@ -42722,6 +44181,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" @@ -43094,20 +44589,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" @@ -43133,10 +44614,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 @@ -44016,12 +45497,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 @@ -44034,15 +45515,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" @@ -44939,7 +46411,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: @@ -45827,6 +47299,16 @@ __metadata: languageName: node linkType: hard +"strtok3@npm:^10.2.0": + version: 10.2.2 + resolution: "strtok3@npm:10.2.2" + dependencies: + "@tokenizer/token": "npm:^0.3.0" + peek-readable: "npm:^7.0.0" + checksum: 10/964e60d76576232803fc75572adfb8778b55a7ef021ecfa3a507dcdb426f1e2c42580e545ab18f882da8f2e9bfca05fb0659f56f71b18723e95c31c0512301fc + languageName: node + linkType: hard + "strtok3@npm:^6.2.4": version: 6.2.4 resolution: "strtok3@npm:6.2.4" @@ -46703,6 +48185,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" @@ -47009,13 +48501,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" @@ -47442,6 +48927,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" @@ -47497,18 +48989,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 @@ -48475,53 +49967,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" @@ -48539,7 +49987,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" @@ -48560,7 +50008,7 @@ __metadata: optional: true bin: webpack-dev-server: bin/webpack-dev-server.js - checksum: 10/424edfe22b7bbe2301a38b8b519dfeb7643e0ca643be01af3fa48ec18512955c1952246741d7577bdb911ee09dcd6c521ade7d65e0059448ee69ab02bfac4624 + checksum: 10/59517409cd38c01a875a03b9658f3d20d492b5b8bead9ded4a0f3d33e6857daf2d352fe89f0181dcaea6d0fbe84b0494cb4750a87120fe81cdbb3c32b499451c languageName: node linkType: hard @@ -49077,31 +50525,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" @@ -49389,9 +50812,9 @@ __metadata: linkType: hard "zod@npm:^3.22.4": - version: 3.23.8 - resolution: "zod@npm:3.23.8" - checksum: 10/846fd73e1af0def79c19d510ea9e4a795544a67d5b34b7e1c4d0425bf6bfd1c719446d94cdfa1721c1987d891321d61f779e8236fde517dc0e524aa851a6eff1 + version: 3.24.4 + resolution: "zod@npm:3.24.4" + checksum: 10/3d545792fa54bb27ee5dbc34a5709e81f603185fcc94c8204b5d95c20dc4c81d870ff9c51f3884a30ef05cdc601449f4c4df254ac4783f0827b1faed7c1cdb48 languageName: node linkType: hard