diff --git a/.changeset/add-scheduler-cancel-task-test-utils.md b/.changeset/add-scheduler-cancel-task-test-utils.md new file mode 100644 index 0000000000..242b103eef --- /dev/null +++ b/.changeset/add-scheduler-cancel-task-test-utils.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Added `cancelTask` to `MockSchedulerService` and mock scheduler service factory. diff --git a/.changeset/add-scheduler-cancel-task.md b/.changeset/add-scheduler-cancel-task.md new file mode 100644 index 0000000000..1f98bccb1c --- /dev/null +++ b/.changeset/add-scheduler-cancel-task.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-plugin-api': minor +'@backstage/backend-defaults': patch +--- + +Added `cancelTask` method to the `SchedulerService` interface and implementation, allowing cancellation of currently running scheduled tasks. For global tasks, the database lock is released and a periodic liveness check aborts the running task function. For local tasks, the task's abort signal is triggered directly. A new `POST /.backstage/scheduler/v1/tasks/:id/cancel` endpoint is also available. diff --git a/.changeset/add-withApis-core-compat-api.md b/.changeset/add-withApis-core-compat-api.md new file mode 100644 index 0000000000..135dcf87c7 --- /dev/null +++ b/.changeset/add-withApis-core-compat-api.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-compat-api': patch +--- + +Added `withApis`, which is a Higher-Order Component for providing APIs as props to a component via `useApiHolder`. diff --git a/.changeset/auth-backend-cimd-endpoint.md b/.changeset/auth-backend-cimd-endpoint.md new file mode 100644 index 0000000000..5e2fd6761a --- /dev/null +++ b/.changeset/auth-backend-cimd-endpoint.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Added optional client metadata document endpoint at `/.well-known/oauth-client/cli.json` relative to the auth backend base URL for CLI authentication. Enabled when `auth.experimentalClientIdMetadataDocuments.enabled` is set to `true`. diff --git a/.changeset/bui-analytics.md b/.changeset/bui-analytics.md new file mode 100644 index 0000000000..65ba31b1cf --- /dev/null +++ b/.changeset/bui-analytics.md @@ -0,0 +1,11 @@ +--- +'@backstage/ui': patch +--- + +Added analytics capabilities to the component library. Components with navigation behavior (Link, ButtonLink, Tab, MenuItem, Tag, Row) now fire analytics events on click when a `BUIProvider` is present. + +New exports: `BUIProvider`, `useAnalytics`, `getNodeText`, and associated types (`AnalyticsTracker`, `UseAnalyticsFn`, `BUIProviderProps`, `AnalyticsEventAttributes`). + +Components with analytics support now accept a `noTrack` prop to suppress event firing. + +**Affected components:** Link, ButtonLink, Tab, MenuItem, Tag, Row diff --git a/.changeset/card-hybrid-click.md b/.changeset/card-hybrid-click.md new file mode 100644 index 0000000000..48343c72a7 --- /dev/null +++ b/.changeset/card-hybrid-click.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed interactive cards so that CardBody can scroll when the card has a constrained height. Previously, the overlay element blocked scroll events. + +**Affected components:** Card diff --git a/.changeset/cli-auth-commands.md b/.changeset/cli-auth-commands.md new file mode 100644 index 0000000000..e642996694 --- /dev/null +++ b/.changeset/cli-auth-commands.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': minor +--- + +Added new `auth` command group for authenticating the CLI with Backstage instances using OAuth 2.0 with a pre-registered client metadata document. Commands include `login`, `logout`, `list`, `show`, `print-token`, and `select` for managing multiple authenticated instances. diff --git a/.changeset/cli-hidden-commands.md b/.changeset/cli-hidden-commands.md new file mode 100644 index 0000000000..57d7468cc8 --- /dev/null +++ b/.changeset/cli-hidden-commands.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Internal refactor of the CLI command registration diff --git a/.changeset/core-app-api-analytics-provider.md b/.changeset/core-app-api-analytics-provider.md new file mode 100644 index 0000000000..f63bace0f3 --- /dev/null +++ b/.changeset/core-app-api-analytics-provider.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +Added `BUIProvider` from `@backstage/ui` to the app shell provider tree, enabling BUI components to fire analytics events through the Backstage analytics system. diff --git a/.changeset/curvy-socks-punch.md b/.changeset/curvy-socks-punch.md new file mode 100644 index 0000000000..a353824c13 --- /dev/null +++ b/.changeset/curvy-socks-punch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend-module-slack': minor +--- + +**BREAKING**: Only send direct messages to user entity recipients. Notifications sent to non-user entities no longer send Slack direct messages to resolved users. diff --git a/.changeset/dependabot-6f7950a.md b/.changeset/dependabot-6f7950a.md new file mode 100644 index 0000000000..ee4ac87e14 --- /dev/null +++ b/.changeset/dependabot-6f7950a.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +chore(deps): bump `dompurify` from 3.3.1 to 3.3.2 diff --git a/.changeset/deprecate-headerpage-to-header.md b/.changeset/deprecate-headerpage-to-header.md new file mode 100644 index 0000000000..83f673a422 --- /dev/null +++ b/.changeset/deprecate-headerpage-to-header.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Deprecated the `HeaderPage` component name in favor of `Header`. The old `HeaderPage`, `HeaderPageProps`, `HeaderPageOwnProps`, `HeaderPageBreadcrumb`, and `HeaderPageDefinition` exports are still available as deprecated aliases. diff --git a/.changeset/deprecate-mock-api-classes.md b/.changeset/deprecate-mock-api-classes.md new file mode 100644 index 0000000000..8aac45bc1f --- /dev/null +++ b/.changeset/deprecate-mock-api-classes.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': patch +--- + +Deprecated standalone mock API exports in favor of the `mockApis` namespace. This includes the mock classes (`MockAlertApi`, `MockAnalyticsApi`, `MockConfigApi`, `MockErrorApi`, `MockFetchApi`, `MockFeatureFlagsApi`, `MockPermissionApi`, `MockStorageApi`, `MockTranslationApi`), their option types (`MockErrorApiOptions`, `MockFeatureFlagsApiOptions`), and the `ErrorWithContext` type. `MockFetchApiOptions` is kept as a non-deprecated export. Use the `mockApis` namespace instead, for example `mockApis.alert()` or `mockApis.alert.mock()`. diff --git a/.changeset/deprecate-nav-item-blueprint.md b/.changeset/deprecate-nav-item-blueprint.md new file mode 100644 index 0000000000..adb4f051dd --- /dev/null +++ b/.changeset/deprecate-nav-item-blueprint.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Deprecated `NavItemBlueprint`. Nav items are now automatically inferred from `PageBlueprint` extensions based on their `title` and `icon` params. diff --git a/.changeset/deprecate-withApis-frontend-plugin-api.md b/.changeset/deprecate-withApis-frontend-plugin-api.md new file mode 100644 index 0000000000..5e0408fd04 --- /dev/null +++ b/.changeset/deprecate-withApis-frontend-plugin-api.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Deprecated `withApis`, use the `withApis` export from `@backstage/core-compat-api` instead. diff --git a/.changeset/eager-toes-rest.md b/.changeset/eager-toes-rest.md new file mode 100644 index 0000000000..ce9764574c --- /dev/null +++ b/.changeset/eager-toes-rest.md @@ -0,0 +1,8 @@ +--- +'@backstage/core-plugin-api': patch +'@backstage/core-app-api': patch +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-app-api': patch +--- + +Add optional `description` field to plugin-level feature flags. diff --git a/.changeset/eight-poems-take.md b/.changeset/eight-poems-take.md new file mode 100644 index 0000000000..00f1d09466 --- /dev/null +++ b/.changeset/eight-poems-take.md @@ -0,0 +1,6 @@ +--- +'@backstage/catalog-client': minor +'@backstage/plugin-catalog-backend': minor +--- + +Add an `onConflict` option to location creation that can refresh an existing location instead of throwing a conflict error. diff --git a/.changeset/export-use-entity-route.md b/.changeset/export-use-entity-route.md new file mode 100644 index 0000000000..c5ab9a9b33 --- /dev/null +++ b/.changeset/export-use-entity-route.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': minor +--- + +Exported `useEntityRefLink` hook that returns a function for generating entity page URLs from entity references. diff --git a/.changeset/fix-dialog-api-jsx-type.md b/.changeset/fix-dialog-api-jsx-type.md new file mode 100644 index 0000000000..67ece8913b --- /dev/null +++ b/.changeset/fix-dialog-api-jsx-type.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Fixed inconsistent `JSX.Element` type reference in the `DialogApiDialog.update` method signature. diff --git a/.changeset/fluffy-colts-stop.md b/.changeset/fluffy-colts-stop.md new file mode 100644 index 0000000000..9f6c9aba3d --- /dev/null +++ b/.changeset/fluffy-colts-stop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +Fixed scheduler task remaining stuck in running state after pod termination by propagating AbortSignal into MicrosoftGraphOrgEntityProvider.read() diff --git a/.changeset/happy-dogs-fly.md b/.changeset/happy-dogs-fly.md new file mode 100644 index 0000000000..30b21a66e7 --- /dev/null +++ b/.changeset/happy-dogs-fly.md @@ -0,0 +1,25 @@ +--- +'@backstage/plugin-org': minor +'@backstage/plugin-catalog-graph': minor +'@backstage/create-app': patch +--- + +**BREAKING**: Migrated `MembersListCard`, `OwnershipCard`, and `CatalogGraphCard` to use BUI card primitives via `EntityInfoCard`. + +- `OwnershipCard`: Removed `variant` and `maxScrollHeight` props. Card height and scrolling are now controlled by the parent container — the card fills its container and the body scrolls automatically when content overflows. +- `CatalogGraphCard`: Removed `variant` prop. +- `MembersListCard`: Translation keys `subtitle`, `paginationLabel`, `aggregateMembersToggle.directMembers`, `aggregateMembersToggle.aggregatedMembers`, and `aggregateMembersToggle.ariaLabel` have been removed. The `title` key now includes `{{groupName}}`. New keys added: `cardLabel`, `noSearchResult`, `aggregateMembersToggle.label`. +- `OwnershipCard`: Translation keys `aggregateRelationsToggle.directRelations`, `aggregateRelationsToggle.aggregatedRelations`, and `aggregateRelationsToggle.ariaLabel` have been removed. New key added: `aggregateRelationsToggle.label`. +- Removed `MemberComponentClassKey` export, and `root` and `cardContent` from `MembersListCardClassKey`, `card` from `OwnershipCardClassKey`, and `card` from `CatalogGraphCardClassKey`. + +**Migration:** + +```diff +- ++ +``` + +```diff +- ++ +``` diff --git a/.changeset/layout-html-attributes.md b/.changeset/layout-html-attributes.md new file mode 100644 index 0000000000..6dff67e7f1 --- /dev/null +++ b/.changeset/layout-html-attributes.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Added support for native HTML div attributes on the `Flex`, `Grid`, and `Grid.Item` components. + +**Affected components:** Flex, Grid, Grid.Item diff --git a/.changeset/menu-item-onaction-chaining.md b/.changeset/menu-item-onaction-chaining.md new file mode 100644 index 0000000000..fe89854a07 --- /dev/null +++ b/.changeset/menu-item-onaction-chaining.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Fixed MenuItem `onAction` prop ordering so user-provided `onAction` handlers are chained rather than silently overwritten. diff --git a/.changeset/migrate-cli-commands-to-cleye.md b/.changeset/migrate-cli-commands-to-cleye.md new file mode 100644 index 0000000000..e6792423ee --- /dev/null +++ b/.changeset/migrate-cli-commands-to-cleye.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Migrated remaining CLI command handlers from `commander` to `cleye` for argument parsing. Several camelCase CLI flags have been deprecated in favor of their kebab-case equivalents (e.g. `--successCache` → `--success-cache`). The old camelCase forms still work but will now log a deprecation warning. Please update any scripts or CI configurations to use the kebab-case versions. diff --git a/.changeset/move-analytics-blueprint-deprecate.md b/.changeset/move-analytics-blueprint-deprecate.md new file mode 100644 index 0000000000..c4ba61966a --- /dev/null +++ b/.changeset/move-analytics-blueprint-deprecate.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Deprecated `AnalyticsImplementationBlueprint` and `AnalyticsImplementationFactory` in favor of the exports from `@backstage/plugin-app-react`. diff --git a/.changeset/move-analytics-blueprint-to-app-react.md b/.changeset/move-analytics-blueprint-to-app-react.md new file mode 100644 index 0000000000..09dc530317 --- /dev/null +++ b/.changeset/move-analytics-blueprint-to-app-react.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-react': patch +--- + +Added `AnalyticsImplementationBlueprint` and `AnalyticsImplementationFactory`, migrated from `@backstage/frontend-plugin-api`. diff --git a/.changeset/move-extension-factory-middleware-app-api.md b/.changeset/move-extension-factory-middleware-app-api.md new file mode 100644 index 0000000000..6cc77a5d26 --- /dev/null +++ b/.changeset/move-extension-factory-middleware-app-api.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Added the `ExtensionFactoryMiddleware` type as a public export. diff --git a/.changeset/move-extension-factory-middleware-plugin-api.md b/.changeset/move-extension-factory-middleware-plugin-api.md new file mode 100644 index 0000000000..607da295a3 --- /dev/null +++ b/.changeset/move-extension-factory-middleware-plugin-api.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Deprecated the `ExtensionFactoryMiddleware` type, which has been moved to `@backstage/frontend-app-api`. diff --git a/.changeset/mui-to-bui-header.md b/.changeset/mui-to-bui-header.md new file mode 100644 index 0000000000..2efa147e29 --- /dev/null +++ b/.changeset/mui-to-bui-header.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-mui-to-bui': patch +--- + +Updated the MUI to BUI theme converter page to use the renamed `Header` component from `@backstage/ui`. diff --git a/.changeset/plugin-app-analytics-wrapper.md b/.changeset/plugin-app-analytics-wrapper.md new file mode 100644 index 0000000000..7e95de5f0f --- /dev/null +++ b/.changeset/plugin-app-analytics-wrapper.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app': patch +--- + +Added `BUIProvider` from `@backstage/ui` to the app root, enabling BUI components to fire analytics events through the Backstage analytics system. diff --git a/.changeset/remove-allowUnknownExtensionConfig-app-api.md b/.changeset/remove-allowUnknownExtensionConfig-app-api.md new file mode 100644 index 0000000000..7be9c33a30 --- /dev/null +++ b/.changeset/remove-allowUnknownExtensionConfig-app-api.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': minor +--- + +**BREAKING**: Removed the `allowUnknownExtensionConfig` option from `createSpecializedApp`. This flag had no effect and was a no-op, so no behavioral changes are expected. diff --git a/.changeset/remove-allowUnknownExtensionConfig-defaults.md b/.changeset/remove-allowUnknownExtensionConfig-defaults.md new file mode 100644 index 0000000000..3936cc226d --- /dev/null +++ b/.changeset/remove-allowUnknownExtensionConfig-defaults.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': minor +--- + +**BREAKING**: Removed the `allowUnknownExtensionConfig` option from `createApp`. This flag had no effect and was a no-op, so no behavioral changes are expected. diff --git a/.changeset/remove-any-extension-data-ref.md b/.changeset/remove-any-extension-data-ref.md new file mode 100644 index 0000000000..b220f5b871 --- /dev/null +++ b/.changeset/remove-any-extension-data-ref.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: Removed the deprecated `AnyExtensionDataRef` type. Use `ExtensionDataRef` without type parameters instead. diff --git a/.changeset/remove-createPublicSignInApp.md b/.changeset/remove-createPublicSignInApp.md new file mode 100644 index 0000000000..2d80d4790b --- /dev/null +++ b/.changeset/remove-createPublicSignInApp.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': minor +--- + +**BREAKING**: Removed the deprecated `createPublicSignInApp` function. Use `createApp` from `@backstage/frontend-defaults` with `appModulePublicSignIn` from `@backstage/plugin-app/alpha` instead. diff --git a/.changeset/remove-page-blueprint-defaultpath.md b/.changeset/remove-page-blueprint-defaultpath.md new file mode 100644 index 0000000000..848039a939 --- /dev/null +++ b/.changeset/remove-page-blueprint-defaultpath.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Removed the deprecated `defaultPath` migration helper from `PageBlueprint` params. diff --git a/.changeset/rename-page-tab-to-page-layout-tab.md b/.changeset/rename-page-tab-to-page-layout-tab.md new file mode 100644 index 0000000000..c0b1f9e807 --- /dev/null +++ b/.changeset/rename-page-tab-to-page-layout-tab.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Renamed the `PageTab` type to `PageLayoutTab`. The old `PageTab` name is now a deprecated type alias. diff --git a/.changeset/renovate-2d5eed6.md b/.changeset/renovate-2d5eed6.md new file mode 100644 index 0000000000..899fc43084 --- /dev/null +++ b/.changeset/renovate-2d5eed6.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Updated dependency `flatted` to `3.3.4`. diff --git a/.changeset/renovate-9a29a92.md b/.changeset/renovate-9a29a92.md new file mode 100644 index 0000000000..5e8393e040 --- /dev/null +++ b/.changeset/renovate-9a29a92.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Updated dependency `express-rate-limit` to `^8.0.0`. diff --git a/.changeset/simplify-extension-attach-to.md b/.changeset/simplify-extension-attach-to.md new file mode 100644 index 0000000000..2beb00d056 --- /dev/null +++ b/.changeset/simplify-extension-attach-to.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: Simplified the `ExtensionAttachTo` type to only support a single attachment target. The array form for attaching to multiple extension points has been removed. Also removed the deprecated `ExtensionAttachToSpec` type alias. diff --git a/.changeset/smooth-bees-invite.md b/.changeset/smooth-bees-invite.md new file mode 100644 index 0000000000..104fd708b9 --- /dev/null +++ b/.changeset/smooth-bees-invite.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-api-docs': patch +--- + +Updated dependency `graphiql` to `3.9.0` to address security vulnerability in `markdown-it` package. +Updated dependency `@graphiql/react` to `0.29.0` to match the version used by `graphiql`. +Moved dependency `graphql-config` to `devDependencies` as it is needed only for types. diff --git a/.changeset/split-mcp-servers.md b/.changeset/split-mcp-servers.md new file mode 100644 index 0000000000..7cfc24ad1f --- /dev/null +++ b/.changeset/split-mcp-servers.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-mcp-actions-backend': patch +--- + +Added support for splitting MCP actions into multiple servers via `mcpActions.servers` configuration. Each server gets its own endpoint at `/api/mcp-actions/v1/{key}` with actions scoped using include/exclude filter rules. Tool names are now namespaced with the plugin ID by default, configurable via `mcpActions.namespacedToolNames`. When `mcpActions.servers` is not configured, the plugin continues to serve a single server at `/api/mcp-actions/v1`. diff --git a/.changeset/template-secrets-schema.md b/.changeset/template-secrets-schema.md new file mode 100644 index 0000000000..5f6b0c4c9e --- /dev/null +++ b/.changeset/template-secrets-schema.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-common': minor +--- + +Added an optional `secrets` field to `TemplateEntityV1beta3` for configuring secrets validation. The schema for validating secrets is defined under `secrets.schema` as a JSON Schema object. diff --git a/.changeset/template-secrets-validation.md b/.changeset/template-secrets-validation.md new file mode 100644 index 0000000000..2b484ea758 --- /dev/null +++ b/.changeset/template-secrets-validation.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Added secrets schema validation for task creation, retry, and dry-run endpoints. When a template defines `spec.secrets.schema`, the API validates provided secrets against the schema and returns a `400` error if validation fails. diff --git a/.changeset/use-api-holder-no-throw.md b/.changeset/use-api-holder-no-throw.md new file mode 100644 index 0000000000..465cc9f401 --- /dev/null +++ b/.changeset/use-api-holder-no-throw.md @@ -0,0 +1,6 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/core-plugin-api': patch +--- + +Changed `useApiHolder` to return an empty `ApiHolder` instead of throwing when used outside of an API context. diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f4369920a5..e6dc0e98f5 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,7 +3,7 @@ "forwardPorts": [3000, 7007], "build": { "dockerfile": "Dockerfile" }, "features": { - "ghcr.io/devcontainers/features/common-utils:2.5.6": {}, + "ghcr.io/devcontainers/features/common-utils:2.5.7": {}, "ghcr.io/devcontainers-extra/features/mkdocs:2": {} }, "postCreateCommand": "bash .devcontainer/setup.sh", diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 11df7fc1a0..fe8306afa8 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -246,6 +246,7 @@ Levenshtein lightbox Lightsail limitranges +liveness LocalStack lockdown lockfile diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index dc63ed3b1b..4425f81905 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -27,7 +27,7 @@ jobs: run: git fetch --depth 1 origin ${{ github.base_ref }} - name: setup-node - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3bf6a2eb1..24854003fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth @@ -85,7 +85,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth @@ -241,7 +241,7 @@ jobs: run: git fetch origin ${{ github.event.pull_request.base.ref }} - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index a53004dbdd..61841f4052 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -31,7 +31,7 @@ jobs: ref: ${{ github.event.client_payload.version && env.RELEASE_VERSION || github.ref }} - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index ae9b3656d5..f88f84adad 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -59,7 +59,7 @@ jobs: ref: refs/tags/${{ steps.find-release.outputs.result }} - name: Use Node.js 22.x - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth @@ -132,7 +132,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js 22.x - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth @@ -223,7 +223,7 @@ jobs: egress-policy: audit - name: Use Node.js 22.x - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index 34dbcdb781..601751c005 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -71,7 +71,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/mui-migration-tracker.yml b/.github/workflows/mui-migration-tracker.yml index 2cb5c025e2..36107d33f2 100644 --- a/.github/workflows/mui-migration-tracker.yml +++ b/.github/workflows/mui-migration-tracker.yml @@ -26,7 +26,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Setup Node.js - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index c1c55f7abc..8f7c462785 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@f5c2471be782132e47a6e6f9c725e56730d6e9a3 # v3.32.3 + uses: github/codeql-action/upload-sarif@820e3160e279568db735cee8ed8f8e77a6da7818 # v3.32.6 with: sarif_file: results.sarif diff --git a/.github/workflows/sync_bui-docs.yml b/.github/workflows/sync_bui-docs.yml index 274307415c..15319c19b9 100644 --- a/.github/workflows/sync_bui-docs.yml +++ b/.github/workflows/sync_bui-docs.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js 22.x - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index 924dd82a40..2ae13eaa25 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -20,7 +20,7 @@ jobs: fetch-depth: 0 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/sync_dependabot-changesets.yml b/.github/workflows/sync_dependabot-changesets.yml index fb51949fd1..8768aff21a 100644 --- a/.github/workflows/sync_dependabot-changesets.yml +++ b/.github/workflows/sync_dependabot-changesets.yml @@ -21,11 +21,47 @@ jobs: fetch-depth: 2 ref: ${{ github.head_ref }} token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + - name: Verify Dependabot branch + id: verify + run: | + if git branch --show-current | grep -q '^dependabot/'; then + echo "is_dependabot=true" >> "$GITHUB_OUTPUT" + else + echo "Not a dependabot branch, skipping" + echo "is_dependabot=false" >> "$GITHUB_OUTPUT" + fi - name: Configure Git + if: steps.verify.outputs.is_dependabot == 'true' run: | git config --global user.email noreply@backstage.io git config --global user.name 'Github changeset workflow' + - name: Setup Node + if: steps.verify.outputs.is_dependabot == 'true' + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 22.x + - name: Dedupe lockfiles + if: steps.verify.outputs.is_dependabot == 'true' + id: dedupe + run: | + corepack enable + changed=false + + for dir in . docs-ui microsite; do + if [ "$dir" = "." ]; then lockfile=yarn.lock; else lockfile="$dir/yarn.lock"; fi + if git diff --name-only HEAD~1 | grep -q "^${lockfile}$"; then + yarn --cwd "$dir" dedupe + if ! git diff --quiet "$lockfile"; then + git add "$lockfile" + changed=true + fi + fi + done + + echo "changed=$changed" >> "$GITHUB_OUTPUT" - name: Generate changeset + if: steps.verify.outputs.is_dependabot == 'true' + id: changeset uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | @@ -50,12 +86,6 @@ jobs: await fs.writeFile(fileName, body); } - const branch = await exec.getExecOutput('git branch --show-current'); - if (!branch.stdout.startsWith('dependabot/')) { - console.log('Not a dependabot branch, skipping'); - return; - } - const diffOutput = await exec.getExecOutput('git diff --name-only HEAD~1'); const diffFiles = diffOutput.stdout.split('\n'); @@ -79,5 +109,9 @@ jobs: const { stdout: commitMessage } = await exec.getExecOutput('git show --pretty=format:%s -s HEAD'); await createChangeset(fileName, commitMessage, packageNames); await exec.exec('git', ['add', fileName]); - await exec.exec('git commit -C HEAD --amend --no-edit'); - await exec.exec('git push --force'); + core.setOutput('changed', 'true'); + - name: Commit and push + if: steps.verify.outputs.is_dependabot == 'true' && (steps.dedupe.outputs.changed == 'true' || steps.changeset.outputs.changed == 'true') + run: | + git commit -C HEAD --amend --no-edit --no-verify + git push --force diff --git a/.github/workflows/sync_patch-release.yml b/.github/workflows/sync_patch-release.yml index e126fe2ab9..3e96529e53 100644 --- a/.github/workflows/sync_patch-release.yml +++ b/.github/workflows/sync_patch-release.yml @@ -32,7 +32,7 @@ jobs: token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - name: Use Node.js 22.x - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index 91dffe32c9..0fee5f17b5 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -19,7 +19,7 @@ jobs: ref: v${{ github.event.client_payload.version }} - name: Use Node.js 22.x - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth @@ -81,9 +81,5 @@ jobs: owner: 'backstage', repo: 'upgrade-helper-diff', workflow_id: 'release.yml', - ref: 'master', - inputs: { - version: require('./packages/create-app/package.json').version, - releaseVersion: require('./package.json').version - }, + ref: 'master' }); diff --git a/.github/workflows/sync_renovate-changesets.yml b/.github/workflows/sync_renovate-changesets.yml index 8575cab760..0940de9c55 100644 --- a/.github/workflows/sync_renovate-changesets.yml +++ b/.github/workflows/sync_renovate-changesets.yml @@ -15,6 +15,29 @@ jobs: with: egress-policy: audit + - name: Approve yarn.lock-only PRs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + script: | + const { data: files } = await github.rest.pulls.listFiles({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + }); + + if (files.some(file => !file.filename.endsWith("yarn.lock"))) { + console.log("Skipping approval since some files are not yarn.lock"); + return; + } + + await github.rest.pulls.createReview({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + event: "APPROVE", + }); + - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 97311c9096..4a067dbbd3 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js 22.x - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index a8b364fbb8..985ff6fe9a 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -58,6 +58,6 @@ jobs: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} NODE_OPTIONS: --max-old-space-size=7168 - name: Upload Snyk report - uses: github/codeql-action/upload-sarif@f5c2471be782132e47a6e6f9c725e56730d6e9a3 # v3.32.3 + uses: github/codeql-action/upload-sarif@820e3160e279568db735cee8ed8f8e77a6da7818 # v3.32.6 with: sarif_file: snyk.sarif diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index 7df18100b0..6cdcddc8be 100644 --- a/.github/workflows/sync_version-packages.yml +++ b/.github/workflows/sync_version-packages.yml @@ -30,7 +30,7 @@ jobs: token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - name: Use Node.js 22.x - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/verify_accessibility.yml b/.github/workflows/verify_accessibility.yml index 0e8f3b7eb7..312cdd2903 100644 --- a/.github/workflows/verify_accessibility.yml +++ b/.github/workflows/verify_accessibility.yml @@ -26,7 +26,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js 22.x - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22.x - name: yarn install diff --git a/.github/workflows/verify_chromatic.yml b/.github/workflows/verify_chromatic.yml index e8905e995f..13be85fe97 100644 --- a/.github/workflows/verify_chromatic.yml +++ b/.github/workflows/verify_chromatic.yml @@ -34,7 +34,7 @@ jobs: fetch-depth: 10000 # Required to retrieve git history - name: Use node.js ${{ matrix.node-version }} - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index a919dff7b0..628577e09d 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -55,7 +55,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@f5c2471be782132e47a6e6f9c725e56730d6e9a3 # v3.32.3 + uses: github/codeql-action/init@820e3160e279568db735cee8ed8f8e77a6da7818 # v3.32.6 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -66,7 +66,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@f5c2471be782132e47a6e6f9c725e56730d6e9a3 # v3.32.3 + uses: github/codeql-action/autobuild@820e3160e279568db735cee8ed8f8e77a6da7818 # v3.32.6 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -80,4 +80,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f5c2471be782132e47a6e6f9c725e56730d6e9a3 # v3.32.3 + uses: github/codeql-action/analyze@820e3160e279568db735cee8ed8f8e77a6da7818 # v3.32.6 diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index f959027d86..4e0f82dd2c 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -67,7 +67,7 @@ jobs: git config --global user.name 'GitHub e2e user' - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index 0526df7d5c..f3786f19c3 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -42,7 +42,7 @@ jobs: python-version: '3.9' - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} - name: yarn install diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index 709c20a201..2b0e3d47c3 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -52,7 +52,7 @@ jobs: git config --global user.name 'GitHub e2e user' - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index 0d9f857740..4fb6a98f3e 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -64,7 +64,7 @@ jobs: ref: refs/tags/${{ steps.find-release.outputs.result }} - name: Use Node.js 22.x - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth @@ -134,7 +134,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js 22.x - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth @@ -219,7 +219,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js 22.x - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22.x - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 diff --git a/.github/workflows/verify_microsite_accessibility.yml b/.github/workflows/verify_microsite_accessibility.yml index 76862a9fa7..ff7a91c716 100644 --- a/.github/workflows/verify_microsite_accessibility.yml +++ b/.github/workflows/verify_microsite_accessibility.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js 22.x - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22.x diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml index 7651160795..26657cbcd3 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -36,7 +36,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/docs-ui/src/app/components/button-link/props-definition.tsx b/docs-ui/src/app/components/button-link/props-definition.tsx index deae5364df..e85a363b5a 100644 --- a/docs-ui/src/app/components/button-link/props-definition.tsx +++ b/docs-ui/src/app/components/button-link/props-definition.tsx @@ -62,6 +62,11 @@ export const buttonLinkPropDefs: Record = { ), }, + noTrack: { + type: 'boolean', + description: + 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.', + }, onSurface: { type: 'enum', values: ['0', '1', '2', '3', 'danger', 'warning', 'success', 'auto'], diff --git a/docs-ui/src/app/components/header-page/components.tsx b/docs-ui/src/app/components/header/components.tsx similarity index 84% rename from docs-ui/src/app/components/header-page/components.tsx rename to docs-ui/src/app/components/header/components.tsx index 791afc5bbe..dc32bdd6cb 100644 --- a/docs-ui/src/app/components/header-page/components.tsx +++ b/docs-ui/src/app/components/header/components.tsx @@ -1,6 +1,6 @@ 'use client'; -import { HeaderPage } from '../../../../../packages/ui/src/components/HeaderPage/HeaderPage'; +import { Header } from '../../../../../packages/ui/src/components/Header/Header'; import { Button } from '../../../../../packages/ui/src/components/Button/Button'; import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon'; import { @@ -31,7 +31,7 @@ const breadcrumbs = [ export const WithEverything = () => ( - ( export const WithLongBreadcrumbs = () => ( - +
); export const WithTabs = () => ( - +
); export const WithCustomActions = () => ( - Custom action} - /> +
Custom action} /> ); export const WithMenu = () => ( - diff --git a/docs-ui/src/app/components/header-page/page.mdx b/docs-ui/src/app/components/header/page.mdx similarity index 88% rename from docs-ui/src/app/components/header-page/page.mdx rename to docs-ui/src/app/components/header/page.mdx index ac99fb1af0..65dd8f2ea8 100644 --- a/docs-ui/src/app/components/header-page/page.mdx +++ b/docs-ui/src/app/components/header/page.mdx @@ -19,11 +19,11 @@ import { } from './snippets'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { HeaderPageDefinition } from '../../../utils/definitions'; +import { HeaderDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; @@ -61,6 +61,6 @@ Use `customActions` to add a dropdown menu. } code={withMenu} /> - + - + diff --git a/docs-ui/src/app/components/header-page/props-definition.tsx b/docs-ui/src/app/components/header/props-definition.tsx similarity index 97% rename from docs-ui/src/app/components/header-page/props-definition.tsx rename to docs-ui/src/app/components/header/props-definition.tsx index 393141870f..1b7a309b17 100644 --- a/docs-ui/src/app/components/header-page/props-definition.tsx +++ b/docs-ui/src/app/components/header/props-definition.tsx @@ -50,7 +50,7 @@ export const headerPagePropDefs: Record = { type: 'complex', description: 'Breadcrumb trail displayed above the title.', complexType: { - name: 'HeaderPageBreadcrumb[]', + name: 'HeaderBreadcrumb[]', properties: { label: { type: 'string', diff --git a/docs-ui/src/app/components/header-page/snippets.ts b/docs-ui/src/app/components/header/snippets.ts similarity index 78% rename from docs-ui/src/app/components/header-page/snippets.ts rename to docs-ui/src/app/components/header/snippets.ts index 607675717c..8dfef0e69e 100644 --- a/docs-ui/src/app/components/header-page/snippets.ts +++ b/docs-ui/src/app/components/header/snippets.ts @@ -1,8 +1,8 @@ -export const usage = `import { HeaderPage } from '@backstage/ui'; +export const usage = `import { Header } from '@backstage/ui'; -`; +
`; -export const defaultSnippet = ``; -export const withBreadcrumbs = ``; -export const withTabs = ``; -export const withCustomActions = `Custom action} />`; -export const withMenu = ` diff --git a/docs-ui/src/app/components/link/props-definition.tsx b/docs-ui/src/app/components/link/props-definition.tsx index be805d4540..951d67e8b4 100644 --- a/docs-ui/src/app/components/link/props-definition.tsx +++ b/docs-ui/src/app/components/link/props-definition.tsx @@ -68,6 +68,11 @@ export const linkPropDefs: Record = { 'Truncates text with ellipsis when it overflows its container.', default: 'false', }, + noTrack: { + type: 'boolean', + description: + 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.', + }, standalone: { type: 'boolean', description: 'Removes underline by default. Underline appears on hover.', diff --git a/docs-ui/src/app/components/menu/props-definition.tsx b/docs-ui/src/app/components/menu/props-definition.tsx index 5e11b22737..f6cb88b3e2 100644 --- a/docs-ui/src/app/components/menu/props-definition.tsx +++ b/docs-ui/src/app/components/menu/props-definition.tsx @@ -309,6 +309,11 @@ export const menuItemPropDefs: Record = { type: 'boolean', description: 'Whether the item is disabled.', }, + noTrack: { + type: 'boolean', + description: + 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.', + }, textValue: { type: 'string', description: 'Text used for typeahead and accessibility.', diff --git a/docs-ui/src/app/components/plugin-header/components.tsx b/docs-ui/src/app/components/plugin-header/components.tsx index b3d831e718..045d0956c4 100644 --- a/docs-ui/src/app/components/plugin-header/components.tsx +++ b/docs-ui/src/app/components/plugin-header/components.tsx @@ -1,7 +1,7 @@ 'use client'; import { PluginHeader } from '../../../../../packages/ui/src/components/PluginHeader/PluginHeader'; -import { HeaderPage } from '../../../../../packages/ui/src/components/HeaderPage/HeaderPage'; +import { Header } from '../../../../../packages/ui/src/components/Header/Header'; import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon'; import { Button } from '../../../../../packages/ui/src/components/Button/Button'; import { MemoryRouter } from 'react-router-dom'; @@ -58,11 +58,11 @@ export const WithAllOptions = () => ( ); -export const WithHeaderPage = () => ( +export const WithHeader = () => ( <> - Custom action} diff --git a/docs-ui/src/app/components/plugin-header/page.mdx b/docs-ui/src/app/components/plugin-header/page.mdx index 5f8e02bfc4..cbe4dfe141 100644 --- a/docs-ui/src/app/components/plugin-header/page.mdx +++ b/docs-ui/src/app/components/plugin-header/page.mdx @@ -4,7 +4,7 @@ import { Snippet } from '@/components/Snippet'; import { WithAllOptionsAndTabs, WithAllOptions, - WithHeaderPage, + WithHeader, } from './components'; import { headerPropDefs } from './props-definition'; import { @@ -12,7 +12,7 @@ import { simple, defaultSnippet, withTabs, - withHeaderPage, + withHeader, } from './snippets'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; @@ -46,11 +46,11 @@ Tabs use React Router and highlight automatically based on the current route. } code={withTabs} open /> -### Plugin header with HeaderPage +### Plugin header with Header -Combine with [HeaderPage](/components/header-page) for multi-level navigation. +Combine with [Header](/components/header) for multi-level navigation. -} code={withHeaderPage} open /> +} code={withHeader} open /> diff --git a/docs-ui/src/app/components/plugin-header/snippets.ts b/docs-ui/src/app/components/plugin-header/snippets.ts index 4e9518af73..874a16184a 100644 --- a/docs-ui/src/app/components/plugin-header/snippets.ts +++ b/docs-ui/src/app/components/plugin-header/snippets.ts @@ -43,7 +43,7 @@ export const withTabs = ``; -export const withHeaderPage = ` - = { description: 'Row content. Can be a render function receiving column config.', }, + noTrack: { + type: 'boolean', + description: + 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.', + }, ...classNamePropDefs, ...stylePropDefs, }; diff --git a/docs-ui/src/app/components/tabs/props-definition.ts b/docs-ui/src/app/components/tabs/props-definition.ts index e1c1f056cb..92d1c3d258 100644 --- a/docs-ui/src/app/components/tabs/props-definition.ts +++ b/docs-ui/src/app/components/tabs/props-definition.ts @@ -68,6 +68,11 @@ export const tabPropDefs: Record = { default: 'false', description: 'Disables this tab. Use for temporarily unavailable options.', }, + noTrack: { + type: 'boolean', + description: + 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.', + }, ...childrenPropDefs, ...classNamePropDefs, }; diff --git a/docs-ui/src/app/components/tag-group/props-definition.tsx b/docs-ui/src/app/components/tag-group/props-definition.tsx index 3f64b03e8c..dc10d5f7e7 100644 --- a/docs-ui/src/app/components/tag-group/props-definition.tsx +++ b/docs-ui/src/app/components/tag-group/props-definition.tsx @@ -84,6 +84,11 @@ export const tagPropDefs: Record = { type: 'boolean', description: 'Whether the tag is disabled.', }, + noTrack: { + type: 'boolean', + description: + 'Suppresses the automatic analytics click event, e.g. if you already have custom tracking.', + }, ...childrenPropDefs, ...classNamePropDefs, }; diff --git a/docs-ui/src/app/get-started/installation/page.mdx b/docs-ui/src/app/get-started/installation/page.mdx index cdb14d5924..c5421ae9d7 100644 --- a/docs-ui/src/app/get-started/installation/page.mdx +++ b/docs-ui/src/app/get-started/installation/page.mdx @@ -1,6 +1,10 @@ import { CodeBlock } from '@/components/CodeBlock'; import { Banner } from '@/components/Banner'; -import { snippet } from './snippets'; +import { + snippet, + analyticsSetupSnippet, + analyticsNoTrackSnippet, +} from './snippets'; # Installation @@ -44,3 +48,25 @@ your plugin and import the components you need. /> + +## Analytics + +BUI components with navigation behavior — Link, ButtonLink, Tab, MenuItem, Tag, and Row — can fire analytics events when clicked. To enable this, you need to connect BUI's analytics layer to Backstage's analytics system. + +### Setup + +If you're using the **new frontend system**, analytics is wired automatically via `@backstage/plugin-app` — no setup required. + +For the **old frontend system**, the `BUIProvider` is included in the app shell from `@backstage/core-app-api` and works out of the box. + +If you need to set up the provider manually (e.g. in a custom app shell), wrap your app content with the `BUIProvider` and pass in Backstage's `useAnalytics` hook: + + + +### How it works + +Once configured, BUI components fire a `click` event through Backstage's analytics system when a user navigates. Events include the link text as the subject and the destination URL in the attributes, along with any `AnalyticsContext` metadata (such as `pluginId`) from the component's position in the tree. + +To suppress tracking on an individual component, use the `noTrack` prop: + + diff --git a/docs-ui/src/app/get-started/installation/snippets.ts b/docs-ui/src/app/get-started/installation/snippets.ts index 7e63005172..12dfff4f44 100644 --- a/docs-ui/src/app/get-started/installation/snippets.ts +++ b/docs-ui/src/app/get-started/installation/snippets.ts @@ -4,3 +4,16 @@ export const snippet = `import { Flex, Button, Text } from '@backstage/ui'; Hello World ;`; + +export const analyticsSetupSnippet = `import { BUIProvider } from '@backstage/ui'; +import { useAnalytics } from '@backstage/core-plugin-api'; + +// Wrap your app content with the provider + + +`; + +export const analyticsNoTrackSnippet = `// Suppress analytics for a specific link + + Skip tracking +`; diff --git a/docs-ui/src/components/Toolbar/Toolbar.tsx b/docs-ui/src/components/Toolbar/Toolbar.tsx index 4a071bf45e..ea094b2a81 100644 --- a/docs-ui/src/components/Toolbar/Toolbar.tsx +++ b/docs-ui/src/components/Toolbar/Toolbar.tsx @@ -3,6 +3,7 @@ import { useState, useEffect } from 'react'; import { RiArrowDownSLine, + RiDiscordLine, RiGithubLine, RiMoonLine, RiSearchLine, @@ -95,6 +96,7 @@ export const Toolbar = ({ version }: ToolbarProps) => { @@ -103,11 +105,23 @@ export const Toolbar = ({ version }: ToolbarProps) => { + + + =18.2.0" - checksum: 10/10241f2e07826ce7d595cf9687a35c96cc9cf9eb68a9431d7dfa1b9df479dbef302874d28c0502cee2a536cf34722de7c49ed12d10a2b071e4e42ba4a278c516 + checksum: 10/3d8f1d86b2bb20ab5e44d15f18811e928b0886f7710eb7a1516afb9913ba72e46facec5dfee382825139d800bcbb6704c15d0c760d0f977c12257d4af8db3295 languageName: node linkType: hard @@ -1558,8 +1558,8 @@ __metadata: linkType: hard "@uiw/codemirror-themes@npm:^4.23.7": - version: 4.25.4 - resolution: "@uiw/codemirror-themes@npm:4.25.4" + version: 4.25.7 + resolution: "@uiw/codemirror-themes@npm:4.25.7" dependencies: "@codemirror/language": "npm:^6.0.0" "@codemirror/state": "npm:^6.0.0" @@ -1568,7 +1568,7 @@ __metadata: "@codemirror/language": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: 10/aab62e2c16a10dbde4592cef704d386c1a9e3caece4ff0eed85e9d9a5acdeaf3fd8717e2cb6a6795e12e890b8600956effb52d453aacbc618934e7ae8c3e59e3 + checksum: 10/6910a41090611b608dfd16eaf82c8e9e9edbab528fe06edfff5947050de6d1363749099a7462ecd3a6544c093feb8d8f1e2c1c7b687b889134c0e59966b1ff1f languageName: node linkType: hard @@ -2821,8 +2821,8 @@ __metadata: linkType: hard "eslint@npm:^9": - version: 9.39.2 - resolution: "eslint@npm:9.39.2" + version: 9.39.3 + resolution: "eslint@npm:9.39.3" dependencies: "@eslint-community/eslint-utils": "npm:^4.8.0" "@eslint-community/regexpp": "npm:^4.12.1" @@ -2830,7 +2830,7 @@ __metadata: "@eslint/config-helpers": "npm:^0.4.2" "@eslint/core": "npm:^0.17.0" "@eslint/eslintrc": "npm:^3.3.1" - "@eslint/js": "npm:9.39.2" + "@eslint/js": "npm:9.39.3" "@eslint/plugin-kit": "npm:^0.4.1" "@humanfs/node": "npm:^0.16.6" "@humanwhocodes/module-importer": "npm:^1.0.1" @@ -2865,7 +2865,7 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: 10/53ff0e9c8264e7e8d40d50fdc0c0df0b701cfc5289beedfb686c214e3e7b199702f894bbd1bb48653727bb1ecbd1147cf5f555a4ae71e1daf35020cdc9072d9f + checksum: 10/1c95c92983ddf435e7f7d54edd06d703a15773a7d189583d3388e5b5ac714f0a2450b91c0b3bb9b9ccec9bd20994fd8e48d231ed6dabca0be56ef314b32820ff languageName: node linkType: hard @@ -3103,11 +3103,11 @@ __metadata: languageName: node linkType: hard -"framer-motion@npm:^12.34.3": - version: 12.34.3 - resolution: "framer-motion@npm:12.34.3" +"framer-motion@npm:^12.35.0": + version: 12.35.0 + resolution: "framer-motion@npm:12.35.0" dependencies: - motion-dom: "npm:^12.34.3" + motion-dom: "npm:^12.35.0" motion-utils: "npm:^12.29.2" tslib: "npm:^2.4.0" peerDependencies: @@ -3121,7 +3121,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 10/4565dc7f35c631c07ade3b77b19c14946ca37cc1125176e4b8c3a6b7d6994207867f9d9befcb2885d5bc8b6b4ea82ff8ee2cfa869f676cc28d18656acb33ff88 + checksum: 10/9c2dd8a44efcf15d787ea1d1a24dbb2352b44824c4c33273f176f3feabe602278b9571541c89cc5673b3590b483daf9e0761889412feaafd4feba758cfdc1c96 languageName: node linkType: hard @@ -4500,12 +4500,12 @@ __metadata: languageName: node linkType: hard -"motion-dom@npm:^12.34.3": - version: 12.34.3 - resolution: "motion-dom@npm:12.34.3" +"motion-dom@npm:^12.35.0": + version: 12.35.0 + resolution: "motion-dom@npm:12.35.0" dependencies: motion-utils: "npm:^12.29.2" - checksum: 10/17abb91f87020f00470f886cced6c6ebd58b9d5848c9bb12eb38f029dc7077344cf6ac2ccbe0adf1a8996191c17a98abfe91eaa99a1723909d7dd25a4f2b1101 + checksum: 10/1a50cd2600d04dc8243c193af45b067aa65d853213f14aea15858a94fd3e901511ff258a91b4dffc1585eb43b61706a6f73fb98fb39dedd87e4b58a48ed8f593 languageName: node linkType: hard @@ -4517,10 +4517,10 @@ __metadata: linkType: hard "motion@npm:^12.4.1": - version: 12.34.3 - resolution: "motion@npm:12.34.3" + version: 12.35.0 + resolution: "motion@npm:12.35.0" dependencies: - framer-motion: "npm:^12.34.3" + framer-motion: "npm:^12.35.0" tslib: "npm:^2.4.0" peerDependencies: "@emotion/is-prop-valid": "*" @@ -4533,7 +4533,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 10/1465fdee135e792f753a8ac5eb3ebdfb547165686f7a896ba947ecf2d02443f11ced9b3933aec773d4531758a9159dd24e48bd2e02c0d94754649645eac5121c + checksum: 10/ef7f4df7337147a0012ba2bfe3fd1f23d76081b4094ab94a7bc9f3f8338074e7a290873a9ea94427aea1aa9c63021804972f62d6c4289e011ddf5ef59865fa01 languageName: node linkType: hard diff --git a/docs/auth/auth0/provider.md b/docs/auth/auth0/provider.md index b836bf01f6..1a560d4f62 100644 --- a/docs/auth/auth0/provider.md +++ b/docs/auth/auth0/provider.md @@ -5,13 +5,6 @@ sidebar_label: Auth0 description: Adding Auth0 as an authentication provider in Backstage --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage -[version 1.24](../../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/auth0/provider--old.md) -instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - The Backstage `core-plugin-api` package comes with an Auth0 authentication provider that can authenticate users using OAuth. diff --git a/docs/auth/bitbucketServer/provider.md b/docs/auth/bitbucketServer/provider.md index 89b8be3ff6..0beaa08f18 100644 --- a/docs/auth/bitbucketServer/provider.md +++ b/docs/auth/bitbucketServer/provider.md @@ -5,13 +5,6 @@ sidebar_label: Bitbucket Server description: Adding Bitbucket Server OAuth as an authentication provider in Backstage --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage -[version 1.24](../../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/bitbucketServer/provider--old.md) -instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - The Backstage `core-plugin-api` package comes with a Bitbucket Server authentication provider that can authenticate users using Bitbucket Server. This does **NOT** work with Bitbucket Cloud. diff --git a/docs/auth/guest/provider.md b/docs/auth/guest/provider.md index 5072aabe50..f2ebfff8d6 100644 --- a/docs/auth/guest/provider.md +++ b/docs/auth/guest/provider.md @@ -19,10 +19,6 @@ This provider should only ever be enabled for `development`. To prevent unauthor ### Backend -:::note -This will only work with the new backend system. There is no support for this in the old backend. -::: - Add the `@backstage/plugin-auth-backend-module-guest-provider` to your backend installation. ```sh title="From your Backstage root directory" diff --git a/docs/auth/identity-resolver.md b/docs/auth/identity-resolver.md index 0d4b50dcbe..26b48dec69 100644 --- a/docs/auth/identity-resolver.md +++ b/docs/auth/identity-resolver.md @@ -4,13 +4,6 @@ title: Sign-in Identities and Resolvers description: An introduction to Backstage user identities and sign-in resolvers --- -:::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage -[version 1.24](../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/identity-resolver--old.md) -instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - By default, every Backstage auth provider is configured only for the use-case of access delegation. This enables Backstage to request resources and actions from external systems on behalf of the user, for example re-triggering a build in CI. diff --git a/docs/auth/index.md b/docs/auth/index.md index fb85eb999b..389f824be3 100644 --- a/docs/auth/index.md +++ b/docs/auth/index.md @@ -12,7 +12,7 @@ access to external resources. :::note Note -Identity management and the Sign-In page in Backstage will only block external access when using the new backend system, without setting `backend.auth.dangerouslyDisableDefaultAuthPolicy` in configuration. Even so, the frontend bundle is not protected from external access, protecting it requires the use of the [experimental public entry point](https://backstage.io/docs/tutorials/enable-public-entry/). You can learn more about this in the [Threat Model](../overview/threat-model.md#operator-responsibilities). +Identity management and the Sign-In page in Backstage will block external access by default, without setting `backend.auth.dangerouslyDisableDefaultAuthPolicy` in configuration. Even so, the frontend bundle is not protected from external access, protecting it requires the use of the [experimental public entry point](https://backstage.io/docs/tutorials/enable-public-entry/). You can learn more about this in the [Threat Model](../overview/threat-model.md#operator-responsibilities). ::: @@ -461,7 +461,7 @@ providerFactories: { }, ``` -In the new backend system you can leverage the `authProvidersExtensionPoint` for this: +You can leverage the `authProvidersExtensionPoint` for this: ```ts // your-auth-plugin-module.ts diff --git a/docs/auth/oidc.md b/docs/auth/oidc.md index a55719e564..194eb08d05 100644 --- a/docs/auth/oidc.md +++ b/docs/auth/oidc.md @@ -4,13 +4,6 @@ title: OIDC provider from scratch description: This section shows how to enable and use the Backstage OIDC provider. --- -:::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage -[version 1.24](../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/oidc--old.md) -instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - This section shows how to enable and use the Backstage OIDC provider. ## Summary diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index 1423fa92f7..6655c76b29 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -4,13 +4,6 @@ title: Service to Service Auth description: This section describes service to service authentication works, both internally within Backstage plugins and when external callers want to make requests. --- -:::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage -[version 1.24](../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/service-to-service-auth--old.md) -instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - This article describes how _service-to-service auth_ works in Backstage, both between Backstage backend plugins and for external callers who want to make requests to them. This is in contrast to _user and user-to-service auth_ which @@ -20,7 +13,7 @@ Each section describes one distinct type of auth flow. ## Standard Plugin-to-Plugin Auth -Backstage plugins that use the new backend system and handle credentials using +Backstage plugins that use the backend system and handle credentials using the `auth` and `httpAuth` service APIs are secure by default, without requiring any configuration. They generate self-signed tokens automatically for making requests to other Backstage backend plugins, and the receivers use the caller's @@ -228,96 +221,6 @@ calling Backstage plugins: Authorization: Bearer eyJhbG... ``` -## Legacy Tokens - -Plugins and backends that are _not_ on the new backend system use a legacy token -flow, where shared static secrets in your app-config are used for signing and -verification. If you are on the new backend system and are not using legacy -plugins using the compatibility wrapper, you can skip this section. - -### Configuration (legacy) - -In local development, there is no need to configure anything for this auth -method. But in production, you must configure at least one legacy type external -access method: - -```yaml title="in e.g. app-config.production.yaml" -backend: - auth: - externalAccess: - - type: legacy - options: - secret: my-secret-key-catalog - subject: legacy-catalog - - type: legacy - options: - secret: my-secret-key-scaffolder - subject: legacy-scaffolder -``` - -The old style keys config is also supported as an alternative, but please -consider using the new style above instead: - -```yaml title="in e.g. app-config.production.yaml" -backend: - auth: - keys: - - secret: my-secret-key-catalog - - secret: my-secret-key-scaffolder -``` - -The secrets must be any base64-encoded random data, but for security reasons -should be sufficiently long so as not to be easy to guess by brute force. You -can for example generate them on the command line: - -```shell -node -p 'require("crypto").randomBytes(24).toString("base64")' -``` - -The subjects must be strings without whitespace. They are used for identifying -each caller, and become part of the credentials object that request recipient -plugins get. - -In both of the examples we showed two secrets being specified, but the minimum -is one. The order is significant: the first one is always used for signing of -outgoing requests to other backend plugins, while all of the keys are used for -verification. This is useful if you want to be able to have unique keys per -deployment if you are using split deployments of Backstage. Then each deployment -lists its own signing secret at the top, and only adds the secrets for those -other deployments that it wants to permit to call it. - -For most organizations, we recommend leaving it at just one key and -[migrating](../backend-system/building-backends/08-migrating.md) to the new -backend system as soon as possible instead of experimenting with multiple legacy -secrets. - -### External Callers (legacy) - -For legacy Backstage backend plugins, the above configuration is enough. But -external callers who wish to make requests using this flow must generate tokens -according to the following rules. - -The token must be a JWT with a `HS256` signature, using the raw base64 decoded -value of the configured key as the secret. It must also have the following -payload: - -- `sub`: the exact string "backstage-server" -- `exp`: one hour from the time it was generated, in epoch seconds - -:::note Note - -The JWT must encode the `alg` header as a protected header, such as with -[setProtectedHeader](https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md#setprotectedheader). - -::: - -The caller then passes along the JWT token with requests in the `Authorization` -header: - -```yaml -Authorization: Bearer eZv5o+fW3KnR3kVabMW4ZcDNLPl8nmMW -``` - ## Access Restrictions Each `externalAccess` entry may optionally have an `accessRestrictions` key, diff --git a/docs/backend-system/architecture/01-index.md b/docs/backend-system/architecture/01-index.md index 293a46b7ca..6e1012dce0 100644 --- a/docs/backend-system/architecture/01-index.md +++ b/docs/backend-system/architecture/01-index.md @@ -2,12 +2,12 @@ id: index title: Backend System Architecture sidebar_label: Overview -description: The structure and architecture of the new Backend System and its component parts +description: The structure and architecture of the Backend System and its component parts --- ## Building Blocks -This section introduces the high-level building blocks upon which this new +This section introduces the high-level building blocks upon which this system is built. These are all concepts that exist in our current system in one way or another, but they have all been lifted up to be first class concerns in the new system. Regardless of whether you are setting up your own backstage diff --git a/docs/backend-system/building-backends/01-index.md b/docs/backend-system/building-backends/01-index.md index a38c1d44e5..3fb9ea60a9 100644 --- a/docs/backend-system/building-backends/01-index.md +++ b/docs/backend-system/building-backends/01-index.md @@ -2,7 +2,7 @@ id: index title: Building Backends sidebar_label: Overview -description: Building backends using the new backend system +description: Building backends using the backend system --- :::note Note 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 53bf4af8ef..3b0696aa8e 100644 --- a/docs/backend-system/building-plugins-and-modules/01-index.md +++ b/docs/backend-system/building-plugins-and-modules/01-index.md @@ -2,16 +2,9 @@ id: index title: Building Backend Plugins and Modules sidebar_label: Overview -description: Building backend plugins and modules using the new backend system +description: Building backend plugins and modules using the backend system --- -:::note Note - -If you have an existing backend and/or backend plugins that are not yet -using the new backend system, see [migrating](./08-migrating.md). - -::: - This section covers how to build your own backend [plugins](../architecture/04-plugins.md) and [modules](../architecture/06-modules.md). They are sometimes collectively referred to as backend _features_, and are the building blocks that adopters add to their diff --git a/docs/backend-system/core-services/auditor.md b/docs/backend-system/core-services/auditor.md index fe4ecd196c..2d0864eeca 100644 --- a/docs/backend-system/core-services/auditor.md +++ b/docs/backend-system/core-services/auditor.md @@ -146,8 +146,8 @@ To clarify how to utilize the Auditor feature effectively, we recommend explorin - **Code Implementation Example (createRouter.ts):** - The [`createRouter.ts`](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend/src/service/createRouter.ts) file within the Catalog Backend showcases a practical integration of the `AuditorService` within a Backstage backend plugin. - Specifically, the lines that demonstrate the creation of an audit event. This includes setting critical parameters such as `eventId` and `severityLevel`, as well as incorporating relevant metadata like `queryType` and `entityRef`. -- **Documentation Example (README.md):** - - The "Audit Events" section of the Catalog Backend's [`README.md`](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend/README.md#audit-events) provides a well-structured example of documenting emitted audit events. +- **Documentation Example:** + - The "Audit Events" section of the Software Catalog [Audit Documentation](../../features/software-catalog/audit-events.md) provides a well-structured example of documenting emitted audit events. - It illustrates how to detail various `eventId` values and their corresponding `meta` fields (e.g., `queryType`, `actionType`) for different plugin operations. These examples provide both a code-level demonstration and a documentation guideline for effectively utilizing the `AuditorService` to manage audit events within your Backstage plugins. diff --git a/docs/features/kubernetes/authenticationstrategy.md b/docs/features/kubernetes/authenticationstrategy.md index e39201aaeb..5c20965bde 100644 --- a/docs/features/kubernetes/authenticationstrategy.md +++ b/docs/features/kubernetes/authenticationstrategy.md @@ -157,7 +157,7 @@ export class AwsIamStrategy implements AuthenticationStrategy { Sometimes you need to add a new way to authenticate against a kubernetes cluster not support by default by Backstage. This is how integrators can bring their own kubernetes auth strategies through the use of the [`addAuthStrategy`](https://github.com/backstage/backstage/blob/57397e7d6d2d725712c439f4ab93f2ac6aa27bf8/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts#L211) method on `KubernetesBuilder` or through the [AuthStrategyExtensionPoint](https://github.com/backstage/backstage/blob/57397e7d6d2d725712c439f4ab93f2ac6aa27bf8/plugins/kubernetes-backend/src/plugin.ts#L112). So, on the following sections, we are going to introduce a new AuthStrategy for [Pinniped][1], an authentication service for Kubernetes clusters. -### Custom Pinniped auth strategy in the new backend system +### Custom Pinniped auth strategy in the backend system To add a new AuthStrategy, we need to create a new Pinniped [backend module](../../backend-system/building-plugins-and-modules/01-index.md#modules) to extend the Kubernetes-Backend plugin. The Pinniped module will interact with the Kubernetes-Backend plugin through the [extension points](../../backend-system/architecture/05-extension-points.md) registered by the plugin. The Kubernetes-Backend plugin [registers](https://github.com/backstage/backstage/blob/ebe7afad9d19f279469168ca0d4feceb92c1ad36/plugins/kubernetes-backend/src/plugin.ts#L155) multiple extension points like `kubernetesObjectsProvider`, `kubernetesClusterSupplier`, `kubernetesFetcher`, `kubernetesServiceLocator` and the `kubernetesAuthStrategy`. diff --git a/docs/features/software-catalog/configuration.md b/docs/features/software-catalog/configuration.md index 3ed027a0a4..8c9241eb36 100644 --- a/docs/features/software-catalog/configuration.md +++ b/docs/features/software-catalog/configuration.md @@ -366,3 +366,39 @@ Now install your module. ```ts title="packages/backend/src/index.ts" backend.add(eventsModuleCatalogErrors); ``` + +## OpenAPI and AsyncAPI Placeholder Support + +The **OpenAPI Catalog Backend Module** registers a JSON Schema placeholder resolver for the `openapi` (and `asyncapi`) placeholder keys. This enables you to use `$openapi` and `$asyncapi` references in your catalog entities, while having all underlying `$ref` pointers resolved and bundled as part of the schema processing. + +### Installation + +1. Add the package to your backend: + +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-openapi +``` + +2. Register the module in your backend: + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend-module-openapi')); +``` + +### Usage + +To trigger the `$ref` resolution, use the `$openapi` (or `$asyncapi`) placeholder in your catalog entity definition: + +```yaml +apiVersion: backstage.io/v1alpha1 +kind: API +metadata: + name: example + description: Example API +spec: + type: openapi + lifecycle: production + owner: team + definition: + $openapi: ./spec/openapi.yaml # by using $openapi Backstage will now resolve all $ref instances +``` diff --git a/docs/features/software-templates/authorizing-scaffolder-template-details.md b/docs/features/software-templates/authorizing-scaffolder-template-details.md index 7ff2cd9377..e35d81d260 100644 --- a/docs/features/software-templates/authorizing-scaffolder-template-details.md +++ b/docs/features/software-templates/authorizing-scaffolder-template-details.md @@ -6,7 +6,13 @@ description: How to authorize parts of a template and authorize scaffolder task The scaffolder plugin integrates with the Backstage [permission framework](../../permissions/overview.md), which allows you to control access to certain parameters and steps in your templates based on the user executing the template. It also allows you to control access to scaffolder tasks. -### Authorizing parameters and steps +:::tip + +To better understand the following examples make sure to review the [Writing a permission policy](../../permissions/writing-a-policy.md)) documentation before moving forward. + +::: + +## Authorizing parameters and steps To mark specific parameters or steps as requiring permission, add the `backstage:permissions` property to the parameter or step with one or more tags. For example: @@ -288,62 +294,3 @@ All other users are granted permissions to perform/access the following actions/ - Read scaffolder tasks and their associated events/logs for tasks created by the user. Although the rules exported by the scaffolder are simple, combining them can help you achieve more complex use cases. - -### Authorizing in the New Backend System - -Instead of the changes in `permission.ts` noted in the above example you will make them in your `index.ts`. You will need to create a module where your permission policy will get added. Here is a very simplified example of how to do that: - -```ts title="packages/backend/src/index.ts" -import { createBackendModule } from '@backstage/backend-plugin-api'; -import { - PolicyDecision, - AuthorizeResult, -} from '@backstage/plugin-permission-common'; -import { - PermissionPolicy, - PolicyQuery, - PolicyQueryUser, -} from '@backstage/plugin-permission-node'; -import { policyExtensionPoint } from '@backstage/plugin-permission-node/alpha'; - -class ExamplePermissionPolicy implements PermissionPolicy { - async handle( - request: PolicyQuery, - user?: PolicyQueryUser, - ): Promise { - // Various scaffolder permission checks ... - - return { - result: AuthorizeResult.ALLOW, - }; - } -} - -const customPermissionBackendModule = createBackendModule({ - pluginId: 'permission', - moduleId: 'allow-all-policy', - register(reg) { - reg.registerInit({ - deps: { policy: policyExtensionPoint }, - async init({ policy }) { - policy.setPolicy(new ExamplePermissionPolicy()); - }, - }); - }, -}); - -const backend = createBackend(); - -// Other plugins... - -/* highlight-add-start */ -backend.add(import('@backstage/plugin-permission-backend')); -backend.add(customPermissionBackendModule); -/* highlight-add-end */ -``` - -:::note Note - -The `ExamplePermissionPolicy` here could be the one from the [Authorizing parameters and steps](#authorizing-parameters-and-steps) example or from the [Authorizing actions](#authorizing-actions) example. It would work the same way for both of them. - -::: diff --git a/docs/features/software-templates/builtin-actions.md b/docs/features/software-templates/builtin-actions.md index 22d8ef6454..378c15e18f 100644 --- a/docs/features/software-templates/builtin-actions.md +++ b/docs/features/software-templates/builtin-actions.md @@ -32,7 +32,7 @@ Here's how to add an action module, first you need to run this command: yarn --cwd packages/backend add @backstage/plugin-scaffolder-backend-module-github ``` -Then you need to add it to your backend, this is a simplified new backend system for example purposes: +Then you need to add it to your backend, this is a simplified backend system for example purposes: ```ts title="/packages/backend/src/index.ts" import { createBackend } from '@backstage/backend-defaults'; diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 5d9fcba603..64c1acc67d 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -325,6 +325,57 @@ spec: token: ${{ each.value.token }} ``` +### Defining a Secrets Schema + +You can define a JSON Schema for secrets that will be validated when a task is created. This is useful when secrets are passed programmatically (e.g., via CI/CD pipelines or API calls) rather than through the UI form. The schema ensures that required secrets are provided before task execution begins. + +```yaml +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: publish-to-npm + title: Publish to NPM +spec: + owner: backstage/techdocs-core + type: service + + # Define required secrets with a JSON Schema + secrets: + schema: + required: + - NPM_TOKEN + properties: + NPM_TOKEN: + type: string + description: NPM authentication token for publishing + + parameters: + - title: Package Details + properties: + packageName: + type: string + title: Package Name + + steps: + - id: publish + action: npm:publish + input: + packageName: ${{ parameters.packageName }} + token: ${{ secrets.NPM_TOKEN }} +``` + +When a task is created without the required secrets, the API returns a `400` error with a descriptive message: + +```json +{ + "errors": [ + { + "message": "secrets.NPM_TOKEN is required" + } + ] +} +``` + ### 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/addons--new.md b/docs/features/techdocs/addons--new.md index cbafcbe127..af7f4d2893 100644 --- a/docs/features/techdocs/addons--new.md +++ b/docs/features/techdocs/addons--new.md @@ -93,7 +93,7 @@ discover available Addons, we've compiled a list of them here: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`techDocsExpandableNavigationAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsExpandableNavigationAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | Allows TechDocs users to expand or collapse the entire TechDocs main navigation, and keeps the user's preferred state between documentation sites. | | [`techDocsReportIssueAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsReportIssueAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. | -| [`techDocsTextSizeAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsReportIssueAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. | +| [`techDocsTextSizeAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsTextSizeAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. | | [`techDocsLightBoxAddonModule`](https://backstage.io/api/stable/variables/_backstage_plugin-techdocs-module-addons-contrib.plugins_techdocs-module-addons-contrib_src_alpha.techDocsLightBoxAddonModule.html) | `@backstage/plugin-techdocs-module-addons-contrib/alpha` | This TechDocs addon allows users to open images in a light-box on documentation pages, they can navigate between images if there are several on one page. The image size of the light-box image is the same as the image size on the document page. When clicking on the zoom icon it zooms the image to fit in the screen (similar to `background-size: contain`). | Got an Addon to contribute? Feel free to add a row above! diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md index 1246600683..76c231ff0e 100644 --- a/docs/features/techdocs/getting-started.md +++ b/docs/features/techdocs/getting-started.md @@ -95,9 +95,6 @@ That's it! Now, we need the TechDocs Backend plugin for the frontend to work. ## Adding TechDocs Backend plugin -:::note -These instructions are for the new backend system. For setting this up in the old backend system, see [here](https://github.com/backstage/backstage/blob/v1.36.1/docs/features/techdocs/getting-started.md) -::: First we need to install the `@backstage/plugin-techdocs-backend` package. ```bash title="From your Backstage root directory" diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 755d88b163..33faf44841 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -852,9 +852,9 @@ and publish the documentation for them. If the value of the `company.com/techdoc annotation is anything other than `'local'`, the user is responsible for publishing documentation to the appropriate location in the TechDocs external storage. -### Hybrid build strategy using the New Backend System +### Hybrid build strategy using the Backend System -To setup a hybrid build strategy using the New Backend System you'll follow the same steps as above but for Step 4 you will need to do the following: +To setup a hybrid build strategy using the Backend System you'll follow the same steps as above but for Step 4 you will need to do the following: ```ts title="packages/backend/src/index.ts" const backend = createBackend(); diff --git a/docs/integrations/aws-s3/discovery.md b/docs/integrations/aws-s3/discovery.md index fcba5ca04f..74e2fde6a5 100644 --- a/docs/integrations/aws-s3/discovery.md +++ b/docs/integrations/aws-s3/discovery.md @@ -5,10 +5,6 @@ sidebar_label: Discovery description: Automatically discovering catalog entities from an AWS S3 Bucket --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/aws-s3/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - The AWS S3 integration has a special entity provider for discovering catalog entities located in an S3 Bucket. If you have a bucket that contains multiple catalog files, and you want to automatically discover them, you can use this diff --git a/docs/integrations/azure-blobStorage/discovery.md b/docs/integrations/azure-blobStorage/discovery.md index bed97441f9..f0953815c2 100644 --- a/docs/integrations/azure-blobStorage/discovery.md +++ b/docs/integrations/azure-blobStorage/discovery.md @@ -5,10 +5,6 @@ sidebar_label: Discovery description: Automatically discovering catalog entities from an Azure Blob Storage account --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). -::: - The Azure Blob Storage account integration has a special entity provider for discovering catalog entities located in a storage account container. If you have a container that contains multiple catalog files, and you want to automatically discover them, you can use this diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md index 7e4d376a7d..0eafc938a2 100644 --- a/docs/integrations/azure/discovery.md +++ b/docs/integrations/azure/discovery.md @@ -5,10 +5,6 @@ sidebar_label: Discovery description: Automatically discovering catalog entities from repositories in an Azure DevOps organization --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/azure/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - The Azure DevOps integration has a special entity provider for discovering catalog entities within an Azure DevOps. The provider will crawl your Azure DevOps organization and register entities matching the configured path. This can be useful as an alternative to static locations or manually adding things to the catalog. This guide explains how to install and configure the Azure DevOps Entity Provider (recommended) or the Azure DevOps Processor. diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md index efcd8ae612..92f3f9c12a 100644 --- a/docs/integrations/azure/org.md +++ b/docs/integrations/azure/org.md @@ -5,10 +5,6 @@ sidebar_label: Org Data description: Importing users and groups from Microsoft Entra ID into Backstage --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/azure/org--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - The Backstage catalog can be set up to ingest organizational data - users and teams - directly from a tenant in Microsoft Entra ID via the Microsoft Graph API. diff --git a/docs/integrations/bitbucketCloud/discovery.md b/docs/integrations/bitbucketCloud/discovery.md index 3f5920be5d..21b896e072 100644 --- a/docs/integrations/bitbucketCloud/discovery.md +++ b/docs/integrations/bitbucketCloud/discovery.md @@ -39,9 +39,9 @@ package. yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-bitbucket-cloud ``` -### Installation with New Backend System +Then add the following to your backend: -```ts +```ts title="packages/backend/src/index.ts" // optional if you want HTTP endpoints to receive external events // backend.add(import('@backstage/plugin-events-backend')); // optional if you want to use AWS SQS instead of HTTP endpoints to receive external events @@ -63,59 +63,6 @@ Further documentation: - - -### Installation with Legacy Backend System - -Please follow the installation instructions at - -- -- - -Additionally, you need to decide how you want to receive events from external sources like - -- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) -- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) -- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md) -- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md) - -Set up your provider - -```ts title="packages/backend/src/plugins/catalog.ts" -import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -/* highlight-add-start */ -import { BitbucketCloudEntityProvider } from '@backstage/plugin-catalog-backend-module-bitbucket-cloud'; -/* highlight-add-end */ - -import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /* highlight-add-start */ - const bitbucketCloudProvider = BitbucketCloudEntityProvider.fromConfig( - env.config, - { - auth: env.auth, - catalogApi: new CatalogClient({ discoveryApi: env.discovery }), - events: env.events, - logger: env.logger, - scheduler: env.scheduler, - }, - ); - builder.addEntityProvider(bitbucketCloudProvider); - /* highlight-add-end */ - const { processingEngine, router } = await builder.build(); - await processingEngine.start(); - return router; -} -``` - -**Attention:** -`catalogApi` and `tokenManager` are required at this variant -compared to the one without events support. - ## Configuration To use the entity provider, you'll need a [Bitbucket Cloud integration set up](locations.md). diff --git a/docs/integrations/bitbucketServer/discovery.md b/docs/integrations/bitbucketServer/discovery.md index bbdd105af4..ee96118f2a 100644 --- a/docs/integrations/bitbucketServer/discovery.md +++ b/docs/integrations/bitbucketServer/discovery.md @@ -5,10 +5,6 @@ sidebar_label: Discovery description: Automatically discovering catalog entities from repositories in Bitbucket Server --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/bitbucketServer/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - The Bitbucket Server integration has a special entity provider for discovering catalog files located in Bitbucket Server. The provider will search your Bitbucket Server account and register catalog files matching the configured path @@ -25,9 +21,9 @@ dependency to `@backstage/plugin-catalog-backend-module-bitbucket-server` to you yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-bitbucket-server ``` -### Installation with New Backend System +Then add the following to your backend: -```ts +```ts title="packages/backend/src/index.ts" // optional if you want HTTP endpojnts to receive external events // backend.add(import('@backstage/plugin-events-backend')); // optional if you want to use AWS SQS instead of HTTP endpoints to receive external events diff --git a/docs/integrations/gerrit/discovery.md b/docs/integrations/gerrit/discovery.md index 06fb144e50..a9ea80e088 100644 --- a/docs/integrations/gerrit/discovery.md +++ b/docs/integrations/gerrit/discovery.md @@ -5,10 +5,6 @@ sidebar_label: Discovery description: Automatically discovering catalog entities from Gerrit repositories --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/gerrit/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - The Gerrit integration has a special entity provider for discovering catalog entities from Gerrit repositories. The provider uses the "List Projects" API in Gerrit to get a list of repositories and will automatically ingest all `catalog-info.yaml` files diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index 66fdaa503b..c4dbc2e10b 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -5,10 +5,6 @@ sidebar_label: Discovery description: Automatically discovering catalog entities from repositories in a GitHub organization or App --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/github/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - ## GitHub Provider The GitHub integration has a discovery provider for discovering catalog diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 5321423432..1d5eac1581 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -5,10 +5,6 @@ sidebar_label: Org Data description: Importing users and groups from a GitHub organization into Backstage --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/github/org--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - The Backstage catalog can be set up to ingest organizational data - users and teams - directly from an organization in GitHub or GitHub Enterprise. The result is a hierarchy of diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index 4d801bdd77..315cc9a4a7 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -23,8 +23,6 @@ the gitlab catalog plugin: yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-gitlab ``` -### Installation with New Backend System - Then add the following to your backend initialization: ```ts title="packages/backend/src/index.ts" @@ -51,87 +49,6 @@ Further documentation: - [Events Plugin](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) - [GitLab Module for the Events Plugin](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-gitlab/README.md) -### Installation with Legacy Backend System (skip if you are using Backstage v1.31.0 or later) - -#### Installation without Events Support - -Add the segment below to `packages/backend/src/plugins/catalog.ts`: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { GitlabDiscoveryEntityProvider } from '@backstage/plugin-catalog-backend-module-gitlab'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /* highlight-add-start */ - builder.addEntityProvider( - ...GitlabDiscoveryEntityProvider.fromConfig(env.config, { - logger: env.logger, - // optional: alternatively, use scheduler with schedule defined in app-config.yaml - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, - timeout: { minutes: 3 }, - }), - // optional: alternatively, use schedule - scheduler: env.scheduler, - }), - ); - /* highlight-add-end */ - // .. -} -``` - -#### Installation with Events Support - -Please follow the installation instructions at - -- [Events Plugin](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) -- [GitLab Module for the Events Plugin](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-gitlab/README.md) - -Additionally, you need to decide how you want to receive events from external sources like - -- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) -- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) -- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md) -- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md) - -Set up your provider - -```ts title="packages/backend/src/plugins/catalog.ts" -import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -/* highlight-add-next-line */ -import { GitlabDiscoveryEntityProvider } from '@backstage/plugin-catalog-backend-module-gitlab'; -import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - builder.addProcessor(new ScaffolderEntitiesProcessor()); - /* highlight-add-start */ - const gitlabProvider = GitlabDiscoveryEntityProvider.fromConfig(env.config, { - logger: env.logger, - // optional: alternatively, use scheduler with schedule defined in app-config.yaml - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, - timeout: { minutes: 3 }, - }), - // optional: alternatively, use schedule - scheduler: env.scheduler, - events: env.events, - }); - builder.addEntityProvider(gitlabProvider); - /* highlight-add-end */ - const { processingEngine, router } = await builder.build(); - await processingEngine.start(); - return router; -} -``` - ## Configuration To use the discovery provider, you'll need a GitLab integration @@ -140,7 +57,7 @@ to the catalog configuration. :::note Note -If you are using the New Backend System, the `schedule` has to be setup in the config, as shown below. +The `schedule` has to be setup in the config, as shown below. ::: diff --git a/docs/integrations/gitlab/org.md b/docs/integrations/gitlab/org.md index bddd1c13a1..6c5641924d 100644 --- a/docs/integrations/gitlab/org.md +++ b/docs/integrations/gitlab/org.md @@ -29,8 +29,6 @@ As this provider is not one of the default providers, you will first need to ins yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-gitlab @backstage/plugin-catalog-backend-module-gitlab-org ``` -### Installation with New Backend System - Then add the following to your backend initialization: ```ts title="packages/backend/src/index.ts @@ -58,92 +56,6 @@ Further documentation: - [Events Plugin](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) - [GitLab Module for the Events Plugin](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-gitlab/README.md) -### Installation with Legacy Backend System - -#### Installation without Events Support - -Add the plugin to the plugin catalog `packages/backend/src/plugins/catalog.ts`: - -```ts -/* packages/backend/src/plugins/catalog.ts */ -/* highlight-add-next-line */ -import { GitlabOrgDiscoveryEntityProvider } from '@backstage/plugin-catalog-backend-module-gitlab'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /** ... other processors and/or providers ... */ - /* highlight-add-start */ - builder.addEntityProvider( - ...GitlabOrgDiscoveryEntityProvider.fromConfig(env.config, { - logger: env.logger, - // optional: alternatively, use scheduler with schedule defined in app-config.yaml - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, - timeout: { minutes: 3 }, - }), - // optional: alternatively, use schedule - scheduler: env.scheduler, - }), - ); - /* highlight-add-end */ - // .. -} -``` - -#### Installation with Events Support - -Please follow the installation instructions at - -- [Events Plugin](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) -- [GitLab Module for the Events Plugin](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-gitlab/README.md) - -Additionally, you need to decide how you want to receive events from external sources like - -- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) -- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) -- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md) -- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md) - -Set up your provider - -```ts title="packages/backend/src/plugins/catalog.ts" -import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -/* highlight-add-next-line */ -import { GitlabOrgDiscoveryEntityProvider } from '@backstage/plugin-catalog-backend-module-gitlab'; -import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - builder.addProcessor(new ScaffolderEntitiesProcessor()); - /* highlight-add-start */ - const gitlabOrgProvider = GitlabOrgDiscoveryEntityProvider.fromConfig( - env.config, - { - logger: env.logger, - // optional: alternatively, use scheduler with schedule defined in app-config.yaml - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, - timeout: { minutes: 3 }, - }), - // optional: alternatively, use schedule - scheduler: env.scheduler, - events: env.events, - }, - ); - builder.addEntityProvider(gitlabOrgProvider); - /* highlight-add-end */ - const { processingEngine, router } = await builder.build(); - await processingEngine.start(); - return router; -} -``` - ## Configuration To use the entity provider, you'll need a [Gitlab integration set up](https://backstage.io/docs/integrations/gitlab/locations). @@ -163,7 +75,7 @@ will be those visible to the account which provisioned the token. :::note Note -If you are using the New Backend System, the `schedule` has to be setup in the config, as shown below. +The `schedule` has to be setup in the config, as shown below. ::: diff --git a/docs/overview/threat-model.md b/docs/overview/threat-model.md index 6acba915d5..60f22a374c 100644 --- a/docs/overview/threat-model.md +++ b/docs/overview/threat-model.md @@ -25,7 +25,7 @@ An **external user** is a user that does not belong to the other three groups, f :::info This section assumes that you are using the -[new backend system](../backend-system/index.md) and at least Backstage release [version 1.24](../releases/v1.24.0.md). Before that Backstage did not come with built-in protection against unauthorized access and you were required to deploy it in a protected environment. +[backend system](../backend-system/index.md) and at least Backstage release [version 1.24](../releases/v1.24.0.md). Before that Backstage did not come with built-in protection against unauthorized access and you were required to deploy it in a protected environment. ::: Backstage is primarily designed to be deployed in a protected environment rather than being exposed to the public internet. From a confidentiality and integrity perspective, Backstage is designed to protect against unauthorized access to data and to ensure that data is not tampered with. However, Backstage does not provide more than rudimentary protection against denial of service attacks, and it is the responsibility of the operator to ensure that the Backstage deployment is protected against such attacks. A common and recommended way to protect a Backstage deployment from unauthorized access is to deploy it behind an authenticating proxy such as AWS’s ALB, GCP’s IAP, or Cloudflare Access. diff --git a/docs/permissions/custom-rules.md b/docs/permissions/custom-rules.md index b4ca86adf5..4b188ce7f9 100644 --- a/docs/permissions/custom-rules.md +++ b/docs/permissions/custom-rules.md @@ -4,10 +4,6 @@ title: Defining custom permission rules description: How to define custom permission rules for existing resources --- -:::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/custom-rules--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - For some use cases, you may want to define custom [rules](../references/glossary.md#rule-permission-plugin) in addition to the ones provided by a plugin. In the [previous section](./writing-a-policy.md) we used the `isEntityOwner` rule to control access for catalog entities. Let's extend this policy with a custom rule that checks what [system](https://backstage.io/docs/features/software-catalog/system-model#system) an entity is part of. ## Define a custom rule diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index f5b9092ec5..3e40b4597c 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -4,10 +4,6 @@ title: Getting Started description: How to get started with the permission framework as an integrator --- -:::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/getting-started--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others. ## Prerequisites diff --git a/docs/permissions/plugin-authors/01-setup.md b/docs/permissions/plugin-authors/01-setup.md index 743c5c9718..4ed348dae6 100644 --- a/docs/permissions/plugin-authors/01-setup.md +++ b/docs/permissions/plugin-authors/01-setup.md @@ -4,10 +4,6 @@ title: 1. Tutorial setup description: How to get started with the permission framework as a plugin author --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/01-setup--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - The following tutorial is designed to help plugin authors add support for permissions to their plugins. We'll add support for permissions to example `todo-list` and `todo-list-backend` plugins, but the process should be similar for other plugins! The rest of this page is focused on adding the `todo-list` and `todo-list-backend` plugins to your Backstage instance. If you want to add support for permissions to your own plugin instead, feel free to skip to the [next section](./02-adding-a-basic-permission-check.md). diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md index 8ba3ac3152..3790fc8577 100644 --- a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md +++ b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md @@ -4,10 +4,6 @@ title: 2. Adding a basic permission check description: Explains how to add a basic permission check to a Backstage plugin --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - If the outcome of a permission check doesn't need to change for different [resources](../../references/glossary.md#resource-permission-plugin), you can use a _basic permission check_. For this kind of check, we simply need to define a permission, and call `authorize` with it. For this tutorial, we'll use a basic permission check to authorize the `create` endpoint in our todo-backend. This will allow Backstage integrators to control whether each of their users is authorized to create todos by adjusting their [permission policy](../../references/glossary.md#policy-permission-plugin). diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md index afa56307ce..1e1a73607b 100644 --- a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md +++ b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md @@ -4,10 +4,6 @@ title: 3. Adding a resource permission check description: Explains how to add a resource permission check to a Backstage plugin --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - When performing updates (or other operations) on specific [resources](../../references/glossary.md#resource-permission-plugin), the permissions framework allows for the decision to be based on characteristics of the resource itself. This means that it's possible to write policies that (for example) allow the operation for users that own a resource, and deny the operation otherwise. ## Creating the update permission diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md index 9b79cec042..28ed56608c 100644 --- a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md +++ b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md @@ -4,10 +4,6 @@ title: 4. Authorizing access to paginated data description: Explains how to authorize access to paginated data in a Backstage plugin --- -:::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - Authorizing `GET /todos` is similar to the update endpoint, in that it should be possible to authorize access based on the characteristics of each resource. However, we'll need to authorize a list of resources for this endpoint. One possible solution may leverage the batching functionality to authorize all of the todos, and then returning only the ones for which the decision was `ALLOW`: diff --git a/docs/permissions/writing-a-policy.md b/docs/permissions/writing-a-policy.md index 2bf2bcade9..652c698be2 100644 --- a/docs/permissions/writing-a-policy.md +++ b/docs/permissions/writing-a-policy.md @@ -4,10 +4,6 @@ title: Writing a permission policy description: How to write your own permission policy as a Backstage integrator --- -:::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/writing-a-policy--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - In the [previous section](./getting-started.md), we were able to set up the permission framework and make a simple change to our `TestPermissionPolicy` to confirm that policy is indeed wired up correctly. That policy looked like this: diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index 9b8b63fc89..2872fdcda9 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -38,12 +38,6 @@ cd plugins/carmen-backend yarn start ``` -:::note Note - -This documentation assumes you are using the latest version of Backstage and the new backend system. If you are not, please upgrade and migrate your backend using the [Migration Guide](../backend-system/building-backends/08-migrating.md) - -::: - This will think for a bit, and then say `Listening on :7007`. In a different terminal window, now run diff --git a/docs/plugins/feature-flags.md b/docs/plugins/feature-flags.md index e9e000df0d..2d0e34ba4b 100644 --- a/docs/plugins/feature-flags.md +++ b/docs/plugins/feature-flags.md @@ -19,11 +19,18 @@ import { createPlugin } from '@backstage/core-plugin-api'; export const examplePlugin = createPlugin({ // ... - featureFlags: [{ name: 'show-example-feature' }], + featureFlags: [ + { + name: 'show-example-feature', + description: 'Enables the new beta dashboard view', + }, + ], // ... }); ``` +Note that the `description` property is optional. If not provided, the default "Registered in {pluginId} plugin" message is shown. + ### In the application Defining a feature flag in the application is done by adding feature flags in `featureFlags` array in the diff --git a/docs/plugins/integrating-search-into-plugins.md b/docs/plugins/integrating-search-into-plugins.md index fd80e0a1eb..7a7201d9e6 100644 --- a/docs/plugins/integrating-search-into-plugins.md +++ b/docs/plugins/integrating-search-into-plugins.md @@ -4,10 +4,6 @@ title: Integrating Search into a plugin description: How to integrate Search into a Backstage plugin --- -:::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/plugins/integrating-search-into-plugins--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - The Backstage Search Platform was designed to give plugin developers the APIs and interfaces needed to offer search experiences within their plugins, while abstracting away (and instead empowering application integrators to choose) the diff --git a/docs/tutorials/enable-public-entry.md b/docs/tutorials/enable-public-entry.md index e430768785..a1321dca05 100644 --- a/docs/tutorials/enable-public-entry.md +++ b/docs/tutorials/enable-public-entry.md @@ -107,10 +107,11 @@ If your app uses the new frontend system, you can still use the public entry poi ```tsx title="in packages/app/src/index-public-experimental.tsx" import ReactDOM from 'react-dom/client'; import { signInPageModule } from './overrides/SignInPage'; -import { createPublicSignInApp } from '@backstage/frontend-defaults'; +import { appModulePublicSignIn } from '@backstage/plugin-app/alpha'; +import { createApp } from '@backstage/frontend-defaults'; -const app = createPublicSignInApp({ - features: [signInPageModule], +const app = createApp({ + features: [signInPageModule, appModulePublicSignIn], }); ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot()); diff --git a/microsite/data/plugins/github-actions.yaml b/microsite/data/plugins/github-actions.yaml index 6b1af3fe36..55503f4fbb 100644 --- a/microsite/data/plugins/github-actions.yaml +++ b/microsite/data/plugins/github-actions.yaml @@ -4,7 +4,7 @@ author: Spotify authorUrl: https://github.com/spotify category: CI/CD description: GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. Build, test, and deploy your code right from GitHub. -documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/github-actions/plugins/github-actions +documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/github/plugins/github-actions iconUrl: https://avatars2.githubusercontent.com/u/44036562?s=400&v=4 npmPackageName: '@backstage-community/plugin-github-actions' addedDate: '2021-01-20' diff --git a/microsite/data/plugins/graphiql.yaml b/microsite/data/plugins/graphiql.yaml index 12c2f106a5..8d0a37cb65 100644 --- a/microsite/data/plugins/graphiql.yaml +++ b/microsite/data/plugins/graphiql.yaml @@ -5,7 +5,7 @@ authorUrl: https://github.com/spotify category: Debugging description: Integrates GraphiQL as a tool to browse GraphQL API endpoints inside Backstage. documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/graphiql/plugins/graphiql -iconUrl: https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/GraphQL_Logo.svg/1024px-GraphQL_Logo.svg.png +iconUrl: https://upload.wikimedia.org/wikipedia/commons/1/17/GraphQL_Logo.svg npmPackageName: '@backstage-community/plugin-graphiql' addedDate: '2020-11-03' status: active diff --git a/microsite/package.json b/microsite/package.json index 0da6c2c8be..51e1284668 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -20,8 +20,8 @@ }, "prettier": "@backstage/cli/config/prettier", "resolutions": { - "@swc/core": "1.15.3", - "@swc/html": "1.15.3", + "@swc/core": "1.15.18", + "@swc/html": "1.15.18", "node-polyfill-webpack-plugin": "^3.0.0" }, "dependencies": { diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 4cd9f95851..0935b07949 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -4053,90 +4053,90 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/core-darwin-arm64@npm:1.15.3" +"@swc/core-darwin-arm64@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-darwin-arm64@npm:1.15.18" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/core-darwin-x64@npm:1.15.3" +"@swc/core-darwin-x64@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-darwin-x64@npm:1.15.18" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.3" +"@swc/core-linux-arm-gnueabihf@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.18" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/core-linux-arm64-gnu@npm:1.15.3" +"@swc/core-linux-arm64-gnu@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-arm64-gnu@npm:1.15.18" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/core-linux-arm64-musl@npm:1.15.3" +"@swc/core-linux-arm64-musl@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-arm64-musl@npm:1.15.18" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/core-linux-x64-gnu@npm:1.15.3" +"@swc/core-linux-x64-gnu@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-x64-gnu@npm:1.15.18" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/core-linux-x64-musl@npm:1.15.3" +"@swc/core-linux-x64-musl@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-x64-musl@npm:1.15.18" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/core-win32-arm64-msvc@npm:1.15.3" +"@swc/core-win32-arm64-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-win32-arm64-msvc@npm:1.15.18" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/core-win32-ia32-msvc@npm:1.15.3" +"@swc/core-win32-ia32-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-win32-ia32-msvc@npm:1.15.18" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/core-win32-x64-msvc@npm:1.15.3" +"@swc/core-win32-x64-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-win32-x64-msvc@npm:1.15.18" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@swc/core@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/core@npm:1.15.3" +"@swc/core@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core@npm:1.15.18" dependencies: - "@swc/core-darwin-arm64": "npm:1.15.3" - "@swc/core-darwin-x64": "npm:1.15.3" - "@swc/core-linux-arm-gnueabihf": "npm:1.15.3" - "@swc/core-linux-arm64-gnu": "npm:1.15.3" - "@swc/core-linux-arm64-musl": "npm:1.15.3" - "@swc/core-linux-x64-gnu": "npm:1.15.3" - "@swc/core-linux-x64-musl": "npm:1.15.3" - "@swc/core-win32-arm64-msvc": "npm:1.15.3" - "@swc/core-win32-ia32-msvc": "npm:1.15.3" - "@swc/core-win32-x64-msvc": "npm:1.15.3" + "@swc/core-darwin-arm64": "npm:1.15.18" + "@swc/core-darwin-x64": "npm:1.15.18" + "@swc/core-linux-arm-gnueabihf": "npm:1.15.18" + "@swc/core-linux-arm64-gnu": "npm:1.15.18" + "@swc/core-linux-arm64-musl": "npm:1.15.18" + "@swc/core-linux-x64-gnu": "npm:1.15.18" + "@swc/core-linux-x64-musl": "npm:1.15.18" + "@swc/core-win32-arm64-msvc": "npm:1.15.18" + "@swc/core-win32-ia32-msvc": "npm:1.15.18" + "@swc/core-win32-x64-msvc": "npm:1.15.18" "@swc/counter": "npm:^0.1.3" "@swc/types": "npm:^0.1.25" peerDependencies: @@ -4165,7 +4165,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/280330d82328818138ed64fdcf9ea9abde6b6f16eca65a9d4db27dde06a8dfffd2649f3447d2243387277513c7430fa4142cafcfd64e943d682ce6a713cb8c2d + checksum: 10/ef198b9feb6eee034e3a912c37988ece2885fec35419e8245d467adbc1fc47a5c3e61869d1bdbe6fff76cbd9186ef278120cbb9746d5f7446576f4a7f15c2dcd languageName: node linkType: hard @@ -4176,91 +4176,91 @@ __metadata: languageName: node linkType: hard -"@swc/html-darwin-arm64@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/html-darwin-arm64@npm:1.15.3" +"@swc/html-darwin-arm64@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-darwin-arm64@npm:1.15.18" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/html-darwin-x64@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/html-darwin-x64@npm:1.15.3" +"@swc/html-darwin-x64@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-darwin-x64@npm:1.15.18" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/html-linux-arm-gnueabihf@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/html-linux-arm-gnueabihf@npm:1.15.3" +"@swc/html-linux-arm-gnueabihf@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-linux-arm-gnueabihf@npm:1.15.18" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/html-linux-arm64-gnu@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/html-linux-arm64-gnu@npm:1.15.3" +"@swc/html-linux-arm64-gnu@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-linux-arm64-gnu@npm:1.15.18" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/html-linux-arm64-musl@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/html-linux-arm64-musl@npm:1.15.3" +"@swc/html-linux-arm64-musl@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-linux-arm64-musl@npm:1.15.18" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/html-linux-x64-gnu@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/html-linux-x64-gnu@npm:1.15.3" +"@swc/html-linux-x64-gnu@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-linux-x64-gnu@npm:1.15.18" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/html-linux-x64-musl@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/html-linux-x64-musl@npm:1.15.3" +"@swc/html-linux-x64-musl@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-linux-x64-musl@npm:1.15.18" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/html-win32-arm64-msvc@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/html-win32-arm64-msvc@npm:1.15.3" +"@swc/html-win32-arm64-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-win32-arm64-msvc@npm:1.15.18" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/html-win32-ia32-msvc@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/html-win32-ia32-msvc@npm:1.15.3" +"@swc/html-win32-ia32-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-win32-ia32-msvc@npm:1.15.18" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/html-win32-x64-msvc@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/html-win32-x64-msvc@npm:1.15.3" +"@swc/html-win32-x64-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html-win32-x64-msvc@npm:1.15.18" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@swc/html@npm:1.15.3": - version: 1.15.3 - resolution: "@swc/html@npm:1.15.3" +"@swc/html@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/html@npm:1.15.18" dependencies: "@swc/counter": "npm:^0.1.3" - "@swc/html-darwin-arm64": "npm:1.15.3" - "@swc/html-darwin-x64": "npm:1.15.3" - "@swc/html-linux-arm-gnueabihf": "npm:1.15.3" - "@swc/html-linux-arm64-gnu": "npm:1.15.3" - "@swc/html-linux-arm64-musl": "npm:1.15.3" - "@swc/html-linux-x64-gnu": "npm:1.15.3" - "@swc/html-linux-x64-musl": "npm:1.15.3" - "@swc/html-win32-arm64-msvc": "npm:1.15.3" - "@swc/html-win32-ia32-msvc": "npm:1.15.3" - "@swc/html-win32-x64-msvc": "npm:1.15.3" + "@swc/html-darwin-arm64": "npm:1.15.18" + "@swc/html-darwin-x64": "npm:1.15.18" + "@swc/html-linux-arm-gnueabihf": "npm:1.15.18" + "@swc/html-linux-arm64-gnu": "npm:1.15.18" + "@swc/html-linux-arm64-musl": "npm:1.15.18" + "@swc/html-linux-x64-gnu": "npm:1.15.18" + "@swc/html-linux-x64-musl": "npm:1.15.18" + "@swc/html-win32-arm64-msvc": "npm:1.15.18" + "@swc/html-win32-ia32-msvc": "npm:1.15.18" + "@swc/html-win32-x64-msvc": "npm:1.15.18" dependenciesMeta: "@swc/html-darwin-arm64": optional: true @@ -4282,7 +4282,7 @@ __metadata: optional: true "@swc/html-win32-x64-msvc": optional: true - checksum: 10/753ddec5a5fb53a5245b4e5ab990c8dc9e6aff2c3da99ab671be2535d3fc566953dac4aa30c9a435e78a80901384fbc8f43c603c379abe2d21a11303dd804393 + checksum: 10/5fdef26fa88693c2913d2ee147332ab366bc6d41726ea2b57db31d3992fbf47aa5b881537f32a077535a64f896a7f7af8f0092343d74256fd8c290431e1a8407 languageName: node linkType: hard @@ -4311,13 +4311,6 @@ __metadata: languageName: node linkType: hard -"@trysound/sax@npm:0.2.0": - version: 0.2.0 - resolution: "@trysound/sax@npm:0.2.0" - checksum: 10/7379713eca480ac0d9b6c7b063e06b00a7eac57092354556c81027066eb65b61ea141a69d0cc2e15d32e05b2834d4c9c2184793a5e36bbf5daf05ee5676af18c - languageName: node - linkType: hard - "@tybys/wasm-util@npm:^0.10.1": version: 0.10.1 resolution: "@tybys/wasm-util@npm:0.10.1" @@ -8555,9 +8548,9 @@ __metadata: linkType: hard "immutable@npm:^5.0.2": - version: 5.0.2 - resolution: "immutable@npm:5.0.2" - checksum: 10/89b1117c610024b7a9214eade8b9f1ed38b00c82235f119515cfa5eaf26270eccbc803296d4c3c12f53e50802f042f84d811998910b866363913720da768472e + version: 5.1.5 + resolution: "immutable@npm:5.1.5" + checksum: 10/7aec2740239772ec8e92e793c991bd809203a97694f4ff3a18e50e28f9a6b02393ad033d87b458037bdf8c0ea37d4446d640e825f6171df3405cf6cf300ce028 languageName: node linkType: hard @@ -13312,10 +13305,10 @@ __metadata: languageName: node linkType: hard -"sax@npm:^1.2.4": - version: 1.2.4 - resolution: "sax@npm:1.2.4" - checksum: 10/09b79ff6dc09689a24323352117c94593c69db348997b2af0edbd82fa08aba47d778055bf9616b57285bb73d25d790900c044bf631a8f10c8252412e3f3fe5dd +"sax@npm:^1.2.4, sax@npm:^1.5.0": + version: 1.5.0 + resolution: "sax@npm:1.5.0" + checksum: 10/9012ff37dda7a7ac5da45db2143b04036103e8bef8d586c3023afd5df6caf0ebd7f38017eee344ad2e2247eded7d38e9c42cf291d8dd91781352900ac0fd2d9f languageName: node linkType: hard @@ -14132,19 +14125,19 @@ __metadata: linkType: hard "svgo@npm:^3.0.2, svgo@npm:^3.2.0": - version: 3.3.2 - resolution: "svgo@npm:3.3.2" + version: 3.3.3 + resolution: "svgo@npm:3.3.3" dependencies: - "@trysound/sax": "npm:0.2.0" commander: "npm:^7.2.0" css-select: "npm:^5.1.0" css-tree: "npm:^2.3.1" css-what: "npm:^6.1.0" csso: "npm:^5.0.5" picocolors: "npm:^1.0.0" + sax: "npm:^1.5.0" bin: svgo: ./bin/svgo - checksum: 10/82fdea9b938884d808506104228e4d3af0050d643d5b46ff7abc903ff47a91bbf6561373394868aaf07a28f006c4057b8fbf14bbd666298abdd7cc590d4f7700 + checksum: 10/f3c1b4d05d1704483e53515d5995af5f06a2718df85e3a8320f57bb256b8dc926b84c87a1a9b98e9d3ca1224314cc0676a803bdd03163508292f2d45c7077096 languageName: node linkType: hard diff --git a/package.json b/package.json index ac79d5976c..d356805b05 100644 --- a/package.json +++ b/package.json @@ -138,7 +138,7 @@ "@storybook/addon-a11y": "^10.3.0-alpha.1", "@storybook/addon-docs": "^10.3.0-alpha.1", "@storybook/addon-links": "^10.3.0-alpha.1", - "@storybook/addon-mcp": "^0.2.2", + "@storybook/addon-mcp": "^0.3.0", "@storybook/addon-themes": "^10.3.0-alpha.1", "@storybook/addon-vitest": "^10.3.0-alpha.1", "@storybook/react-vite": "^10.3.0-alpha.1", diff --git a/packages/app-example-plugin/report.api.md b/packages/app-example-plugin/report.api.md index 70b3446a2b..432174da68 100644 --- a/packages/app-example-plugin/report.api.md +++ b/packages/app-example-plugin/report.api.md @@ -87,7 +87,6 @@ const examplePlugin: OverridableFrontendPlugin< >; }; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; diff --git a/packages/app-legacy/src/components/catalog/EntityPage.tsx b/packages/app-legacy/src/components/catalog/EntityPage.tsx index 32249cb84a..7c42b62542 100644 --- a/packages/app-legacy/src/components/catalog/EntityPage.tsx +++ b/packages/app-legacy/src/components/catalog/EntityPage.tsx @@ -170,7 +170,7 @@ const overviewContent = ( - + @@ -299,7 +299,7 @@ const apiPage = ( - + @@ -333,10 +333,7 @@ const userPage = ( - + @@ -352,10 +349,7 @@ const groupPage = ( - + @@ -377,7 +371,7 @@ const systemPage = ( - + @@ -392,7 +386,6 @@ const systemPage = ( - + @@ -440,7 +433,7 @@ const resourcePage = ( - + diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 4aaf0408b4..95df1407ad 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -164,7 +164,7 @@ "cron": "^3.0.0", "express": "^4.22.0", "express-promise-router": "^4.1.0", - "express-rate-limit": "^7.5.0", + "express-rate-limit": "^8.2.2", "fs-extra": "^11.2.0", "git-url-parse": "^15.0.0", "helmet": "^6.0.0", diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.test.ts index 14fedfb3c0..04c2b17ad3 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.test.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.test.ts @@ -16,6 +16,7 @@ import { LocalTaskWorker } from './LocalTaskWorker'; import { mockServices } from '@backstage/backend-test-utils'; +import { ConflictError } from '@backstage/errors'; import waitFor from 'wait-for-expect'; jest.setTimeout(10_000); @@ -110,6 +111,54 @@ describe('LocalTaskWorker', () => { controller.abort(); }); + it('can cancel a running task', async () => { + let receivedSignal: AbortSignal | undefined; + const fn = jest.fn(async (signal: AbortSignal) => { + receivedSignal = signal; + await new Promise(r => setTimeout(r, 5000)); + }); + const controller = new AbortController(); + + const worker = new LocalTaskWorker('a', fn, logger); + worker.start( + { + version: 2, + cadence: 'PT10S', + timeoutAfterDuration: 'PT10S', + }, + { signal: controller.signal }, + ); + + await waitFor(() => { + expect(fn).toHaveBeenCalledTimes(1); + }); + + expect(receivedSignal?.aborted).toBe(false); + worker.cancel(); + expect(receivedSignal?.aborted).toBe(true); + + controller.abort(); + }); + + it('cannot cancel a task that is not running', async () => { + const fn = jest.fn(); + const controller = new AbortController(); + + const worker = new LocalTaskWorker('a', fn, logger); + worker.start( + { + version: 2, + initialDelayDuration: 'PT1000S', + cadence: 'PT10S', + timeoutAfterDuration: 'PT10S', + }, + { signal: controller.signal }, + ); + + expect(() => worker.cancel()).toThrow(ConflictError); + controller.abort(); + }); + it('goes through the expected states', async () => { const fn = jest .fn() diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.ts index efecb1c0ed..168649436c 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.ts @@ -29,6 +29,7 @@ import { delegateAbortController, serializeError, sleep } from './util'; */ export class LocalTaskWorker { private abortWait: AbortController | undefined; + private taskAbortController: AbortController | undefined; #taskState: Exclude = { status: 'idle', }; @@ -93,6 +94,13 @@ export class LocalTaskWorker { this.abortWait.abort(); } + cancel(): void { + if (!this.taskAbortController) { + throw new ConflictError(`Task ${this.taskId} is not running`); + } + this.taskAbortController.abort(); + } + taskState(): TaskApiTasksResponse['taskState'] { return this.#taskState; } @@ -134,10 +142,10 @@ export class LocalTaskWorker { ): Promise { // Abort the task execution either if the worker is stopped, or if the // task timeout is hit - const taskAbortController = delegateAbortController(signal); + this.taskAbortController = delegateAbortController(signal); const timeoutDuration = Duration.fromISO(settings.timeoutAfterDuration); const timeoutHandle = setTimeout(() => { - taskAbortController.abort(); + this.taskAbortController?.abort(); }, timeoutDuration.as('milliseconds')); this.#taskState = { @@ -152,7 +160,7 @@ export class LocalTaskWorker { }; try { - await this.fn(taskAbortController.signal); + await this.fn(this.taskAbortController.signal); this.#taskState.lastRunEndedAt = DateTime.utc().toISO()!; this.#taskState.lastRunError = undefined; } catch (e) { @@ -162,7 +170,8 @@ export class LocalTaskWorker { // release resources clearTimeout(timeoutHandle); - taskAbortController.abort(); + this.taskAbortController.abort(); + this.taskAbortController = undefined; } /** diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts index 2148148c9e..cc8aac5f51 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts @@ -399,6 +399,100 @@ describe('PluginTaskManagerImpl', () => { ); }); + describe('cancelTask with local scope', () => { + it('can cancel a running task', async () => { + const { manager } = await init('SQLITE_3'); + + const promise = createDeferred(); + + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromObject({ years: 1 }), + fn: async () => { + promise.resolve(); + await new Promise(r => setTimeout(r, 20000)); + }, + scope: 'local', + }); + + await promise; + await expect(manager.cancelTask('task1')).resolves.toBeUndefined(); + }, 60_000); + + it('cannot cancel a task that is not running', async () => { + const { manager } = await init('SQLITE_3'); + + const fn = jest.fn(); + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromObject({ years: 1 }), + initialDelay: Duration.fromObject({ years: 1 }), + fn, + scope: 'local', + }); + + await expect(manager.cancelTask('task1')).rejects.toThrow(ConflictError); + }, 60_000); + }); + + describe('cancelTask with global scope', () => { + it.each(databases.eachSupportedId())( + 'can cancel a running task, %p', + async databaseId => { + const { manager } = await init(databaseId); + + const promise = createDeferred(); + + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromObject({ years: 1 }), + fn: async () => { + promise.resolve(); + await new Promise(r => setTimeout(r, 20000)); + }, + scope: 'global', + }); + + await promise; + await expect(manager.cancelTask('task1')).resolves.toBeUndefined(); + }, + ); + + it.each(databases.eachSupportedId())( + 'cannot cancel a non-existent task, %p', + async databaseId => { + const { manager } = await init(databaseId); + + await expect(manager.cancelTask('nonexistent')).rejects.toThrow( + NotFoundError, + ); + }, + ); + + it.each(databases.eachSupportedId())( + 'cannot cancel a task that is not running, %p', + async databaseId => { + const { manager } = await init(databaseId); + + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromObject({ years: 1 }), + initialDelay: Duration.fromObject({ years: 1 }), + fn: jest.fn(), + scope: 'global', + }); + + await expect(manager.cancelTask('task1')).rejects.toThrow( + ConflictError, + ); + }, + ); + }); + describe('parseDuration', () => { it('should parse durations', () => { expect(parseDuration({ milliseconds: 5000 })).toEqual('PT5S'); diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts index e711744123..2aad8647fe 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts @@ -107,6 +107,17 @@ export class PluginTaskSchedulerImpl implements SchedulerService { await TaskWorker.trigger(knex, id); } + async cancelTask(id: string): Promise { + const localTask = this.localWorkersById.get(id); + if (localTask) { + localTask.cancel(); + return; + } + + const knex = await this.databaseFactory(); + await TaskWorker.cancel(knex, id); + } + async scheduleTask( task: SchedulerServiceTaskScheduleDefinition & SchedulerServiceTaskInvocationDefinition, @@ -206,6 +217,15 @@ export class PluginTaskSchedulerImpl implements SchedulerService { }, ); + router.post( + '/.backstage/scheduler/v1/tasks/:id/cancel', + async (req, res) => { + const { id } = req.params; + await this.cancelTask(id); + res.status(200).end(); + }, + ); + return router; } diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts index 5bd3fb38cb..3446d635d1 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts @@ -15,6 +15,7 @@ */ import { TestDatabases, mockServices } from '@backstage/backend-test-utils'; +import { ConflictError, NotFoundError } from '@backstage/errors'; import { DateTime, Duration } from 'luxon'; import waitForExpect from 'wait-for-expect'; import { migrateBackendTasks } from '../database/migrateBackendTasks'; @@ -584,4 +585,79 @@ describe('TaskWorker', () => { await knex.destroy(); }, ); + + it.each(databases.eachSupportedId())( + 'can cancel a running task, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const fn = jest.fn(async () => {}); + const settings: TaskSettingsV2 = { + version: 2, + cadence: '* * * * * *', + initialDelayDuration: undefined, + timeoutAfterDuration: Duration.fromObject({ minutes: 1 }).toISO()!, + }; + + const worker = new TaskWorker('task1', fn, knex, logger); + await worker.persistTask(settings); + await worker.tryClaimTask('ticket', settings); + + // Verify the task is running + let row = (await knex(DB_TASKS_TABLE))[0]; + expect(row.current_run_ticket).toBe('ticket'); + + await TaskWorker.cancel(knex, 'task1'); + + // Verify the task is now idle with a cancellation error recorded + row = (await knex(DB_TASKS_TABLE))[0]; + expect(row.current_run_ticket).toBeNull(); + expect(row.current_run_started_at).toBeNull(); + expect(row.current_run_expires_at).toBeNull(); + expect(row.last_run_ended_at).not.toBeNull(); + expect(row.last_run_error_json).toContain('Task was cancelled'); + + await knex.destroy(); + }, + ); + + it.each(databases.eachSupportedId())( + 'cannot cancel a non-existent task, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + await expect(TaskWorker.cancel(knex, 'nonexistent')).rejects.toThrow( + NotFoundError, + ); + + await knex.destroy(); + }, + ); + + it.each(databases.eachSupportedId())( + 'cannot cancel a task that is not running, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const fn = jest.fn(async () => {}); + const settings: TaskSettingsV2 = { + version: 2, + cadence: '* * * * * *', + initialDelayDuration: undefined, + timeoutAfterDuration: Duration.fromObject({ minutes: 1 }).toISO()!, + }; + + const worker = new TaskWorker('task1', fn, knex, logger); + await worker.persistTask(settings); + + await expect(TaskWorker.cancel(knex, 'task1')).rejects.toThrow( + ConflictError, + ); + + await knex.destroy(); + }, + ); }); diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts index e47967de90..4f32dc6863 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts @@ -152,6 +152,36 @@ export class TaskWorker { } } + static async cancel(knex: Knex, taskId: string): Promise { + const [row] = await knex(DB_TASKS_TABLE) + .where('id', '=', taskId) + .select('settings_json', 'current_run_ticket'); + if (!row) { + throw new NotFoundError(`Task ${taskId} does not exist`); + } + if (!row.current_run_ticket) { + throw new ConflictError(`Task ${taskId} is not running`); + } + + const settings = taskSettingsV2Schema.parse(JSON.parse(row.settings_json)); + const nextRun = TaskWorker.computeNextRunStartAt(knex, settings); + + const updatedRows = await knex(DB_TASKS_TABLE) + .where('id', '=', taskId) + .where('current_run_ticket', '=', row.current_run_ticket) + .update({ + next_run_start_at: nextRun, + current_run_ticket: knex.raw('null'), + current_run_started_at: knex.raw('null'), + current_run_expires_at: knex.raw('null'), + last_run_ended_at: knex.fn.now(), + last_run_error_json: serializeError(new Error('Task was cancelled')), + }); + if (updatedRows < 1) { + throw new ConflictError(`Task ${taskId} is not running`); + } + } + static async taskStates( knex: Knex, ): Promise> { @@ -227,11 +257,22 @@ export class TaskWorker { } // Abort the task execution either if the worker is stopped, or if the - // task timeout is hit + // task timeout is hit, or if the task ticket was lost (e.g. due to + // cancellation from another host) const taskAbortController = delegateAbortController(signal); const timeoutHandle = setTimeout(() => { taskAbortController.abort(); }, Duration.fromISO(taskSettings.timeoutAfterDuration).as('milliseconds')); + let livenessHandle: ReturnType | undefined; + const scheduleLivenessCheck = () => { + livenessHandle = setTimeout(async () => { + await this.checkLiveness(ticket, taskAbortController); + if (!taskAbortController.signal.aborted) { + scheduleLivenessCheck(); + } + }, this.workCheckFrequency.as('milliseconds')); + }; + scheduleLivenessCheck(); try { this.#workerState = { @@ -248,6 +289,7 @@ export class TaskWorker { status: 'idle', }; clearTimeout(timeoutHandle); + clearTimeout(livenessHandle); } await this.tryReleaseTask(ticket, taskSettings); @@ -283,7 +325,7 @@ export class TaskWorker { // We make a conversion here to make typescript happy, because the luxon versions of the cron library and here may not be the same const timeConverted = DateTime.fromJSDate(time.toJSDate()); - nextStartAt = this.nextRunAtRaw(timeConverted); + nextStartAt = TaskWorker.nextRunAtRaw(this.knex, timeConverted); startAt ||= nextStartAt; } else if (isManual) { nextStartAt = this.knex.raw('null'); @@ -334,6 +376,33 @@ export class TaskWorker { ); } + /** + * Checks whether the current task ticket is still valid in the database. + * If the ticket has been cleared (e.g. by cancellation or janitor cleanup), + * aborts the task execution. + */ + private async checkLiveness( + ticket: string, + taskAbortController: AbortController, + ): Promise { + try { + const [row] = await this.knex(DB_TASKS_TABLE) + .where('id', '=', this.taskId) + .select('current_run_ticket'); + + if (!row || row.current_run_ticket !== ticket) { + this.logger.info( + `Task ticket for "${this.taskId}" is no longer valid; aborting execution`, + ); + taskAbortController.abort(); + } + } catch (e) { + this.logger.warn( + `Failed to check liveness for task "${this.taskId}", ${e}`, + ); + } + } + /** * Check if the task is ready to run */ @@ -407,48 +476,49 @@ export class TaskWorker { return rows === 1; } + private static computeNextRunStartAt( + knex: Knex, + settings: TaskSettingsV2, + ): Knex.Raw { + const isManual = settings?.cadence === 'manual'; + const isDuration = settings?.cadence.startsWith('P'); + const isCron = !isManual && !isDuration; + + if (isCron) { + const time = new CronTime(settings.cadence).sendAt().toUTC(); + const timeConverted = DateTime.fromJSDate(time.toJSDate()); + return TaskWorker.nextRunAtRaw(knex, timeConverted); + } + + if (isManual) { + return knex.raw('null'); + } + + const dt = Duration.fromISO(settings.cadence).as('seconds'); + + if (knex.client.config.client.includes('sqlite3')) { + return knex.raw(`max(datetime(next_run_start_at, ?), datetime('now'))`, [ + `+${dt} seconds`, + ]); + } + + if (knex.client.config.client.includes('mysql')) { + return knex.raw( + `greatest(next_run_start_at + interval ${dt} second, now())`, + ); + } + + return knex.raw( + `greatest(next_run_start_at + interval '${dt} seconds', now())`, + ); + } + async tryReleaseTask( ticket: string, settings: TaskSettingsV2, error?: Error, ): Promise { - const isManual = settings?.cadence === 'manual'; - const isDuration = settings?.cadence.startsWith('P'); - const isCron = !isManual && !isDuration; - - let nextRun: Knex.Raw; - if (isCron) { - const time = new CronTime(settings.cadence).sendAt().toUTC(); - this.logger.debug(`task: ${this.taskId} will next occur around ${time}`); - // We make a conversion here to make typescript happy, because the luxon versions of the cron library and here may not be the same - const timeConverted = DateTime.fromJSDate(time.toJSDate()); - - nextRun = this.nextRunAtRaw(timeConverted); - } else if (isManual) { - nextRun = this.knex.raw('null'); - } else { - const dt = Duration.fromISO(settings.cadence).as('seconds'); - this.logger.debug( - `task: ${this.taskId} will next occur around ${DateTime.now().plus({ - seconds: dt, - })}`, - ); - - if (this.knex.client.config.client.includes('sqlite3')) { - nextRun = this.knex.raw( - `max(datetime(next_run_start_at, ?), datetime('now'))`, - [`+${dt} seconds`], - ); - } else if (this.knex.client.config.client.includes('mysql')) { - nextRun = this.knex.raw( - `greatest(next_run_start_at + interval ${dt} second, now())`, - ); - } else { - nextRun = this.knex.raw( - `greatest(next_run_start_at + interval '${dt} seconds', now())`, - ); - } - } + const nextRun = TaskWorker.computeNextRunStartAt(this.knex, settings); const rows = await this.knex(DB_TASKS_TABLE) .where('id', '=', this.taskId) @@ -467,12 +537,13 @@ export class TaskWorker { return rows === 1; } - private nextRunAtRaw(time: DateTime): Knex.Raw { - if (this.knex.client.config.client.includes('sqlite3')) { - return this.knex.raw('datetime(?)', [time.toISO()]); - } else if (this.knex.client.config.client.includes('mysql')) { - return this.knex.raw(`?`, [time.toSQL({ includeOffset: false })]); + private static nextRunAtRaw(knex: Knex, time: DateTime): Knex.Raw { + if (knex.client.config.client.includes('sqlite3')) { + return knex.raw('datetime(?)', [time.toISO()]); } - return this.knex.raw(`?`, [time.toISO()]); + if (knex.client.config.client.includes('mysql')) { + return knex.raw(`?`, [time.toSQL({ includeOffset: false })]); + } + return knex.raw(`?`, [time.toISO()]); } } diff --git a/packages/backend-plugin-api/report.api.md b/packages/backend-plugin-api/report.api.md index 504be97ec6..96d1a02c0a 100644 --- a/packages/backend-plugin-api/report.api.md +++ b/packages/backend-plugin-api/report.api.md @@ -643,6 +643,7 @@ export interface RootServiceFactoryOptions< // @public export interface SchedulerService { + cancelTask(id: string): Promise; createScheduledTaskRunner( schedule: SchedulerServiceTaskScheduleDefinition, ): SchedulerServiceTaskRunner; diff --git a/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts b/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts index b1a0e17df8..2748b8fac7 100644 --- a/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts +++ b/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts @@ -304,6 +304,16 @@ export interface SchedulerService { */ triggerTask(id: string): Promise; + /** + * Cancels a currently running task by ID, marking it as idle. + * + * If the task doesn't exist, a NotFoundError is thrown. If the task is + * not currently running, a ConflictError is thrown. + * + * @param id - The task ID + */ + cancelTask(id: string): Promise; + /** * Schedules a task function for recurring runs. * diff --git a/packages/backend-test-utils/src/services/MockSchedulerService.test.ts b/packages/backend-test-utils/src/services/MockSchedulerService.test.ts index faf4e3e26d..2668fa7205 100644 --- a/packages/backend-test-utils/src/services/MockSchedulerService.test.ts +++ b/packages/backend-test-utils/src/services/MockSchedulerService.test.ts @@ -206,6 +206,68 @@ describe('MockSchedulerService', () => { await expect(isDone()).resolves.toBe(true); }); + it('should cancel a running task and allow re-triggering with a fresh signal', async () => { + const scheduler = new MockSchedulerService(); + const signals: AbortSignal[] = []; + + scheduler.scheduleTask({ + ...baseOpts, + id: 'test', + fn: async signal => { + signals.push(signal); + // Simulate long-running work that respects cancellation + await new Promise((resolve, reject) => { + if (signal.aborted) { + reject(new Error('aborted')); + return; + } + signal.addEventListener('abort', () => reject(new Error('aborted'))); + setTimeout(1).then(resolve); + }); + }, + }); + + // First run completes normally + await scheduler.triggerTask('test'); + expect(signals).toHaveLength(1); + expect(signals[0].aborted).toBe(false); + + // Start a task that will block until cancelled + const blockingScheduler = new MockSchedulerService(); + let resolveBlock: (() => void) | undefined; + blockingScheduler.scheduleTask({ + ...baseOpts, + id: 'blocking', + fn: async signal => { + signals.push(signal); + await new Promise((resolve, reject) => { + signal.addEventListener('abort', () => reject(new Error('aborted'))); + resolveBlock = resolve; + }); + }, + }); + + const triggerPromise = blockingScheduler.triggerTask('blocking'); + // Give the task fn time to start + await setTimeout(1); + + await blockingScheduler.cancelTask('blocking'); + await triggerPromise.catch(() => {}); + + expect(signals).toHaveLength(2); + expect(signals[1].aborted).toBe(true); + + // Re-trigger should get a fresh non-aborted signal + resolveBlock = undefined; + const triggerPromise2 = blockingScheduler.triggerTask('blocking'); + await setTimeout(1); + resolveBlock!(); + await triggerPromise2; + + expect(signals).toHaveLength(3); + expect(signals[2].aborted).toBe(false); + }); + it('should abort tasks when shutting down', async () => { let taskSignal: AbortSignal | undefined; diff --git a/packages/backend-test-utils/src/services/MockSchedulerService.ts b/packages/backend-test-utils/src/services/MockSchedulerService.ts index aafd6d98ec..3309ebc954 100644 --- a/packages/backend-test-utils/src/services/MockSchedulerService.ts +++ b/packages/backend-test-utils/src/services/MockSchedulerService.ts @@ -23,6 +23,7 @@ import { SchedulerServiceTaskRunner, SchedulerServiceTaskScheduleDefinition, } from '@backstage/backend-plugin-api'; +import { ConflictError, NotFoundError } from '@backstage/errors'; import { createDeferred, DeferredPromise } from '@backstage/types'; export class MockSchedulerService implements SchedulerService { @@ -95,10 +96,22 @@ export class MockSchedulerService implements SchedulerService { }); } + async cancelTask(id: string): Promise { + const task = this.#tasks.get(id); + if (!task) { + throw new NotFoundError(`Task ${id} not found`); + } + if (!this.#runningTasks.has(id)) { + throw new ConflictError(`Task ${id} is not running`); + } + task.abortControllers.abort(); + task.abortControllers = new AbortController(); + } + async triggerTask(id: string): Promise { const task = this.#tasks.get(id); if (!task) { - throw new Error(`Task ${id} not found`); + throw new NotFoundError(`Task ${id} not found`); } if (this.#runningTasks.has(id)) { return; diff --git a/packages/backend-test-utils/src/services/mockServices.ts b/packages/backend-test-utils/src/services/mockServices.ts index 84783f04d7..4d54269b98 100644 --- a/packages/backend-test-utils/src/services/mockServices.ts +++ b/packages/backend-test-utils/src/services/mockServices.ts @@ -526,6 +526,7 @@ export namespace mockServices { getScheduledTasks: jest.fn(), scheduleTask: jest.fn(), triggerTask: jest.fn(), + cancelTask: jest.fn(), })); } diff --git a/packages/backend/package.json b/packages/backend/package.json index 01de1d1626..07ccc2327d 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -66,7 +66,7 @@ "@backstage/plugin-search-backend-node": "workspace:^", "@backstage/plugin-signals-backend": "workspace:^", "@backstage/plugin-techdocs-backend": "workspace:^", - "@opentelemetry/auto-instrumentations-node": "^0.67.0", + "@opentelemetry/auto-instrumentations-node": "^0.71.0", "@opentelemetry/exporter-prometheus": "^0.211.0", "@opentelemetry/sdk-node": "^0.211.0", "example-app": "link:../app" diff --git a/packages/catalog-client/report.api.md b/packages/catalog-client/report.api.md index c155084ccd..73eb280d87 100644 --- a/packages/catalog-client/report.api.md +++ b/packages/catalog-client/report.api.md @@ -15,6 +15,7 @@ export type AddLocationRequest = { type?: string; target: string; dryRun?: boolean; + onConflict?: 'refresh' | 'reject'; }; // @public diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index a671a155d3..cf363e1823 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -567,12 +567,15 @@ export class CatalogClient implements CatalogApi { request: AddLocationRequest, options?: CatalogRequestOptions, ): Promise { - const { type = 'url', target, dryRun } = request; + const { type = 'url', target, dryRun, onConflict } = request; const response = await this.apiClient.createLocation( { body: { type, target }, - query: { dryRun: dryRun ? 'true' : undefined }, + query: { + dryRun: dryRun ? 'true' : undefined, + onConflict, + }, }, options, ); diff --git a/packages/catalog-client/src/schema/openapi/generated/apis/Api.client.ts b/packages/catalog-client/src/schema/openapi/generated/apis/Api.client.ts index 63d55aea55..4b3e088fe2 100644 --- a/packages/catalog-client/src/schema/openapi/generated/apis/Api.client.ts +++ b/packages/catalog-client/src/schema/openapi/generated/apis/Api.client.ts @@ -178,6 +178,7 @@ export type CreateLocation = { body: CreateLocationRequest; query: { dryRun?: string; + onConflict?: 'refresh' | 'reject'; }; }; /** @@ -592,6 +593,7 @@ export class DefaultApiClient { * Create a location for a given target. * @param createLocationRequest - * @param dryRun - + * @param onConflict - Behavior when the location already exists. \'reject\' (default) returns a 409 error, \'refresh\' triggers a refresh of the existing location entity and returns 201. */ public async createLocation( // @ts-ignore @@ -600,7 +602,7 @@ export class DefaultApiClient { ): Promise> { const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); - const uriTemplate = `/locations{?dryRun}`; + const uriTemplate = `/locations{?dryRun,onConflict}`; const uri = parser.parse(uriTemplate).expand({ ...request.query, diff --git a/packages/catalog-client/src/types/api.ts b/packages/catalog-client/src/types/api.ts index fac4ed5928..d6d5392fcf 100644 --- a/packages/catalog-client/src/types/api.ts +++ b/packages/catalog-client/src/types/api.ts @@ -396,6 +396,12 @@ export type AddLocationRequest = { * contain the entities that match the given location. */ dryRun?: boolean; + /** + * Behavior when the location already exists. If set to `'reject'` (the + * default), a conflict error is returned. If set to `'refresh'`, the + * existing location entity is marked for refresh and a 201 is returned. + */ + onConflict?: 'refresh' | 'reject'; }; /** diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index 591b32ead7..bb22326786 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -12,6 +12,7 @@ Options: -h, --help Commands: + auth [command] build-workspace config [command] config:check @@ -30,13 +31,92 @@ Commands: versions:migrate ``` +### `backstage-cli auth` + +``` +Usage: backstage-cli auth [options] [command] [command] + +Options: + -h, --help + +Commands: + help [command] + list + login + logout + print-token + select + show +``` + +### `backstage-cli auth list` + +``` +Usage: backstage-cli auth list + +Options: + -h, --help +``` + +### `backstage-cli auth login` + +``` +Usage: backstage-cli auth login + +Options: + --backend-url + --instance + --no-browser + -h, --help +``` + +### `backstage-cli auth logout` + +``` +Usage: backstage-cli auth logout + +Options: + --instance + -h, --help +``` + +### `backstage-cli auth print-token` + +``` +Usage: backstage-cli auth print-token + +Options: + --instance + -h, --help +``` + +### `backstage-cli auth select` + +``` +Usage: backstage-cli auth select + +Options: + --instance + -h, --help +``` + +### `backstage-cli auth show` + +``` +Usage: backstage-cli auth show + +Options: + --instance + -h, --help +``` + ### `backstage-cli build-workspace` ``` -Usage: program [options] [packages...] +Usage: backstage-cli build-workspace [packages...] Options: - --alwaysPack + --always-pack -h, --help ``` @@ -131,7 +211,7 @@ Options: ### `backstage-cli create-github-app` ``` -Usage: program [options] +Usage: backstage-cli create-github-app Options: -h, --help @@ -213,16 +293,16 @@ Options: ### `backstage-cli new` ``` -Usage: program [options] +Usage: backstage-cli new Options: - --baseVersion - --license - --no-private - --npm-registry - --option = - --scope - --select + --base-version + --license + --npm-registry + --option + --private + --scope + --select --skip-install -h, --help ``` @@ -249,13 +329,13 @@ Commands: ### `backstage-cli package build` ``` -Usage: program [options] +Usage: backstage-cli package build Options: - --config + --config --minify --module-federation - --role + --role --skip-build-dependencies --stats -h, --help @@ -273,13 +353,13 @@ Options: ### `backstage-cli package lint` ``` -Usage: program [options] [directories...] +Usage: backstage-cli package lint [directories...] Options: --fix - --format - --max-warnings - --output-file + --format + --max-warnings + --output-file -h, --help ``` @@ -304,17 +384,17 @@ Options: ### `backstage-cli package start` ``` -Usage: program [options] +Usage: backstage-cli package start Options: --check - --config - --entrypoint - --inspect [host] - --inspect-brk [host] - --link - --require - --role + --config + --entrypoint + --inspect + --inspect-brk + --link + --require + --role -h, --help ``` @@ -453,12 +533,12 @@ Commands: ### `backstage-cli repo build` ``` -Usage: program [options] [command] +Usage: backstage-cli repo build Options: --all --minify - --since + --since -h, --help ``` @@ -474,7 +554,7 @@ Options: ### `backstage-cli repo fix` ``` -Usage: program [options] +Usage: backstage-cli repo fix Options: --check @@ -485,23 +565,23 @@ Options: ### `backstage-cli repo lint` ``` -Usage: program [options] [command] +Usage: backstage-cli repo lint Options: --fix - --format - --max-warnings - --output-file - --since - --successCache - --successCacheDir + --format + --max-warnings + --output-file + --since + --success-cache + --success-cache-dir -h, --help ``` ### `backstage-cli repo list-deprecations` ``` -Usage: program [options] +Usage: backstage-cli repo list-deprecations Options: --json @@ -511,28 +591,28 @@ Options: ### `backstage-cli repo start` ``` -Usage: program [options] [packageNameOrPath...] +Usage: backstage-cli repo start [packages...] Options: - --config - --inspect [host] - --inspect-brk [host] - --link - --plugin - --require + --config + --inspect + --inspect-brk + --link + --plugin + --require -h, --help ``` ### `backstage-cli repo test` ``` -Usage: program [options] +Usage: backstage-cli repo test Options: --jest-help - --since - --successCache - --successCacheDir + --since + --success-cache + --success-cache-dir -h, --help ``` @@ -575,11 +655,11 @@ Options: ### `backstage-cli versions:bump` ``` -Usage: program [options] +Usage: backstage-cli versions:bump Options: - --pattern - --release + --pattern + --release --skip-install --skip-migrate -h, --help @@ -588,10 +668,10 @@ Options: ### `backstage-cli versions:migrate` ``` -Usage: program [options] +Usage: backstage-cli versions:migrate Options: - --pattern + --pattern --skip-code-changes -h, --help ``` diff --git a/packages/cli/package.json b/packages/cli/package.json index 4350836d22..f3eda60401 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -120,6 +120,7 @@ "postcss": "^8.1.0", "postcss-import": "^16.1.0", "process": "^0.11.10", + "proper-lockfile": "^4.1.2", "raw-loader": "^4.0.2", "react-dev-utils": "^12.0.0-next.60", "react-refresh": "^0.17.0", @@ -131,6 +132,7 @@ "rollup-plugin-postcss": "^4.0.0", "rollup-pluginutils": "^2.8.2", "semver": "^7.5.3", + "shell-quote": "^1.8.1", "style-loader": "^3.3.1", "sucrase": "^3.20.2", "swc-loader": "^0.2.3", @@ -174,9 +176,11 @@ "@types/jest": "^30.0.0", "@types/node": "^22.13.14", "@types/npm-packlist": "^3.0.0", + "@types/proper-lockfile": "^4", "@types/recursive-readdir": "^2.2.0", "@types/rollup-plugin-peer-deps-external": "^2.2.0", "@types/rollup-plugin-postcss": "^3.1.4", + "@types/shell-quote": "^1.7.5", "@types/svgo": "^2.6.2", "@types/tar": "^6.1.1", "@types/terser-webpack-plugin": "^5.0.4", @@ -194,6 +198,9 @@ "webpack": "~5.105.0", "webpack-dev-server": "^5.0.0" }, + "optionalDependencies": { + "keytar": "^7.9.0" + }, "peerDependencies": { "@jest/environment-jsdom-abstract": "^30.0.0", "@module-federation/enhanced": "^0.21.6", diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 9240ad876c..14da5e791e 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -28,5 +28,6 @@ import { CliInitializer } from './wiring/CliInitializer'; initializer.add(import('./modules/new')); initializer.add(import('./modules/test')); initializer.add(import('./modules/translations')); + initializer.add(import('./modules/auth')); await initializer.run(); })(); diff --git a/packages/cli/src/modules/auth/commands/list.ts b/packages/cli/src/modules/auth/commands/list.ts new file mode 100644 index 0000000000..c245072c88 --- /dev/null +++ b/packages/cli/src/modules/auth/commands/list.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { cli } from 'cleye'; +import type { CommandContext } from '../../../wiring/types'; +import { getAllInstances } from '../lib/storage'; + +export default async ({ args, info }: CommandContext) => { + cli({ help: info }, undefined, args); + + const { instances, selected } = await getAllInstances(); + if (!instances.length) { + process.stderr.write('No instances found\n'); + return; + } + for (const inst of instances) { + const mark = inst.name === selected?.name ? '* ' : ' '; + process.stdout.write(`${mark}${inst.name} - ${inst.baseUrl}\n`); + } +}; diff --git a/packages/cli/src/modules/auth/commands/login.ts b/packages/cli/src/modules/auth/commands/login.ts new file mode 100644 index 0000000000..777fcb1807 --- /dev/null +++ b/packages/cli/src/modules/auth/commands/login.ts @@ -0,0 +1,383 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { cli } from 'cleye'; +import type { CommandContext } from '../../../wiring/types'; +import { startCallbackServer } from '../lib/localServer'; +import { spawn } from 'node:child_process'; +import { challengeFromVerifier, generateVerifier } from '../lib/pkce'; +import { httpJson } from '../lib/http'; +import { + upsertInstance, + withMetadataLock, + getAllInstances, + getInstanceByName, + StoredInstance, +} from '../lib/storage'; +import { getSecretStore } from '../lib/secretStore'; +import crypto from 'node:crypto'; +import fs from 'fs-extra'; +import path from 'node:path'; +import glob from 'glob'; +import YAML from 'yaml'; +import inquirer from 'inquirer'; + +const TOKEN_EXCHANGE_TIMEOUT_MS = 30_000; + +export default async ({ args, info }: CommandContext) => { + const { + flags: { backendUrl, noBrowser, instance: instanceFlag }, + } = cli( + { + help: info, + flags: { + backendUrl: { type: String, description: 'Backend base URL' }, + noBrowser: { + type: Boolean, + description: 'Do not open browser automatically', + }, + instance: { + type: String, + description: 'Name for this instance (used by other auth commands)', + }, + }, + }, + undefined, + args, + ); + + const { instances, selected } = await getAllInstances(); + + let backendBaseUrl: string; + let instanceName: string; + + if (instanceFlag) { + instanceName = instanceFlag; + const targetInstance = instances.find(i => i.name === instanceFlag); + if (targetInstance) { + backendBaseUrl = normalizeUrl(backendUrl) ?? targetInstance.baseUrl; + } else { + backendBaseUrl = normalizeUrl(backendUrl) ?? (await pickBaseUrl()); + } + } else if (backendUrl) { + backendBaseUrl = normalizeUrl(backendUrl); + instanceName = deriveInstanceName(backendBaseUrl); + } else if (instances.length > 0) { + const choice = await promptForInstance(instances, selected); + if (choice === '__new__') { + backendBaseUrl = await pickBaseUrl(); + instanceName = deriveInstanceName(backendBaseUrl); + } else { + const targetInstance = instances.find(i => i.name === choice); + if (!targetInstance) { + throw new Error('Instance not found'); + } + backendBaseUrl = targetInstance.baseUrl; + instanceName = targetInstance.name; + } + } else { + backendBaseUrl = await pickBaseUrl(); + instanceName = deriveInstanceName(backendBaseUrl); + } + + const authBaseUrl = `${backendBaseUrl}/api/auth`; + const clientId = `${authBaseUrl}/.well-known/oauth-client/cli.json`; + + const metadataResponse = await fetch(clientId, { + signal: AbortSignal.timeout(30_000), + }); + if (!metadataResponse.ok) { + throw new Error( + `Server does not support CLI authentication. Ensure CIMD is enabled on the backend.`, + ); + } + + const { verifier, challenge, state } = createPkceState(); + const callback = await startCallbackServer({ state }); + + try { + const authorizeUrl = buildAuthorizeUrl({ + authBaseUrl, + clientId, + redirectUri: callback.url, + state, + challenge, + }); + + await openBrowserOrPrint(authorizeUrl, noBrowser); + + const code = await waitForAuthorizationCode(callback, state); + + const token = await exchangeAuthorizationCode({ + authBaseUrl, + code, + redirectUri: callback.url, + verifier, + }); + + await persistInstance({ + instanceName, + backendBaseUrl, + clientId, + token, + }); + + process.stdout.write('Login successful\n'); + } finally { + await callback.close(); + } +}; + +async function promptForInstance( + instances: StoredInstance[], + selected: StoredInstance | undefined, +): Promise { + const choices = instances.map(i => ({ + name: `${i.name === selected?.name ? '* ' : ' '}${i.name} (${i.baseUrl})`, + value: i.name, + })); + + choices.push({ + name: 'Add new instance...', + value: '__new__', + }); + + const { choice } = await inquirer.prompt<{ choice: string }>([ + { + type: 'list', + name: 'choice', + message: 'Select instance to authenticate:', + choices, + default: selected?.name ?? '__new__', + }, + ]); + + return choice; +} + +async function pickBaseUrl() { + const cwd = process.cwd(); + const candidates: Array<{ url: string; file: string }> = []; + + const patterns = [ + 'app-config.yaml', + 'app-config.*.yaml', + 'packages/*/app-config.yaml', + 'packages/*/app-config.*.yaml', + ]; + const files = patterns.flatMap(p => glob.sync(p, { cwd, nodir: true })); + for (const file of files) { + try { + const content = await fs.readFile(path.resolve(cwd, file), 'utf8'); + const doc = YAML.parse(content); + const url = doc?.backend?.baseUrl as string | undefined; + if (url) { + candidates.push({ url: normalizeUrl(url), file }); + } + } catch { + // ignore parse errors + } + } + + const list = [...new Map(candidates.map(c => [c.url, c])).values()]; + if (list.length === 0) { + const { manual } = await inquirer.prompt<{ manual: string }>([ + { type: 'input', name: 'manual', message: 'Enter backend base URL' }, + ]); + return normalizeUrl(manual); + } + if (list.length === 1) { + return list[0].url; + } + + const { picked } = await inquirer.prompt<{ picked: string }>([ + { + type: 'list', + name: 'picked', + message: 'Select backend base URL', + choices: [ + ...list.map(e => ({ name: `${e.url} (${e.file})`, value: e.url })), + { name: 'Enter manually', value: '__manual__' }, + ], + }, + ]); + if (picked === '__manual__') { + const { manual } = await inquirer.prompt<{ manual: string }>([ + { type: 'input', name: 'manual', message: 'Enter backend base URL' }, + ]); + return normalizeUrl(manual); + } + return picked; +} + +function normalizeUrl(u: string): string; +function normalizeUrl(u: string | undefined): string | undefined; +function normalizeUrl(u: string | undefined): string | undefined { + if (u === undefined) { + return undefined; + } + try { + const url = new URL(u); + return url.toString().replace(/\/$/, ''); + } catch { + throw new Error(`'${u}' is not a valid URL`); + } +} + +function deriveInstanceName(url: string): string { + return new URL(url).host; +} + +function createPkceState() { + const verifier = generateVerifier(); + const challenge = challengeFromVerifier(verifier); + const state = cryptoRandom(); + return { verifier, challenge, state }; +} + +function buildAuthorizeUrl(options: { + authBaseUrl: string; + clientId: string; + redirectUri: string; + state: string; + challenge: string; +}): string { + const { authBaseUrl, clientId, redirectUri, state, challenge } = options; + const authorize = new URL(`${authBaseUrl}/v1/authorize`); + authorize.searchParams.set('client_id', clientId); + authorize.searchParams.set('redirect_uri', redirectUri); + authorize.searchParams.set('response_type', 'code'); + authorize.searchParams.set('scope', 'openid offline_access'); + authorize.searchParams.set('state', state); + authorize.searchParams.set('code_challenge', challenge); + authorize.searchParams.set('code_challenge_method', 'S256'); + return authorize.toString(); +} + +async function openBrowserOrPrint(url: string, noBrowser?: boolean) { + if (noBrowser) { + process.stdout.write(`Open this URL to continue: ${url}\n`); + } else { + process.stdout.write(`Opening the following URL: ${url}\n`); + openInBrowser(url); + } +} + +async function waitForAuthorizationCode( + callback: Awaited>, + expectedState: string, +) { + const { code, state } = await callback.waitForCode(); + if (state !== expectedState) { + throw new Error('State mismatch'); + } + return code; +} + +async function exchangeAuthorizationCode(options: { + authBaseUrl: string; + code: string; + redirectUri: string; + verifier: string; +}) { + const { authBaseUrl, code, redirectUri, verifier } = options; + return await httpJson<{ + access_token: string; + token_type: string; + expires_in: number; + id_token?: string; + refresh_token?: string; + }>(`${authBaseUrl}/v1/token`, { + method: 'POST', + body: { + grant_type: 'authorization_code', + code, + redirect_uri: redirectUri, + code_verifier: verifier, + }, + signal: AbortSignal.timeout(TOKEN_EXCHANGE_TIMEOUT_MS), + }); +} + +async function persistInstance(options: { + instanceName: string; + backendBaseUrl: string; + clientId: string; + token: { access_token: string; refresh_token?: string; expires_in: number }; +}) { + const { instanceName, backendBaseUrl, clientId, token } = options; + const secretStore = await getSecretStore(); + await withMetadataLock(async () => { + const service = `backstage-cli:auth-instance:${instanceName}`; + await secretStore.set(service, 'accessToken', token.access_token); + if (token.refresh_token) { + await secretStore.set(service, 'refreshToken', token.refresh_token); + } else { + process.stderr.write( + 'Warning: No refresh token received. You will need to re-authenticate when the access token expires.\n', + ); + } + let existing: StoredInstance | undefined; + try { + existing = await getInstanceByName(instanceName); + } catch { + // new instance + } + await upsertInstance({ + name: instanceName, + baseUrl: backendBaseUrl, + clientId, + issuedAt: Date.now(), + accessTokenExpiresAt: Date.now() + token.expires_in * 1000, + selected: existing?.selected, + }); + }); +} + +function cryptoRandom(): string { + return crypto.randomBytes(32).toString('hex'); +} + +// The react-dev-utils/openBrowser breaks the login URL by encoding the URL parameters again +export function openInBrowser(url: string): void { + const handleError = (error: unknown) => { + const message = error instanceof Error ? error.message : 'Unknown error'; + process.stderr.write( + `Warning: Failed to open browser automatically: ${message}\n`, + ); + process.stderr.write(`Please open this URL manually: ${url}\n`); + }; + + const spawnOpts = { detached: true, stdio: 'ignore' } as const; + let child; + try { + if (process.platform === 'darwin') { + child = spawn('open', [url], spawnOpts); + } else if (process.platform === 'win32') { + child = spawn( + 'powershell', + ['-Command', `Start-Process '${url.replace(/'/g, "''")}'`], + spawnOpts, + ); + } else { + child = spawn('xdg-open', [url], spawnOpts); + } + child.unref(); + child.on('error', handleError); + } catch (error) { + handleError(error); + } +} diff --git a/packages/cli/src/modules/auth/commands/logout.ts b/packages/cli/src/modules/auth/commands/logout.ts new file mode 100644 index 0000000000..8277ea6335 --- /dev/null +++ b/packages/cli/src/modules/auth/commands/logout.ts @@ -0,0 +1,77 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { cli } from 'cleye'; +import type { CommandContext } from '../../../wiring/types'; +import { getSecretStore } from '../lib/secretStore'; +import { + removeInstance, + withMetadataLock, + getInstanceByName, +} from '../lib/storage'; +import { httpJson } from '../lib/http'; +import { pickInstance } from '../lib/prompt'; + +export default async ({ args, info }: CommandContext) => { + const { + flags: { instance: instanceFlag }, + } = cli( + { + help: info, + flags: { + instance: { + type: String, + description: 'Name of the instance to log out', + }, + }, + }, + undefined, + args, + ); + + const { name: instanceName } = await pickInstance(instanceFlag); + + await withMetadataLock(async () => { + const instance = await getInstanceByName(instanceName); + const secretStore = await getSecretStore(); + const service = `backstage-cli:auth-instance:${instanceName}`; + const refreshToken = (await secretStore.get(service, 'refreshToken')) ?? ''; + + if (refreshToken) { + try { + const authBaseUrl = new URL('/api/auth', instance.baseUrl) + .toString() + .replace(/\/$/, ''); + await httpJson(`${authBaseUrl}/v1/revoke`, { + method: 'POST', + body: { + token: refreshToken, + token_type_hint: 'refresh_token', + }, + signal: AbortSignal.timeout(30_000), + }); + } catch { + // ignore errors per RFC 7009 + } + } + + await secretStore.delete(service, 'accessToken'); + await secretStore.delete(service, 'refreshToken'); + await removeInstance(instance.name); + }); + + process.stdout.write('Logged out\n'); +}; diff --git a/packages/cli/src/modules/auth/commands/printToken.ts b/packages/cli/src/modules/auth/commands/printToken.ts new file mode 100644 index 0000000000..ea848a112a --- /dev/null +++ b/packages/cli/src/modules/auth/commands/printToken.ts @@ -0,0 +1,54 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { cli } from 'cleye'; +import type { CommandContext } from '../../../wiring/types'; +import { accessTokenNeedsRefresh, refreshAccessToken } from '../lib/auth'; +import { getSelectedInstance } from '../lib/storage'; +import { getSecretStore } from '../lib/secretStore'; + +export default async ({ args, info }: CommandContext) => { + const { + flags: { instance: instanceFlag }, + } = cli( + { + help: info, + flags: { + instance: { + type: String, + description: 'Name of the instance to use', + }, + }, + }, + undefined, + args, + ); + + let instance = await getSelectedInstance(instanceFlag); + + if (accessTokenNeedsRefresh(instance)) { + instance = await refreshAccessToken(instance.name); + } + + const secretStore = await getSecretStore(); + const service = `backstage-cli:auth-instance:${instance.name}`; + const accessToken = await secretStore.get(service, 'accessToken'); + if (!accessToken) { + throw new Error('No access token found. Run "auth login" to authenticate.'); + } + + process.stdout.write(`${accessToken}\n`); +}; diff --git a/packages/cli/src/modules/auth/commands/select.ts b/packages/cli/src/modules/auth/commands/select.ts new file mode 100644 index 0000000000..839fee691e --- /dev/null +++ b/packages/cli/src/modules/auth/commands/select.ts @@ -0,0 +1,43 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { cli } from 'cleye'; +import type { CommandContext } from '../../../wiring/types'; +import { setSelectedInstance } from '../lib/storage'; +import { pickInstance } from '../lib/prompt'; + +export default async ({ args, info }: CommandContext) => { + const { + flags: { instance: instanceFlag }, + } = cli( + { + help: info, + flags: { + instance: { + type: String, + description: 'Name of the instance to select', + }, + }, + }, + undefined, + args, + ); + + const instance = await pickInstance(instanceFlag); + + await setSelectedInstance(instance.name); + process.stderr.write(`Selected instance '${instance.name}'\n`); +}; diff --git a/packages/cli/src/modules/auth/commands/show.ts b/packages/cli/src/modules/auth/commands/show.ts new file mode 100644 index 0000000000..cd55430f91 --- /dev/null +++ b/packages/cli/src/modules/auth/commands/show.ts @@ -0,0 +1,72 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { cli } from 'cleye'; +import type { CommandContext } from '../../../wiring/types'; +import { httpJson } from '../lib/http'; +import { getSelectedInstance } from '../lib/storage'; +import { accessTokenNeedsRefresh, refreshAccessToken } from '../lib/auth'; +import { getSecretStore } from '../lib/secretStore'; + +export default async ({ args, info }: CommandContext) => { + const { + flags: { instance: instanceFlag }, + } = cli( + { + help: info, + flags: { + instance: { + type: String, + description: 'Name of the instance to show', + }, + }, + }, + undefined, + args, + ); + + let instance = await getSelectedInstance(instanceFlag); + + if (accessTokenNeedsRefresh(instance)) { + process.stdout.write('Refreshing access token...\n'); + instance = await refreshAccessToken(instance.name); + } + const authBase = new URL('/api/auth', instance.baseUrl) + .toString() + .replace(/\/$/, ''); + + const secretStore = await getSecretStore(); + const service = `backstage-cli:auth-instance:${instance.name}`; + const accessToken = await secretStore.get(service, 'accessToken'); + if (!accessToken) { + throw new Error('No access token found. Run "auth login" to authenticate.'); + } + + const userinfo = await httpJson<{ claims: { sub: string; ent: string[] } }>( + `${authBase}/v1/userinfo`, + { + headers: { Authorization: `Bearer ${accessToken}` }, + signal: AbortSignal.timeout(30_000), + }, + ); + + process.stdout.write(`User: ${userinfo.claims.sub}\n`); + process.stdout.write(`\n`); + process.stdout.write(`Ownership:\n`); + for (const ent of userinfo.claims.ent ?? []) { + process.stdout.write(` - ${ent}\n`); + } +}; diff --git a/packages/cli/src/modules/auth/index.ts b/packages/cli/src/modules/auth/index.ts new file mode 100644 index 0000000000..d5766219b2 --- /dev/null +++ b/packages/cli/src/modules/auth/index.ts @@ -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 { createCliPlugin } from '../../wiring/factory'; + +export default createCliPlugin({ + pluginId: 'auth', + init: async reg => { + reg.addCommand({ + path: ['auth', 'login'], + description: 'Log in the CLI to a Backstage instance', + execute: { loader: () => import('./commands/login') }, + }); + reg.addCommand({ + path: ['auth', 'logout'], + description: 'Log out the CLI and clear stored credentials', + execute: { loader: () => import('./commands/logout') }, + }); + reg.addCommand({ + path: ['auth', 'show'], + description: 'Show details of an authenticated instance', + execute: { loader: () => import('./commands/show') }, + }); + reg.addCommand({ + path: ['auth', 'list'], + description: 'List authenticated instances', + execute: { loader: () => import('./commands/list') }, + }); + reg.addCommand({ + path: ['auth', 'print-token'], + description: 'Print an access token to stdout (auto-refresh if needed)', + execute: { loader: () => import('./commands/printToken') }, + }); + reg.addCommand({ + path: ['auth', 'select'], + description: 'Select the default instance', + execute: { loader: () => import('./commands/select') }, + }); + }, +}); diff --git a/packages/cli/src/modules/auth/lib/auth.test.ts b/packages/cli/src/modules/auth/lib/auth.test.ts new file mode 100644 index 0000000000..5ba4ae0295 --- /dev/null +++ b/packages/cli/src/modules/auth/lib/auth.test.ts @@ -0,0 +1,336 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { accessTokenNeedsRefresh, refreshAccessToken } from './auth'; +import * as storage from './storage'; +import * as secretStore from './secretStore'; +import * as http from './http'; + +jest.mock('./storage'); +jest.mock('./secretStore'); +jest.mock('./http'); + +const mockStorage = storage as jest.Mocked; +const mockSecretStore = secretStore as jest.Mocked; +const mockHttp = http as jest.Mocked; + +describe('auth', () => { + describe('accessTokenNeedsRefresh', () => { + it('should return true if token expires within 2 minutes', () => { + const now = Date.now(); + const instance = { + name: 'test', + baseUrl: 'http://localhost:7007', + clientId: 'test-client', + issuedAt: now, + + accessTokenExpiresAt: now + 60_000, // 1 minute from now + }; + + expect(accessTokenNeedsRefresh(instance)).toBe(true); + }); + + it('should return true if token has already expired', () => { + const now = Date.now(); + const instance = { + name: 'test', + baseUrl: 'http://localhost:7007', + clientId: 'test-client', + issuedAt: now - 3600_000, + + accessTokenExpiresAt: now - 60_000, // expired 1 minute ago + }; + + expect(accessTokenNeedsRefresh(instance)).toBe(true); + }); + + it('should return false if token is valid for more than 2 minutes', () => { + const now = Date.now(); + const instance = { + name: 'test', + baseUrl: 'http://localhost:7007', + clientId: 'test-client', + issuedAt: now, + + accessTokenExpiresAt: now + 5 * 60_000, // 5 minutes from now + }; + + expect(accessTokenNeedsRefresh(instance)).toBe(false); + }); + + it('should return true at exactly 2 minutes before expiration', () => { + const now = Date.now(); + const instance = { + name: 'test', + baseUrl: 'http://localhost:7007', + clientId: 'test-client', + issuedAt: now, + + accessTokenExpiresAt: now + 2 * 60_000, // exactly 2 minutes from now + }; + + expect(accessTokenNeedsRefresh(instance)).toBe(true); + }); + }); + + describe('refreshAccessToken', () => { + const mockSecretStoreInstance = { + get: jest.fn(), + set: jest.fn(), + delete: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockSecretStore.getSecretStore.mockResolvedValue(mockSecretStoreInstance); + }); + + it('should successfully refresh access token', async () => { + const now = Date.now(); + const instance = { + name: 'test', + baseUrl: 'http://localhost:7007', + clientId: 'test-client-id', + issuedAt: now - 3600_000, + + accessTokenExpiresAt: now - 60_000, + }; + + mockStorage.withMetadataLock.mockImplementation( + async (fn: () => Promise) => fn(), + ); + mockStorage.getInstanceByName.mockResolvedValue(instance); + mockSecretStoreInstance.get.mockImplementation( + async (_service: string, account: string) => { + if (account === 'clientSecret') return 'test-secret'; + if (account === 'refreshToken') return 'old-refresh-token'; + return undefined; + }, + ); + + const tokenResponse = { + access_token: 'new-access-token', + token_type: 'Bearer', + expires_in: 3600, + refresh_token: 'new-refresh-token', + }; + + mockHttp.httpJson.mockResolvedValue(tokenResponse); + mockStorage.upsertInstance.mockResolvedValue(); + + const result = await refreshAccessToken('test'); + + expect(mockStorage.getInstanceByName).toHaveBeenCalledWith('test'); + expect(mockSecretStoreInstance.get).toHaveBeenCalledWith( + 'backstage-cli:auth-instance:test', + 'refreshToken', + ); + expect(mockHttp.httpJson).toHaveBeenCalledWith( + 'http://localhost:7007/api/auth/v1/token', + { + signal: expect.any(AbortSignal), + method: 'POST', + body: { + grant_type: 'refresh_token', + refresh_token: 'old-refresh-token', + }, + }, + ); + expect(mockSecretStoreInstance.set).toHaveBeenCalledWith( + 'backstage-cli:auth-instance:test', + 'accessToken', + 'new-access-token', + ); + expect(mockSecretStoreInstance.set).toHaveBeenCalledWith( + 'backstage-cli:auth-instance:test', + 'refreshToken', + 'new-refresh-token', + ); + expect(mockStorage.upsertInstance).toHaveBeenCalled(); + expect(result.accessTokenExpiresAt).toBeGreaterThan(now); + }); + + it('should throw error if refresh token is missing', async () => { + const now = Date.now(); + const instance = { + name: 'test', + baseUrl: 'http://localhost:7007', + clientId: 'test-client-id', + issuedAt: now - 3600_000, + + accessTokenExpiresAt: now - 60_000, + }; + + mockStorage.withMetadataLock.mockImplementation( + async (fn: () => Promise) => fn(), + ); + mockStorage.getInstanceByName.mockResolvedValue(instance); + mockSecretStoreInstance.get.mockResolvedValue(undefined); + + await expect(refreshAccessToken('test')).rejects.toThrow( + 'Access token is expired and no refresh token is available', + ); + }); + + it('should use metadata lock during refresh', async () => { + const now = Date.now(); + const instance = { + name: 'test', + baseUrl: 'http://localhost:7007', + clientId: 'test-client-id', + issuedAt: now - 3600_000, + + accessTokenExpiresAt: now - 60_000, + }; + + let lockAcquired = false; + mockStorage.withMetadataLock.mockImplementation( + async (fn: () => Promise) => { + lockAcquired = true; + return fn(); + }, + ); + mockStorage.getInstanceByName.mockResolvedValue(instance); + mockSecretStoreInstance.get.mockImplementation( + async (_service: string, account: string) => { + if (account === 'clientSecret') return 'test-secret'; + if (account === 'refreshToken') return 'refresh-token'; + return undefined; + }, + ); + + const tokenResponse = { + access_token: 'new-access-token', + token_type: 'Bearer', + expires_in: 3600, + refresh_token: 'new-refresh-token', + }; + + mockHttp.httpJson.mockResolvedValue(tokenResponse); + mockStorage.upsertInstance.mockResolvedValue(); + + await refreshAccessToken('test'); + + expect(lockAcquired).toBe(true); + expect(mockStorage.withMetadataLock).toHaveBeenCalled(); + }); + + it('should handle HTTP and network errors during refresh', async () => { + const now = Date.now(); + const instance = { + name: 'test', + baseUrl: 'http://localhost:7007', + clientId: 'test-client-id', + issuedAt: now - 3600_000, + + accessTokenExpiresAt: now - 60_000, + }; + + const errorCases = [ + new Error('Request failed with 401 Unauthorized'), + new Error('Network error'), + ]; + + for (const error of errorCases) { + mockStorage.withMetadataLock.mockImplementation( + async (fn: () => Promise) => fn(), + ); + mockStorage.getInstanceByName.mockResolvedValue(instance); + mockSecretStoreInstance.get.mockImplementation( + async (_service: string, account: string) => { + if (account === 'clientSecret') return 'test-secret'; + if (account === 'refreshToken') return 'refresh-token'; + return undefined; + }, + ); + + mockHttp.httpJson.mockRejectedValue(error); + + await expect(refreshAccessToken('test')).rejects.toThrow(error.message); + } + }); + + it('should validate token response and reject malformed responses', async () => { + const now = Date.now(); + const instance = { + name: 'test', + baseUrl: 'http://localhost:7007', + clientId: 'test-client-id', + issuedAt: now - 3600_000, + + accessTokenExpiresAt: now - 60_000, + }; + + mockStorage.withMetadataLock.mockImplementation( + async (fn: () => Promise) => fn(), + ); + mockStorage.getInstanceByName.mockResolvedValue(instance); + mockSecretStoreInstance.get.mockImplementation( + async (_service: string, account: string) => { + if (account === 'clientSecret') return 'test-secret'; + if (account === 'refreshToken') return 'refresh-token'; + return undefined; + }, + ); + + // Test missing access_token + mockHttp.httpJson.mockResolvedValue({ + token_type: 'Bearer', + expires_in: 3600, + refresh_token: 'new-refresh-token', + } as any); + + await expect(refreshAccessToken('test')).rejects.toThrow( + 'Invalid token response', + ); + await expect(refreshAccessToken('test')).rejects.toThrow('access_token'); + + // Test missing expires_in + mockHttp.httpJson.mockResolvedValue({ + access_token: 'new-access-token', + token_type: 'Bearer', + refresh_token: 'new-refresh-token', + } as any); + + await expect(refreshAccessToken('test')).rejects.toThrow( + 'Invalid token response', + ); + await expect(refreshAccessToken('test')).rejects.toThrow('expires_in'); + + // Test missing refresh_token still succeeds and preserves existing token + mockHttp.httpJson.mockResolvedValue({ + access_token: 'new-access-token', + token_type: 'Bearer', + expires_in: 3600, + } as any); + + await expect(refreshAccessToken('test')).resolves.toBeDefined(); + + // Test invalid expires_in (non-positive) + mockHttp.httpJson.mockResolvedValue({ + access_token: 'new-access-token', + token_type: 'Bearer', + expires_in: 0, + refresh_token: 'new-refresh-token', + } as any); + + await expect(refreshAccessToken('test')).rejects.toThrow( + 'Invalid token response', + ); + await expect(refreshAccessToken('test')).rejects.toThrow('expires_in'); + }); + }); +}); diff --git a/packages/cli/src/modules/auth/lib/auth.ts b/packages/cli/src/modules/auth/lib/auth.ts new file mode 100644 index 0000000000..3ea7baef5c --- /dev/null +++ b/packages/cli/src/modules/auth/lib/auth.ts @@ -0,0 +1,84 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { z } from 'zod'; +import { + StoredInstance, + upsertInstance, + withMetadataLock, + getInstanceByName, +} from './storage'; +import { getSecretStore } from './secretStore'; +import { httpJson } from './http'; + +const TokenResponseSchema = z.object({ + access_token: z.string().min(1), + token_type: z.string().min(1), + expires_in: z.number().positive().finite(), + refresh_token: z.string().min(1).optional(), +}); + +export function accessTokenNeedsRefresh(instance: StoredInstance): boolean { + return instance.accessTokenExpiresAt <= Date.now() + 2 * 60_000; // 2 minutes before expiration +} + +export async function refreshAccessToken( + instanceName: string, +): Promise { + const secretStore = await getSecretStore(); + + return withMetadataLock(async () => { + const instance = await getInstanceByName(instanceName); + + const service = `backstage-cli:auth-instance:${instanceName}`; + const refreshToken = (await secretStore.get(service, 'refreshToken')) ?? ''; + if (!refreshToken) { + throw new Error( + 'Access token is expired and no refresh token is available', + ); + } + + const response = await httpJson( + `${instance.baseUrl}/api/auth/v1/token`, + { + method: 'POST', + body: { + grant_type: 'refresh_token', + refresh_token: refreshToken, + }, + signal: AbortSignal.timeout(30_000), + }, + ); + + const parsed = TokenResponseSchema.safeParse(response); + if (!parsed.success) { + throw new Error(`Invalid token response: ${parsed.error.message}`); + } + const token = parsed.data; + + await secretStore.set(service, 'accessToken', token.access_token); + if (token.refresh_token) { + await secretStore.set(service, 'refreshToken', token.refresh_token); + } + const newInstance = { + ...instance, + issuedAt: Date.now(), + accessTokenExpiresAt: Date.now() + token.expires_in * 1000, + }; + await upsertInstance(newInstance); + return newInstance; + }); +} diff --git a/packages/cli/src/modules/auth/lib/http.test.ts b/packages/cli/src/modules/auth/lib/http.test.ts new file mode 100644 index 0000000000..74416a0dee --- /dev/null +++ b/packages/cli/src/modules/auth/lib/http.test.ts @@ -0,0 +1,269 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fetch from 'cross-fetch'; +import { httpJson } from './http'; + +jest.mock('cross-fetch'); + +const mockFetch = fetch as jest.MockedFunction; + +describe('http', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('httpJson', () => { + it('should make successful GET request and parse JSON', async () => { + const mockResponse = { + ok: true, + json: jest.fn().mockResolvedValue({ data: 'test' }), + }; + mockFetch.mockResolvedValue(mockResponse as any); + + const result = await httpJson('https://example.com/api'); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://example.com/api', + expect.objectContaining({ + body: undefined, + }), + ); + expect(result).toEqual({ data: 'test' }); + }); + + it('should make POST request with JSON body', async () => { + const mockResponse = { + ok: true, + json: jest.fn().mockResolvedValue({ success: true }), + }; + mockFetch.mockResolvedValue(mockResponse as any); + + const body = { username: 'test', password: 'secret' }; + const result = await httpJson('https://example.com/api', { + method: 'POST', + body, + }); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://example.com/api', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify(body), + headers: { + 'Content-Type': 'application/json', + }, + }), + ); + expect(result).toEqual({ success: true }); + }); + + it('should include and merge custom headers', async () => { + const mockResponse = { + ok: true, + json: jest.fn().mockResolvedValue({ data: 'test' }), + }; + mockFetch.mockResolvedValue(mockResponse as any); + + // Test custom headers without body + await httpJson('https://example.com/api', { + headers: { + Authorization: 'Bearer token', + 'X-Custom': 'value', + }, + }); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://example.com/api', + expect.objectContaining({ + headers: { + Authorization: 'Bearer token', + 'X-Custom': 'value', + }, + }), + ); + + // Test merging headers with content-type when body is present + await httpJson('https://example.com/api', { + method: 'POST', + body: { data: 'test' }, + headers: { + Authorization: 'Bearer token', + }, + }); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://example.com/api', + expect.objectContaining({ + headers: { + Authorization: 'Bearer token', + 'Content-Type': 'application/json', + }, + }), + ); + }); + + it('should throw ResponseError for non-ok responses', async () => { + const errorCases = [ + { status: 404, statusText: 'Not Found' }, + { status: 401, statusText: 'Unauthorized' }, + { status: 500, statusText: 'Internal Server Error' }, + ]; + + for (const { status, statusText } of errorCases) { + const mockResponse = { + ok: false, + status, + statusText, + url: 'https://example.com/api', + text: jest.fn().mockResolvedValue('Error'), + }; + mockFetch.mockResolvedValue(mockResponse as any); + + await expect(httpJson('https://example.com/api')).rejects.toThrow( + `Request failed with ${status} ${statusText}`, + ); + } + }); + + it('should pass through abort signal from caller', async () => { + const abortController = new AbortController(); + let rejectFn: (error: Error) => void; + const mockResponse = new Promise((_, reject) => { + rejectFn = reject; + setTimeout(() => { + reject(new Error('Request should have been aborted')); + }, 60000); // 60 seconds + }); + + mockFetch.mockImplementation((_url, options) => { + const signal = options?.signal as AbortSignal; + signal?.addEventListener('abort', () => { + rejectFn(new Error('The operation was aborted')); + }); + return mockResponse as any; + }); + + const requestPromise = httpJson('https://example.com/api', { + signal: abortController.signal, + }); + + // Abort the request + abortController.abort(); + + await expect(requestPromise).rejects.toThrow('The operation was aborted'); + }); + + it('should handle JSON parsing errors gracefully', async () => { + const mockResponse = { + ok: true, + json: jest.fn().mockRejectedValue(new Error('Invalid JSON')), + }; + mockFetch.mockResolvedValue(mockResponse as any); + + await expect(httpJson('https://example.com/api')).rejects.toThrow( + 'Invalid JSON', + ); + }); + + it('should support different HTTP methods', async () => { + const mockResponse = { + ok: true, + json: jest.fn().mockResolvedValue({ success: true }), + }; + + for (const method of ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']) { + mockFetch.mockResolvedValue(mockResponse as any); + + await httpJson('https://example.com/api', { method }); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://example.com/api', + expect.objectContaining({ + method, + }), + ); + } + }); + + it('should handle various response body types', async () => { + const testCases = [ + { body: null, expected: null }, + { body: [1, 2, 3], expected: [1, 2, 3] }, + { body: { data: 'test' }, expected: { data: 'test' } }, + ]; + + for (const { body, expected } of testCases) { + const mockResponse = { + ok: true, + json: jest.fn().mockResolvedValue(body), + }; + mockFetch.mockResolvedValue(mockResponse as any); + + const result = await httpJson('https://example.com/api'); + expect(result).toEqual(expected); + } + }); + + it('should handle network errors', async () => { + const networkError = new Error('Network error'); + mockFetch.mockRejectedValue(networkError); + + await expect(httpJson('https://example.com/api')).rejects.toThrow( + 'Network error', + ); + }); + + it('should use custom abort signal if provided', async () => { + const mockResponse = { + ok: true, + json: jest.fn().mockResolvedValue({ data: 'test' }), + }; + mockFetch.mockResolvedValue(mockResponse as any); + + const customController = new AbortController(); + await httpJson('https://example.com/api', { + signal: customController.signal, + }); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://example.com/api', + expect.objectContaining({ + signal: expect.any(AbortSignal), + }), + ); + }); + + it('should handle malformed URLs gracefully', async () => { + const networkError = new TypeError('Failed to parse URL'); + mockFetch.mockRejectedValue(networkError); + + await expect(httpJson('not-a-valid-url')).rejects.toThrow(); + }); + + it('should handle very large response bodies', async () => { + const largeData = { items: Array(10000).fill({ data: 'x'.repeat(100) }) }; + const mockResponse = { + ok: true, + json: jest.fn().mockResolvedValue(largeData), + }; + mockFetch.mockResolvedValue(mockResponse as any); + + const result = await httpJson('https://example.com/api'); + expect(result).toEqual(largeData); + }); + }); +}); diff --git a/packages/cli/src/modules/auth/lib/http.ts b/packages/cli/src/modules/auth/lib/http.ts new file mode 100644 index 0000000000..307d7d2334 --- /dev/null +++ b/packages/cli/src/modules/auth/lib/http.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fetch from 'cross-fetch'; +import { ResponseError } from '@backstage/errors'; + +type HttpInit = { + headers?: Record; + method?: string; + body?: any; + signal?: AbortSignal; +}; + +export async function httpJson(url: string, init?: HttpInit): Promise { + const res = await fetch(url, { + ...init, + body: init?.body ? JSON.stringify(init.body) : undefined, + headers: { + ...(init?.body ? { 'Content-Type': 'application/json' } : {}), + ...init?.headers, + }, + }); + if (!res.ok) { + throw await ResponseError.fromResponse(res); + } + return (await res.json()) as T; +} diff --git a/packages/cli/src/modules/auth/lib/localServer.test.ts b/packages/cli/src/modules/auth/lib/localServer.test.ts new file mode 100644 index 0000000000..3b03331d3e --- /dev/null +++ b/packages/cli/src/modules/auth/lib/localServer.test.ts @@ -0,0 +1,65 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { startCallbackServer } from './localServer'; + +describe('localServer', () => { + it('should start on port 8055, handle requests, and resolve the code', async () => { + const { url, waitForCode, close } = await startCallbackServer({ + state: 'test-state', + }); + + expect(url).toBe('http://127.0.0.1:8055/callback'); + + // 404 for non-callback paths + const notFoundResponse = await fetch( + url.replace('/callback', '/other-path'), + ); + expect(notFoundResponse.status).toBe(404); + + // 400 for missing code + const missingCodeResponse = await fetch(`${url}?state=test-state`); + expect(missingCodeResponse.status).toBe(400); + expect(await missingCodeResponse.text()).toBe('Missing code'); + + // 400 for mismatched state + const mismatchResponse = await fetch( + `${url}?code=test-code&state=wrong-state`, + ); + expect(mismatchResponse.status).toBe(400); + expect(await mismatchResponse.text()).toBe('State mismatch'); + + // 200 for valid callback with matching state + const codePromise = waitForCode(); + const specialCode = 'test-code+with/special=chars'; + const successResponse = await fetch( + `${url}?code=${encodeURIComponent( + specialCode, + )}&state=${encodeURIComponent('test-state')}`, + ); + expect(successResponse.status).toBe(200); + expect(await successResponse.text()).toBe('You may now close this window.'); + expect(successResponse.headers.get('content-type')).toBe( + 'text/plain; charset=utf-8', + ); + + const result = await codePromise; + expect(result.code).toBe(specialCode); + expect(result.state).toBe('test-state'); + + await close(); + }); +}); diff --git a/packages/cli/src/modules/auth/lib/localServer.ts b/packages/cli/src/modules/auth/lib/localServer.ts new file mode 100644 index 0000000000..0b71075a5b --- /dev/null +++ b/packages/cli/src/modules/auth/lib/localServer.ts @@ -0,0 +1,98 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import http from 'node:http'; +import { URL } from 'node:url'; + +const CALLBACK_PORT = 8055; + +export async function startCallbackServer(options: { state: string }): Promise<{ + url: string; + waitForCode: () => Promise<{ code: string; state?: string }>; + close: () => Promise; +}> { + const server = http.createServer(); + + let resolveResult: + | ((v: { code: string; state?: string }) => void) + | undefined; + const resultPromise = new Promise<{ code: string; state?: string }>( + resolve => { + resolveResult = resolve; + }, + ); + + server.on('request', (req, res) => { + if (!req.url) { + res.statusCode = 400; + res.end('Bad Request'); + return; + } + const u = new URL(req.url, 'http://127.0.0.1'); + if (u.pathname !== '/callback') { + res.statusCode = 404; + res.end('Not Found'); + return; + } + const code = u.searchParams.get('code') ?? undefined; + const state = u.searchParams.get('state') ?? undefined; + if (!code) { + res.statusCode = 400; + res.end('Missing code'); + return; + } + if (state !== options.state) { + res.statusCode = 400; + res.end('State mismatch'); + return; + } + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain; charset=utf-8'); + res.end('You may now close this window.'); + resolveResult?.({ code, state }); + }); + + const port = await new Promise((resolve, reject) => { + server.on('error', (err: NodeJS.ErrnoException) => { + if (err.code === 'EADDRINUSE') { + reject( + new Error( + `Port ${CALLBACK_PORT} is already in use. Close the application using it and try again.`, + ), + ); + } else { + reject(err); + } + }); + server.listen(CALLBACK_PORT, '127.0.0.1', () => { + const address = server.address(); + if (typeof address === 'object' && address && 'port' in address) { + resolve(address.port); + } else { + reject(new Error('Failed to bind local server')); + } + }); + }); + + return { + url: `http://127.0.0.1:${port}/callback`, + waitForCode: () => resultPromise, + close: async () => { + server.closeAllConnections(); + return new Promise(resolve => server.close(() => resolve())); + }, + }; +} diff --git a/packages/cli/src/modules/auth/lib/pkce.test.ts b/packages/cli/src/modules/auth/lib/pkce.test.ts new file mode 100644 index 0000000000..09db60745a --- /dev/null +++ b/packages/cli/src/modules/auth/lib/pkce.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 crypto from 'node:crypto'; +import { generateVerifier, challengeFromVerifier } from './pkce'; + +describe('pkce', () => { + describe('generateVerifier', () => { + it('should generate verifiers with proper encoding and length', () => { + // Test default length + const defaultVerifier = generateVerifier(); + expect(defaultVerifier).toBeDefined(); + expect(typeof defaultVerifier).toBe('string'); + expect(defaultVerifier.length).toBeGreaterThan(0); + + // Test custom lengths + const shortVerifier = generateVerifier(32); + const longVerifier = generateVerifier(96); + expect(shortVerifier).toBeDefined(); + expect(longVerifier).toBeDefined(); + expect(shortVerifier.length).toBeGreaterThan(0); + expect(longVerifier.length).toBeGreaterThan(shortVerifier.length); + + // Test base64url encoding (no padding, proper characters) + const verifier = generateVerifier(); + expect(verifier).not.toContain('='); + expect(verifier).not.toContain('+'); + expect(verifier).not.toContain('/'); + expect(verifier).toMatch(/^[A-Za-z0-9_-]+$/); + + // Test uniqueness + const verifier1 = generateVerifier(); + const verifier2 = generateVerifier(); + expect(verifier1).not.toBe(verifier2); + }); + + it('should enforce minimum and maximum length constraints', () => { + // Test minimum length enforcement + const minVerifier = generateVerifier(10); // Less than minimum + expect(minVerifier).toBeDefined(); + expect(minVerifier.length).toBeGreaterThanOrEqual(43); // 32 bytes = 43 base64url chars + + // Test maximum length enforcement + const maxVerifier = generateVerifier(200); // More than maximum + expect(maxVerifier).toBeDefined(); + expect(maxVerifier.length).toBeLessThanOrEqual(128); // 96 bytes = 128 base64url chars + }); + + it('should produce consistent results for same byte sequence', () => { + // Mock crypto.randomBytes to return predictable values + const mockBytes = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]); + jest.spyOn(crypto, 'randomBytes').mockImplementation(_count => mockBytes); + + const verifier1 = generateVerifier(8); + const verifier2 = generateVerifier(8); + + expect(verifier1).toBe(verifier2); + + (crypto.randomBytes as jest.Mock).mockRestore(); + }); + }); + + describe('challengeFromVerifier', () => { + it('should generate challenges with proper encoding and consistency', () => { + const verifier = 'test-verifier-string'; + const challenge = challengeFromVerifier(verifier); + + // Basic properties + expect(challenge).toBeDefined(); + expect(typeof challenge).toBe('string'); + expect(challenge.length).toBe(43); // SHA-256 = 32 bytes = 43 base64url chars + + // Base64url encoding (no padding, proper characters) + expect(challenge).not.toContain('='); + expect(challenge).not.toContain('+'); + expect(challenge).not.toContain('/'); + expect(challenge).toMatch(/^[A-Za-z0-9_-]+$/); + + // Consistency for same verifier + const challenge1 = challengeFromVerifier(verifier); + const challenge2 = challengeFromVerifier(verifier); + expect(challenge1).toBe(challenge2); + expect(challenge1).toBe(challenge); + + // Different challenges for different verifiers + const verifier2 = 'test-verifier-2'; + const challenge3 = challengeFromVerifier(verifier2); + expect(challenge3).not.toBe(challenge); + }); + + it('should handle edge cases for verifier length', () => { + // Empty verifier + const emptyChallenge = challengeFromVerifier(''); + expect(emptyChallenge).toBeDefined(); + expect(emptyChallenge.length).toBe(43); + + // Very long verifier + const longVerifier = 'a'.repeat(1000); + const longChallenge = challengeFromVerifier(longVerifier); + expect(longChallenge).toBeDefined(); + expect(longChallenge.length).toBe(43); // SHA-256 always produces 32 bytes + }); + + it('should produce RFC 7636 compliant challenge', () => { + // Test with a known verifier + const verifier = generateVerifier(); + const challenge = challengeFromVerifier(verifier); + + // Verify it's using SHA-256 correctly + const expectedHash = crypto + .createHash('sha256') + .update(verifier) + .digest(); + const expectedChallenge = expectedHash + .toString('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + + expect(challenge).toBe(expectedChallenge); + }); + }); + + describe('PKCE flow integration', () => { + it('should generate valid verifier and challenge pair', () => { + const verifier = generateVerifier(); + const challenge = challengeFromVerifier(verifier); + + expect(verifier).toBeDefined(); + expect(challenge).toBeDefined(); + expect(verifier).not.toBe(challenge); + + // Verifier should be longer than challenge + expect(verifier.length).toBeGreaterThan(challenge.length); + }); + + it('should generate multiple unique pairs', () => { + const pair1 = { + verifier: generateVerifier(), + challenge: '', + }; + pair1.challenge = challengeFromVerifier(pair1.verifier); + + const pair2 = { + verifier: generateVerifier(), + challenge: '', + }; + pair2.challenge = challengeFromVerifier(pair2.verifier); + + expect(pair1.verifier).not.toBe(pair2.verifier); + expect(pair1.challenge).not.toBe(pair2.challenge); + }); + + it('should maintain one-to-one mapping between verifier and challenge', () => { + const verifier = generateVerifier(); + const challenge1 = challengeFromVerifier(verifier); + const challenge2 = challengeFromVerifier(verifier); + const challenge3 = challengeFromVerifier(verifier); + + // All challenges from same verifier should be identical + expect(challenge1).toBe(challenge2); + expect(challenge2).toBe(challenge3); + }); + }); +}); diff --git a/packages/cli/src/modules/auth/lib/pkce.ts b/packages/cli/src/modules/auth/lib/pkce.ts new file mode 100644 index 0000000000..fd5bba2d70 --- /dev/null +++ b/packages/cli/src/modules/auth/lib/pkce.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import crypto from 'node:crypto'; + +function base64url(input: Buffer): string { + return input + .toString('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); +} + +export function generateVerifier(length = 64): string { + // length in bytes ~ 48 results in 64 base64url chars; keep within 43..128 chars + const bytes = crypto.randomBytes(Math.max(32, Math.min(96, length))); + return base64url(bytes); +} + +export function challengeFromVerifier(verifier: string): string { + const hash = crypto.createHash('sha256').update(verifier).digest(); + return base64url(hash); +} diff --git a/packages/cli/src/modules/auth/lib/prompt.test.ts b/packages/cli/src/modules/auth/lib/prompt.test.ts new file mode 100644 index 0000000000..acb28fc124 --- /dev/null +++ b/packages/cli/src/modules/auth/lib/prompt.test.ts @@ -0,0 +1,191 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import inquirer from 'inquirer'; +import { pickInstance } from './prompt'; +import * as storage from './storage'; + +jest.mock('inquirer'); +jest.mock('./storage'); + +const mockStorage = storage as jest.Mocked; +const mockInquirer = inquirer as jest.Mocked; + +describe('prompt', () => { + describe('pickInstance', () => { + const mockInstances = [ + { + name: 'production', + baseUrl: 'https://backstage.example.com', + clientId: 'prod-client', + issuedAt: Date.now(), + accessToken: 'prod-token', + accessTokenExpiresAt: Date.now() + 3600_000, + selected: true, + }, + { + name: 'staging', + baseUrl: 'https://staging.backstage.example.com', + clientId: 'staging-client', + issuedAt: Date.now(), + accessToken: 'staging-token', + accessTokenExpiresAt: Date.now() + 3600_000, + selected: false, + }, + { + name: 'local', + baseUrl: 'http://localhost:7007', + clientId: 'local-client', + issuedAt: Date.now(), + accessToken: 'local-token', + accessTokenExpiresAt: Date.now() + 3600_000, + selected: false, + }, + ]; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return instance by name if provided', async () => { + mockStorage.getInstanceByName.mockResolvedValue(mockInstances[1]); + + const result = await pickInstance('staging'); + + expect(result).toEqual(mockInstances[1]); + expect(mockStorage.getInstanceByName).toHaveBeenCalledWith('staging'); + expect(mockInquirer.prompt).not.toHaveBeenCalled(); + }); + + it('should prompt for instance and show selected instance with asterisk prefix', async () => { + // Test with production selected + mockStorage.getAllInstances.mockResolvedValue({ + instances: mockInstances, + selected: mockInstances[0], + }); + mockInquirer.prompt.mockResolvedValue({ choice: 'staging' }); + + const result = await pickInstance(); + + expect(mockInquirer.prompt).toHaveBeenCalledWith([ + { + type: 'list', + name: 'choice', + message: 'Select instance:', + choices: [ + { + name: '* production (https://backstage.example.com)', + value: 'production', + }, + { + name: ' staging (https://staging.backstage.example.com)', + value: 'staging', + }, + { + name: ' local (http://localhost:7007)', + value: 'local', + }, + ], + default: 'production', + }, + ]); + expect(result).toEqual(mockInstances[1]); + + // Test with staging selected + mockStorage.getAllInstances.mockResolvedValue({ + instances: mockInstances, + selected: mockInstances[1], + }); + mockInquirer.prompt.mockResolvedValue({ choice: 'staging' }); + + await pickInstance(); + + expect(mockInquirer.prompt).toHaveBeenCalledWith([ + expect.objectContaining({ + choices: [ + { + name: ' production (https://backstage.example.com)', + value: 'production', + }, + { + name: '* staging (https://staging.backstage.example.com)', + value: 'staging', + }, + { + name: ' local (http://localhost:7007)', + value: 'local', + }, + ], + default: 'staging', + }), + ]); + }); + + it('should throw error if no instances are available', async () => { + mockStorage.getAllInstances.mockResolvedValue({ + instances: [], + selected: undefined, + }); + + await expect(pickInstance()).rejects.toThrow( + 'No instances found. Run "auth login" to authenticate first.', + ); + }); + + it('should throw error if selected instance is not found', async () => { + mockStorage.getAllInstances.mockResolvedValue({ + instances: mockInstances, + selected: mockInstances[0], + }); + mockInquirer.prompt.mockResolvedValue({ choice: 'non-existent' }); + + await expect(pickInstance()).rejects.toThrow( + "Instance 'non-existent' not found", + ); + }); + + it('should handle single instance and use selected instance as default', async () => { + // Test single instance + const singleInstance = [mockInstances[0]]; + mockStorage.getAllInstances.mockResolvedValue({ + instances: singleInstance, + selected: mockInstances[0], + }); + mockInquirer.prompt.mockResolvedValue({ choice: 'production' }); + + const result = await pickInstance(); + + expect(result).toEqual(mockInstances[0]); + expect(mockInquirer.prompt).toHaveBeenCalled(); + + // Test default selection matches selected instance + const selectedInstance = mockInstances[2]; + mockStorage.getAllInstances.mockResolvedValue({ + instances: mockInstances, + selected: selectedInstance, + }); + mockInquirer.prompt.mockResolvedValue({ choice: 'local' }); + + await pickInstance(); + + expect(mockInquirer.prompt).toHaveBeenCalledWith([ + expect.objectContaining({ + default: 'local', + }), + ]); + }); + }); +}); diff --git a/packages/cli/src/modules/auth/lib/prompt.ts b/packages/cli/src/modules/auth/lib/prompt.ts new file mode 100644 index 0000000000..46a7430548 --- /dev/null +++ b/packages/cli/src/modules/auth/lib/prompt.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import inquirer from 'inquirer'; +import { getInstanceByName, getAllInstances, StoredInstance } from './storage'; + +export async function pickInstance(name?: string): Promise { + if (name) { + return getInstanceByName(name); + } + + const { instances, selected } = await getAllInstances(); + if (instances.length === 0) { + throw new Error( + 'No instances found. Run "auth login" to authenticate first.', + ); + } + return await promptForInstance(instances, selected); +} + +async function promptForInstance( + instances: StoredInstance[], + selected: StoredInstance | undefined, +): Promise { + const choices = instances.map(i => ({ + name: `${i.name === selected?.name ? '* ' : ' '}${i.name} (${i.baseUrl})`, + value: i.name, + })); + + const { choice } = await inquirer.prompt<{ choice: string }>([ + { + type: 'list', + name: 'choice', + message: 'Select instance:', + choices, + default: selected?.name, + }, + ]); + + const instance = instances.find(i => i.name === choice); + if (!instance) { + throw new Error(`Instance '${choice}' not found`); + } + return instance; +} diff --git a/packages/cli/src/modules/auth/lib/secretStore.test.ts b/packages/cli/src/modules/auth/lib/secretStore.test.ts new file mode 100644 index 0000000000..6729f7c6a5 --- /dev/null +++ b/packages/cli/src/modules/auth/lib/secretStore.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. + */ + +jest.mock('keytar', () => { + throw new Error('keytar not available'); +}); + +import fs from 'fs-extra'; +import path from 'node:path'; +import { createMockDirectory } from '@backstage/backend-test-utils'; +import { getSecretStore, resetSecretStore } from './secretStore'; + +const mockDir = createMockDirectory(); + +describe('secretStore', () => { + beforeEach(() => { + mockDir.clear(); + process.env.XDG_DATA_HOME = mockDir.resolve('data'); + resetSecretStore(); + }); + + afterEach(() => { + delete process.env.XDG_DATA_HOME; + resetSecretStore(); + }); + + describe('FileSecretStore', () => { + it('should store and retrieve secrets', async () => { + const store = await getSecretStore(); + await store.set('test-service', 'test-account', 'test-secret'); + const result = await store.get('test-service', 'test-account'); + + expect(result).toBe('test-secret'); + }); + + it('should return undefined for non-existent secrets', async () => { + const store = await getSecretStore(); + const result = await store.get('test-service', 'test-account'); + + expect(result).toBeUndefined(); + }); + + it('should delete secrets', async () => { + const store = await getSecretStore(); + await store.set('test-service', 'test-account', 'test-secret'); + + let result = await store.get('test-service', 'test-account'); + expect(result).toBe('test-secret'); + + await store.delete('test-service', 'test-account'); + + result = await store.get('test-service', 'test-account'); + expect(result).toBeUndefined(); + }); + + it('should not throw when deleting non-existent secrets', async () => { + const store = await getSecretStore(); + + await expect( + store.delete('non-existent-service', 'non-existent-account'), + ).resolves.not.toThrow(); + }); + + it('should create files with correct directory structure', async () => { + const store = await getSecretStore(); + await store.set('test-service', 'test-account', 'test-secret'); + + const expectedDir = path.join( + mockDir.resolve('data'), + 'backstage-cli', + 'auth-secrets', + encodeURIComponent('test-service'), + ); + const expectedFile = path.join( + expectedDir, + `${encodeURIComponent('test-account')}.secret`, + ); + + expect(await fs.pathExists(expectedFile)).toBe(true); + expect(await fs.pathExists(expectedDir)).toBe(true); + }); + + it('should create files with correct permissions (0o600)', async () => { + // File permissions are not reliably enforced on Windows + if (process.platform === 'win32') { + return; + } + + const store = await getSecretStore(); + await store.set('test-service', 'test-account', 'test-secret'); + + const expectedFile = path.join( + mockDir.resolve('data'), + 'backstage-cli', + 'auth-secrets', + encodeURIComponent('test-service'), + `${encodeURIComponent('test-account')}.secret`, + ); + + const stats = await fs.stat(expectedFile); + const mode = stats.mode & 0o777; + expect(mode).toBe(0o600); + }); + + it('should encode service and account names in file path', async () => { + const store = await getSecretStore(); + await store.set('my-service/test', 'my-account@test', 'test-secret'); + + const result = await store.get('my-service/test', 'my-account@test'); + expect(result).toBe('test-secret'); + + const expectedFile = path.join( + mockDir.resolve('data'), + 'backstage-cli', + 'auth-secrets', + encodeURIComponent('my-service/test'), + `${encodeURIComponent('my-account@test')}.secret`, + ); + expect(await fs.pathExists(expectedFile)).toBe(true); + }); + + it('should handle unicode characters in service and account names', async () => { + const store = await getSecretStore(); + await store.set('service-æĩ‹č¯•', 'account-🚀', 'test-secret'); + + const result = await store.get('service-æĩ‹č¯•', 'account-🚀'); + expect(result).toBe('test-secret'); + }); + + it('should handle multiple secrets for same service', async () => { + const store = await getSecretStore(); + await store.set('test-service', 'account1', 'secret1'); + await store.set('test-service', 'account2', 'secret2'); + + const result1 = await store.get('test-service', 'account1'); + const result2 = await store.get('test-service', 'account2'); + + expect(result1).toBe('secret1'); + expect(result2).toBe('secret2'); + }); + + it('should handle multiple services', async () => { + const store = await getSecretStore(); + await store.set('service1', 'account', 'secret1'); + await store.set('service2', 'account', 'secret2'); + + const result1 = await store.get('service1', 'account'); + const result2 = await store.get('service2', 'account'); + + expect(result1).toBe('secret1'); + expect(result2).toBe('secret2'); + }); + + it('should update existing secrets', async () => { + const store = await getSecretStore(); + await store.set('test-service', 'test-account', 'old-secret'); + await store.set('test-service', 'test-account', 'new-secret'); + + const result = await store.get('test-service', 'test-account'); + expect(result).toBe('new-secret'); + }); + + it('should handle empty string secrets', async () => { + const store = await getSecretStore(); + await store.set('test-service', 'test-account', ''); + + const result = await store.get('test-service', 'test-account'); + expect(result).toBe(''); + }); + + it('should handle very long secrets', async () => { + const store = await getSecretStore(); + const longSecret = 'a'.repeat(10000); + await store.set('test-service', 'test-account', longSecret); + + const result = await store.get('test-service', 'test-account'); + expect(result).toBe(longSecret); + }); + + it('should use XDG_DATA_HOME when set', async () => { + const customDataHome = mockDir.resolve('custom-data'); + process.env.XDG_DATA_HOME = customDataHome; + resetSecretStore(); + + const store = await getSecretStore(); + await store.set('test-service', 'test-account', 'test-secret'); + + const expectedFile = path.join( + customDataHome, + 'backstage-cli', + 'auth-secrets', + encodeURIComponent('test-service'), + `${encodeURIComponent('test-account')}.secret`, + ); + expect(await fs.pathExists(expectedFile)).toBe(true); + + const result = await store.get('test-service', 'test-account'); + expect(result).toBe('test-secret'); + }); + }); + + describe('getSecretStore singleton', () => { + it('should return the same instance on multiple calls', async () => { + const store1 = await getSecretStore(); + const store2 = await getSecretStore(); + + expect(store1).toBe(store2); + }); + + it('should create new instance after reset', async () => { + const store1 = await getSecretStore(); + resetSecretStore(); + const store2 = await getSecretStore(); + + expect(store1).not.toBe(store2); + }); + }); + + describe('fallback behavior', () => { + it('should fall back to FileSecretStore when keytar is not available', async () => { + const store = await getSecretStore(); + await store.set('test-service', 'test-account', 'test-secret'); + + const result = await store.get('test-service', 'test-account'); + expect(result).toBe('test-secret'); + + const expectedFile = path.join( + mockDir.resolve('data'), + 'backstage-cli', + 'auth-secrets', + encodeURIComponent('test-service'), + `${encodeURIComponent('test-account')}.secret`, + ); + expect(await fs.pathExists(expectedFile)).toBe(true); + }); + }); +}); diff --git a/packages/cli/src/modules/auth/lib/secretStore.ts b/packages/cli/src/modules/auth/lib/secretStore.ts new file mode 100644 index 0000000000..55fac878b7 --- /dev/null +++ b/packages/cli/src/modules/auth/lib/secretStore.ts @@ -0,0 +1,110 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from 'fs-extra'; +import os from 'node:os'; +import path from 'node:path'; + +type SecretStore = { + get(service: string, account: string): Promise; + set(service: string, account: string, secret: string): Promise; + delete(service: string, account: string): Promise; +}; + +async function loadKeytar(): Promise { + try { + // eslint-disable-next-line import/no-extraneous-dependencies, @backstage/no-undeclared-imports + const keytar = require('keytar') as typeof import('keytar'); + if (keytar && typeof keytar.getPassword === 'function') { + return keytar; + } + } catch { + // keytar not available + } + return undefined; +} + +class KeytarSecretStore implements SecretStore { + private readonly keytar: typeof import('keytar'); + constructor(keytar: typeof import('keytar')) { + this.keytar = keytar; + } + async get(service: string, account: string): Promise { + const result = await this.keytar.getPassword(service, account); + return result ?? undefined; + } + async set(service: string, account: string, secret: string): Promise { + await this.keytar.setPassword(service, account, secret); + } + async delete(service: string, account: string): Promise { + await this.keytar.deletePassword(service, account); + } +} + +class FileSecretStore implements SecretStore { + private readonly baseDir: string; + constructor() { + const root = + process.env.XDG_DATA_HOME || + (process.platform === 'win32' + ? process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming') + : path.join(os.homedir(), '.local', 'share')); + this.baseDir = path.join(root, 'backstage-cli', 'auth-secrets'); + } + private filePath(service: string, account: string): string { + return path.join( + this.baseDir, + encodeURIComponent(service), + `${encodeURIComponent(account)}.secret`, + ); + } + async get(service: string, account: string): Promise { + const file = this.filePath(service, account); + if (!(await fs.pathExists(file))) return undefined; + return await fs.readFile(file, 'utf8'); + } + async set(service: string, account: string, secret: string): Promise { + const file = this.filePath(service, account); + await fs.ensureDir(path.dirname(file)); + await fs.writeFile(file, secret, { encoding: 'utf8', mode: 0o600 }); + } + async delete(service: string, account: string): Promise { + const file = this.filePath(service, account); + await fs.remove(file); + } +} + +let singleton: SecretStore | undefined; + +export async function getSecretStore(): Promise { + if (!singleton) { + const keytar = await loadKeytar(); + if (keytar) { + singleton = new KeytarSecretStore(keytar); + } else { + singleton = new FileSecretStore(); + } + } + return singleton; +} + +/** + * Reset the singleton instance (for testing purposes only) + * @internal + */ +export function resetSecretStore(): void { + singleton = undefined; +} diff --git a/packages/cli/src/modules/auth/lib/storage.test.ts b/packages/cli/src/modules/auth/lib/storage.test.ts new file mode 100644 index 0000000000..07ffba9510 --- /dev/null +++ b/packages/cli/src/modules/auth/lib/storage.test.ts @@ -0,0 +1,420 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from 'fs-extra'; +import path from 'node:path'; +import { NotFoundError } from '@backstage/errors'; +import { createMockDirectory } from '@backstage/backend-test-utils'; +import { + getAllInstances, + getSelectedInstance, + getInstanceByName, + upsertInstance, + removeInstance, + setSelectedInstance, + withMetadataLock, + StoredInstance, +} from './storage'; + +const mockDir = createMockDirectory(); + +describe('storage', () => { + const mockInstance1: StoredInstance = { + name: 'production', + baseUrl: 'https://backstage.example.com', + clientId: 'prod-client', + issuedAt: Date.now(), + accessTokenExpiresAt: Date.now() + 3600_000, + selected: true, + }; + + const mockInstance2: StoredInstance = { + name: 'staging', + baseUrl: 'https://staging.backstage.example.com', + clientId: 'staging-client', + issuedAt: Date.now(), + accessTokenExpiresAt: Date.now() + 3600_000, + selected: false, + }; + + beforeEach(() => { + mockDir.clear(); + process.env.XDG_CONFIG_HOME = mockDir.resolve('config'); + }); + + afterEach(() => { + delete process.env.XDG_CONFIG_HOME; + }); + + describe('getAllInstances', () => { + it('should return empty array if file does not exist or is empty', async () => { + const result1 = await getAllInstances(); + expect(result1).toEqual({ instances: [], selected: undefined }); + + mockDir.setContent({ + 'config/backstage-cli/auth-instances.yaml': '', + }); + + const result2 = await getAllInstances(); + expect(result2).toEqual({ instances: [], selected: undefined }); + }); + + it('should parse and return instances from YAML', async () => { + mockDir.setContent({ + 'config/backstage-cli/auth-instances.yaml': `instances: + - name: production + baseUrl: https://backstage.example.com + clientId: prod-client + issuedAt: ${mockInstance1.issuedAt} + + accessTokenExpiresAt: ${mockInstance1.accessTokenExpiresAt} + selected: true + - name: staging + baseUrl: https://staging.backstage.example.com + clientId: staging-client + issuedAt: ${mockInstance2.issuedAt} + + accessTokenExpiresAt: ${mockInstance2.accessTokenExpiresAt} +`, + }); + + const result = await getAllInstances(); + + expect(result.instances).toHaveLength(2); + expect(result.selected?.name).toBe('production'); + }); + + it('should select first instance if none marked as selected', async () => { + mockDir.setContent({ + 'config/backstage-cli/auth-instances.yaml': `instances: + - name: production + baseUrl: https://backstage.example.com + clientId: prod-client + issuedAt: ${mockInstance1.issuedAt} + + accessTokenExpiresAt: ${mockInstance1.accessTokenExpiresAt} + - name: staging + baseUrl: https://staging.backstage.example.com + clientId: staging-client + issuedAt: ${mockInstance2.issuedAt} + + accessTokenExpiresAt: ${mockInstance2.accessTokenExpiresAt} +`, + }); + + const result = await getAllInstances(); + + expect(result.selected?.name).toBe('production'); + }); + + it('should return empty array if YAML parsing fails', async () => { + mockDir.setContent({ + 'config/backstage-cli/auth-instances.yaml': 'invalid: yaml: [', + }); + + const result = await getAllInstances(); + + expect(result).toEqual({ instances: [], selected: undefined }); + }); + + it('should normalize selected property across instances', async () => { + mockDir.setContent({ + 'config/backstage-cli/auth-instances.yaml': `instances: + - name: production + baseUrl: https://backstage.example.com + clientId: prod-client + issuedAt: ${mockInstance1.issuedAt} + + accessTokenExpiresAt: ${mockInstance1.accessTokenExpiresAt} + selected: true + - name: staging + baseUrl: https://staging.backstage.example.com + clientId: staging-client + issuedAt: ${mockInstance2.issuedAt} + + accessTokenExpiresAt: ${mockInstance2.accessTokenExpiresAt} + selected: true +`, + }); + + const result = await getAllInstances(); + + const selectedCount = result.instances.filter(i => i.selected).length; + expect(selectedCount).toBe(1); + }); + }); + + describe('getSelectedInstance', () => { + it('should return instance by name if provided', async () => { + mockDir.setContent({ + 'config/backstage-cli/auth-instances.yaml': `instances: + - name: production + baseUrl: https://backstage.example.com + clientId: prod-client + issuedAt: ${mockInstance1.issuedAt} + + accessTokenExpiresAt: ${mockInstance1.accessTokenExpiresAt} +`, + }); + + const result = await getSelectedInstance('production'); + + expect(result.name).toBe('production'); + }); + + it('should return selected instance if no name provided', async () => { + mockDir.setContent({ + 'config/backstage-cli/auth-instances.yaml': `instances: + - name: production + baseUrl: https://backstage.example.com + clientId: prod-client + issuedAt: ${mockInstance1.issuedAt} + + accessTokenExpiresAt: ${mockInstance1.accessTokenExpiresAt} + - name: staging + baseUrl: https://staging.backstage.example.com + clientId: staging-client + issuedAt: ${mockInstance2.issuedAt} + + accessTokenExpiresAt: ${mockInstance2.accessTokenExpiresAt} + selected: true +`, + }); + + const result = await getSelectedInstance(); + + expect(result.name).toBe('staging'); + }); + + it('should throw error if no instances exist', async () => { + await expect(getSelectedInstance()).rejects.toThrow( + 'No instances found. Run "auth login" to authenticate first.', + ); + }); + }); + + describe('getInstanceByName', () => { + it('should return instance with matching name', async () => { + mockDir.setContent({ + 'config/backstage-cli/auth-instances.yaml': `instances: + - name: production + baseUrl: https://backstage.example.com + clientId: prod-client + issuedAt: ${mockInstance1.issuedAt} + + accessTokenExpiresAt: ${mockInstance1.accessTokenExpiresAt} +`, + }); + + const result = await getInstanceByName('production'); + + expect(result.name).toBe('production'); + }); + + it('should throw NotFoundError if instance does not exist', async () => { + await expect(getInstanceByName('nonexistent')).rejects.toThrow( + NotFoundError, + ); + await expect(getInstanceByName('nonexistent')).rejects.toThrow( + "Instance 'nonexistent' not found", + ); + }); + }); + + describe('upsertInstance', () => { + it('should add new instance if it does not exist', async () => { + await upsertInstance(mockInstance1); + + const result = await getAllInstances(); + expect(result.instances).toHaveLength(1); + expect(result.instances[0].name).toBe('production'); + }); + + it('should update existing instance', async () => { + mockDir.setContent({ + 'config/backstage-cli/auth-instances.yaml': `instances: + - name: production + baseUrl: https://backstage.example.com + clientId: prod-client + issuedAt: ${mockInstance1.issuedAt} + accessTokenExpiresAt: ${mockInstance1.accessTokenExpiresAt} +`, + }); + + const updatedInstance = { + ...mockInstance1, + clientId: 'updated-client', + }; + + await upsertInstance(updatedInstance); + + const result = await getInstanceByName('production'); + expect(result.clientId).toBe('updated-client'); + }); + }); + + describe('removeInstance', () => { + it('should remove instance with matching name', async () => { + mockDir.setContent({ + 'config/backstage-cli/auth-instances.yaml': `instances: + - name: production + baseUrl: https://backstage.example.com + clientId: prod-client + issuedAt: ${mockInstance1.issuedAt} + + accessTokenExpiresAt: ${mockInstance1.accessTokenExpiresAt} + - name: staging + baseUrl: https://staging.backstage.example.com + clientId: staging-client + issuedAt: ${mockInstance2.issuedAt} + + accessTokenExpiresAt: ${mockInstance2.accessTokenExpiresAt} +`, + }); + + await removeInstance('production'); + + const result = await getAllInstances(); + expect(result.instances).toHaveLength(1); + expect(result.instances[0].name).toBe('staging'); + }); + + it('should do nothing if instance does not exist', async () => { + await removeInstance('nonexistent'); + + const result = await getAllInstances(); + expect(result.instances).toHaveLength(0); + }); + }); + + describe('setSelectedInstance', () => { + it('should set selected instance and unselect others', async () => { + mockDir.setContent({ + 'config/backstage-cli/auth-instances.yaml': `instances: + - name: production + baseUrl: https://backstage.example.com + clientId: prod-client + issuedAt: ${mockInstance1.issuedAt} + + accessTokenExpiresAt: ${mockInstance1.accessTokenExpiresAt} + selected: true + - name: staging + baseUrl: https://staging.backstage.example.com + clientId: staging-client + issuedAt: ${mockInstance2.issuedAt} + + accessTokenExpiresAt: ${mockInstance2.accessTokenExpiresAt} +`, + }); + + await setSelectedInstance('staging'); + + const result = await getAllInstances(); + expect(result.selected?.name).toBe('staging'); + + const prodInstance = result.instances.find(i => i.name === 'production'); + expect(prodInstance?.selected).toBe(false); + }); + + it('should throw error if instance does not exist', async () => { + await expect(setSelectedInstance('nonexistent')).rejects.toThrow( + "Unknown instance 'nonexistent'", + ); + }); + }); + + describe('withMetadataLock', () => { + it('should acquire and release lock', async () => { + const callback = jest.fn().mockResolvedValue('result'); + const result = await withMetadataLock(callback); + + expect(callback).toHaveBeenCalled(); + expect(result).toBe('result'); + }); + + it('should release lock even if callback throws', async () => { + const error = new Error('Test error'); + const callback = jest.fn().mockRejectedValue(error); + + await expect(withMetadataLock(callback)).rejects.toThrow(error); + + // Lock should still be released, allowing subsequent calls + const callback2 = jest.fn().mockResolvedValue('result'); + await expect(withMetadataLock(callback2)).resolves.toBe('result'); + }); + }); + + describe('file path resolution', () => { + it('should use XDG_CONFIG_HOME when set', async () => { + const customConfigHome = mockDir.resolve('custom-config'); + process.env.XDG_CONFIG_HOME = customConfigHome; + + await upsertInstance(mockInstance1); + + const result = await getAllInstances(); + expect(result.instances).toHaveLength(1); + expect(result.instances[0].name).toBe('production'); + + // Verify file was created in custom location + const expectedFile = path.join( + customConfigHome, + 'backstage-cli', + 'auth-instances.yaml', + ); + expect(await fs.pathExists(expectedFile)).toBe(true); + }); + + it('should create files with correct permissions (0o600)', async () => { + // File permissions are not reliably enforced on Windows + if (process.platform === 'win32') { + return; + } + + await upsertInstance(mockInstance1); + + const file = path.join( + mockDir.resolve('config'), + 'backstage-cli', + 'auth-instances.yaml', + ); + const stats = await fs.stat(file); + const mode = stats.mode & 0o777; + expect(mode).toBe(0o600); + }); + + it('should handle invalid schema and missing fields gracefully', async () => { + mockDir.setContent({ + 'config/backstage-cli/auth-instances.yaml': `instances: + - name: "" + baseUrl: not-a-url + clientId: "" +`, + }); + + const result1 = await getAllInstances(); + expect(result1.instances).toHaveLength(0); + + mockDir.setContent({ + 'config/backstage-cli/auth-instances.yaml': `instances: + - name: production +`, + }); + + const result2 = await getAllInstances(); + expect(result2.instances).toHaveLength(0); + }); + }); +}); diff --git a/packages/cli/src/modules/auth/lib/storage.ts b/packages/cli/src/modules/auth/lib/storage.ts new file mode 100644 index 0000000000..c2eb153112 --- /dev/null +++ b/packages/cli/src/modules/auth/lib/storage.ts @@ -0,0 +1,177 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NotFoundError } from '@backstage/errors'; +import fs from 'fs-extra'; +import os from 'node:os'; +import path from 'node:path'; +import lockfile from 'proper-lockfile'; +import YAML from 'yaml'; +import { z } from 'zod'; + +const METADATA_FILE = 'auth-instances.yaml'; + +const INSTANCE_NAME_PATTERN = /^[a-zA-Z0-9._:@-]+$/; + +const storedInstanceSchema = z.object({ + name: z + .string() + .min(1) + .regex(INSTANCE_NAME_PATTERN, 'Instance name contains invalid characters'), + baseUrl: z.string().url(), + clientId: z.string().min(1), + issuedAt: z.number().int().nonnegative(), + accessTokenExpiresAt: z.number().int().nonnegative(), + selected: z.boolean().optional(), +}); + +export type StoredInstance = z.infer; + +const authYamlSchema = z.object({ + instances: z.array(storedInstanceSchema).default([]), +}); + +function getMetadataFilePath(): string { + const root = + process.env.XDG_CONFIG_HOME || + (process.platform === 'win32' + ? process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming') + : path.join(os.homedir(), '.config')); + + return path.join(root, 'backstage-cli', METADATA_FILE); +} + +async function readAll(): Promise<{ instances: StoredInstance[] }> { + const file = getMetadataFilePath(); + if (!(await fs.pathExists(file))) { + return { instances: [] }; + } + const text = await fs.readFile(file, 'utf8'); + if (!text.trim()) { + return { instances: [] }; + } + try { + const doc = YAML.parse(text); + const parsed = authYamlSchema.safeParse(doc); + if (parsed.success) { + return parsed.data; + } + return { instances: [] }; + } catch { + return { instances: [] }; + } +} + +async function writeAll(data: { instances: StoredInstance[] }): Promise { + const file = getMetadataFilePath(); + await fs.ensureDir(path.dirname(file)); + const yaml = YAML.stringify(authYamlSchema.parse(data), { indentSeq: false }); + await fs.writeFile(file, yaml, { encoding: 'utf8', mode: 0o600 }); +} + +export async function getAllInstances(): Promise<{ + instances: StoredInstance[]; + selected: StoredInstance | undefined; +}> { + const { instances } = await readAll(); + const selected = instances.find(i => i.selected) ?? instances[0]; + return { + // Normalize selection prop + instances: instances.map(i => ({ + ...i, + selected: i.name === selected.name, + })), + selected, + }; +} + +export async function getSelectedInstance( + instanceName?: string, +): Promise { + if (instanceName) { + return await getInstanceByName(instanceName); + } + const { selected } = await getAllInstances(); + if (!selected) { + throw new Error( + 'No instances found. Run "auth login" to authenticate first.', + ); + } + return selected; +} + +export async function getInstanceByName(name: string): Promise { + const { instances } = await readAll(); + const instance = instances.find(i => i.name === name); + if (!instance) { + throw new NotFoundError(`Instance '${name}' not found`); + } + return instance; +} + +export async function upsertInstance(instance: StoredInstance): Promise { + const data = await readAll(); + const idx = data.instances.findIndex(i => i.name === instance.name); + if (idx === -1) { + data.instances.push(instance); + } else { + data.instances[idx] = instance; + } + await writeAll(data); +} + +export async function removeInstance(name: string): Promise { + const data = await readAll(); + const next = data.instances.filter(i => i.name !== name); + if (next.length !== data.instances.length) { + await writeAll({ instances: next }); + } +} + +export async function setSelectedInstance(name: string): Promise { + return withMetadataLock(async () => { + const data = await readAll(); + let found = false; + data.instances = data.instances.map(i => { + if (i.name === name) { + found = true; + return { ...i, selected: true }; + } + const { selected, ...rest } = i; + return { ...rest, selected: false }; + }); + if (!found) { + throw new Error(`Unknown instance '${name}'`); + } + await writeAll(data); + }); +} + +export async function withMetadataLock(fn: () => Promise): Promise { + const file = getMetadataFilePath(); + await fs.ensureDir(path.dirname(file)); + if (!(await fs.pathExists(file))) { + await fs.writeFile(file, '', { encoding: 'utf8', mode: 0o600 }); + } + const release = await lockfile.lock(file, { + retries: { retries: 5, factor: 1.5, minTimeout: 100, maxTimeout: 1000 }, + }); + try { + return await fn(); + } finally { + await release(); + } +} diff --git a/packages/cli/src/modules/build/commands/buildWorkspace.ts b/packages/cli/src/modules/build/commands/buildWorkspace.ts index 159c347eb8..649171d7d8 100644 --- a/packages/cli/src/modules/build/commands/buildWorkspace.ts +++ b/packages/cli/src/modules/build/commands/buildWorkspace.ts @@ -15,20 +15,51 @@ */ import fs from 'fs-extra'; +import { cli } from 'cleye'; import { createDistWorkspace } from '../lib/packager'; +import type { CommandContext } from '../../../wiring/types'; -type Options = { - alwaysPack?: boolean; -}; +export default async ({ args, info }: CommandContext) => { + // Normalize legacy --alwaysYarnPack alias (a genuinely different name, not + // just a casing variant — type-flag handles camelCase/kebab-case natively) + const normalizedArgs = args.map(a => { + if (a === '--alwaysYarnPack') { + return '--always-pack'; + } + if (a.startsWith('--alwaysYarnPack=')) { + return `--always-pack${a.substring('--alwaysYarnPack'.length)}`; + } + return a; + }); + + const { + flags: { alwaysPack }, + _: positionals, + } = cli( + { + help: { ...info, usage: `${info.usage} [packages...]` }, + parameters: ['', '[packages...]'], + flags: { + alwaysPack: { + type: Boolean, + description: + 'Force workspace output to be a result of running `yarn pack` on each package (warning: very slow)', + }, + }, + }, + undefined, + normalizedArgs, + ); + + const [dir, ...packages] = positionals; -export default async (dir: string, packages: string[], options: Options) => { if (!(await fs.pathExists(dir))) { throw new Error(`Target workspace directory doesn't exist, '${dir}'`); } await createDistWorkspace(packages, { targetDir: dir, - alwaysPack: options.alwaysPack, + alwaysPack, enableFeatureDetection: true, }); }; diff --git a/packages/cli/src/modules/build/commands/package/build/command.ts b/packages/cli/src/modules/build/commands/package/build/command.ts index 944be832f0..8c802fe2b5 100644 --- a/packages/cli/src/modules/build/commands/package/build/command.ts +++ b/packages/cli/src/modules/build/commands/package/build/command.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { OptionValues } from 'commander'; +import { cli } from 'cleye'; import fs from 'fs-extra'; import { buildPackage, Output } from '../../../lib/builder'; import { findRoleFromCommand } from '../../../lib/role'; @@ -29,40 +29,90 @@ import { buildFrontend } from '../../../lib/buildFrontend'; import { buildBackend } from '../../../lib/buildBackend'; import { isValidUrl } from '../../../lib/urls'; import chalk from 'chalk'; +import type { CommandContext } from '../../../../../wiring/types'; + +export default async ({ args, info }: CommandContext) => { + const { + flags: { + role, + minify, + skipBuildDependencies, + stats, + config, + moduleFederation, + }, + } = cli( + { + help: info, + flags: { + role: { + type: String, + description: 'Run the command with an explicit package role', + }, + minify: { + type: Boolean, + description: + 'Minify the generated code. Does not apply to app package (app is minified by default).', + }, + skipBuildDependencies: { + type: Boolean, + description: + 'Skip the automatic building of local dependencies. Applies to backend packages only.', + }, + stats: { + type: Boolean, + description: + 'If bundle stats are available, write them to the output directory. Applies to app packages only.', + }, + config: { + type: [String], + description: + 'Config files to load instead of app-config.yaml. Applies to app packages only.', + default: [], + }, + moduleFederation: { + type: Boolean, + description: + 'Build a package as a module federation remote. Applies to frontend plugin packages only.', + }, + }, + }, + undefined, + args, + ); -export async function command(opts: OptionValues): Promise { const webpack = process.env.LEGACY_WEBPACK_BUILD ? (require('webpack') as typeof import('webpack')) : undefined; - const role = await findRoleFromCommand(opts); + const resolvedRole = await findRoleFromCommand({ role }); - if (role === 'frontend' || role === 'backend') { - const configPaths = (opts.config as string[]).map(arg => { + if (resolvedRole === 'frontend' || resolvedRole === 'backend') { + const configPaths = config.map(arg => { if (isValidUrl(arg)) { return arg; } return targetPaths.resolve(arg); }); - if (role === 'frontend') { + if (resolvedRole === 'frontend') { return buildFrontend({ targetDir: targetPaths.dir, configPaths, - writeStats: Boolean(opts.stats), + writeStats: Boolean(stats), webpack, }); } return buildBackend({ targetDir: targetPaths.dir, configPaths, - skipBuildDependencies: Boolean(opts.skipBuildDependencies), - minify: Boolean(opts.minify), + skipBuildDependencies: Boolean(skipBuildDependencies), + minify: Boolean(minify), }); } let isModuleFederationRemote: boolean | undefined = undefined; - if ((role as string) === 'frontend-dynamic-container') { + if ((resolvedRole as string) === 'frontend-dynamic-container') { console.log( chalk.yellow( `âš ī¸ WARNING: The 'frontend-dynamic-container' package role is experimental and will receive immediate breaking changes in the future.`, @@ -70,7 +120,7 @@ export async function command(opts: OptionValues): Promise { ); isModuleFederationRemote = true; } - if (opts.moduleFederation) { + if (moduleFederation) { isModuleFederationRemote = true; } @@ -79,13 +129,13 @@ export async function command(opts: OptionValues): Promise { return buildFrontend({ targetDir: targetPaths.dir, configPaths: [], - writeStats: Boolean(opts.stats), + writeStats: Boolean(stats), isModuleFederationRemote, webpack, }); } - const roleInfo = PackageRoles.getRoleInfo(role); + const roleInfo = PackageRoles.getRoleInfo(resolvedRole); const outputs = new Set(); @@ -106,7 +156,7 @@ export async function command(opts: OptionValues): Promise { return buildPackage({ outputs, packageJson, - minify: Boolean(opts.minify), + minify: Boolean(minify), workspacePackages: await PackageGraph.listTargetPackages(), }); -} +}; diff --git a/packages/cli/src/modules/build/commands/package/build/index.ts b/packages/cli/src/modules/build/commands/package/build/index.ts index 680fe9e11d..7121eac500 100644 --- a/packages/cli/src/modules/build/commands/package/build/index.ts +++ b/packages/cli/src/modules/build/commands/package/build/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { command } from './command'; +export { default } from './command'; diff --git a/packages/cli/src/modules/build/commands/package/start/command.ts b/packages/cli/src/modules/build/commands/package/start/command.ts index 18f06ab0d2..143385e4a6 100644 --- a/packages/cli/src/modules/build/commands/package/start/command.ts +++ b/packages/cli/src/modules/build/commands/package/start/command.ts @@ -14,22 +14,80 @@ * limitations under the License. */ -import { OptionValues } from 'commander'; +import { cli } from 'cleye'; import { startPackage } from './startPackage'; import { resolveLinkedWorkspace } from './resolveLinkedWorkspace'; import { findRoleFromCommand } from '../../../lib/role'; import { targetPaths } from '@backstage/cli-common'; +import type { CommandContext } from '../../../../../wiring/types'; + +export default async ({ args, info }: CommandContext) => { + const { + flags: { + config, + role, + check, + require: requirePath, + link, + entrypoint, + inspect, + inspectBrk, + }, + } = cli( + { + help: info, + flags: { + config: { + type: [String], + description: 'Config files to load instead of app-config.yaml', + default: [], + }, + role: { + type: String, + description: 'Run the command with an explicit package role', + }, + check: { + type: Boolean, + description: 'Enable type checking and linting if available', + }, + require: { + type: String, + description: 'Add a --require argument to the node process', + }, + link: { + type: String, + description: 'Link an external workspace for module resolution', + }, + entrypoint: { + type: String, + description: + 'The entrypoint to start from, relative to the package root. Can point to either a file (without extension) or a directory (in which case the index file in that directory is used). Defaults to "dev"', + }, + inspect: { + type: String, + description: + 'Enable the Node.js inspector, optionally at a specific host:port', + }, + inspectBrk: { + type: String, + description: + 'Enable the Node.js inspector and break before user code starts', + }, + }, + }, + undefined, + args, + ); -export async function command(opts: OptionValues): Promise { await startPackage({ - role: await findRoleFromCommand(opts), - entrypoint: opts.entrypoint, + role: await findRoleFromCommand({ role }), + entrypoint, targetDir: targetPaths.dir, - configPaths: opts.config as string[], - checksEnabled: Boolean(opts.check), - linkedWorkspace: await resolveLinkedWorkspace(opts.link), - inspectEnabled: opts.inspect, - inspectBrkEnabled: opts.inspectBrk, - require: opts.require, + configPaths: config, + checksEnabled: Boolean(check), + linkedWorkspace: await resolveLinkedWorkspace(link), + inspectEnabled: inspect || (inspect === '' ? true : undefined), + inspectBrkEnabled: inspectBrk || (inspectBrk === '' ? true : undefined), + require: requirePath, }); -} +}; diff --git a/packages/cli/src/modules/build/commands/package/start/index.ts b/packages/cli/src/modules/build/commands/package/start/index.ts index 680fe9e11d..7121eac500 100644 --- a/packages/cli/src/modules/build/commands/package/start/index.ts +++ b/packages/cli/src/modules/build/commands/package/start/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { command } from './command'; +export { default } from './command'; diff --git a/packages/cli/src/modules/build/commands/repo/build.ts b/packages/cli/src/modules/build/commands/repo/build.ts index 59d0a839e4..b1e0239b4b 100644 --- a/packages/cli/src/modules/build/commands/repo/build.ts +++ b/packages/cli/src/modules/build/commands/repo/build.ts @@ -15,7 +15,7 @@ */ import chalk from 'chalk'; -import { Command, OptionValues } from 'commander'; +import { cli } from 'cleye'; import { relative as relativePath } from 'node:path'; import { buildPackages, getOutputsForRole } from '../../lib/builder'; import { targetPaths } from '@backstage/cli-common'; @@ -29,18 +29,46 @@ import { import { buildFrontend } from '../../lib/buildFrontend'; import { buildBackend } from '../../lib/buildBackend'; import { createScriptOptionsParser } from '../../lib/optionsParser'; +import type { CommandContext } from '../../../../wiring/types'; + +export default async ({ args, info }: CommandContext) => { + const { + flags: { all, since, minify }, + } = cli( + { + help: info, + flags: { + all: { + type: Boolean, + description: + 'Build all packages, including bundled app and backend packages.', + }, + since: { + type: String, + description: + 'Only build packages and their dev dependents that changed since the specified ref', + }, + minify: { + type: Boolean, + description: + 'Minify the generated code. Does not apply to app package (app is minified by default).', + }, + }, + }, + undefined, + args, + ); -export async function command(opts: OptionValues, cmd: Command): Promise { let packages = await PackageGraph.listTargetPackages(); const webpack = process.env.LEGACY_WEBPACK_BUILD ? (require('webpack') as typeof import('webpack')) : undefined; - if (opts.since) { + if (since) { const graph = PackageGraph.fromPackages(packages); const changedPackages = await graph.listChangedPackages({ - ref: opts.since, + ref: since, analyzeLockfile: true, }); const withDevDependents = graph.collectPackageNames( @@ -53,7 +81,14 @@ export async function command(opts: OptionValues, cmd: Command): Promise { const apps = new Array(); const backends = new Array(); - const parseBuildScript = createScriptOptionsParser(cmd, ['package', 'build']); + const parseBuildScript = createScriptOptionsParser(['package', 'build'], { + role: { type: 'string' }, + minify: { type: 'boolean' }, + 'skip-build-dependencies': { type: 'boolean' }, + stats: { type: 'boolean' }, + config: { type: 'string', multiple: true }, + 'module-federation': { type: 'boolean' }, + }); const options = packages.flatMap(pkg => { const role = @@ -92,14 +127,14 @@ export async function command(opts: OptionValues, cmd: Command): Promise { outputs, logPrefix: `${chalk.cyan(relativePath(targetPaths.rootDir, pkg.dir))}: `, workspacePackages: packages, - minify: opts.minify ?? buildOptions.minify, + minify: minify ?? Boolean(buildOptions.minify), }; }); console.log('Building packages'); await buildPackages(options); - if (opts.all) { + if (all) { console.log('Building apps'); await runConcurrentTasks({ items: apps, @@ -112,9 +147,12 @@ export async function command(opts: OptionValues, cmd: Command): Promise { ); return; } + const configPaths = buildOptions.config; await buildFrontend({ targetDir: pkg.dir, - configPaths: (buildOptions.config as string[]) ?? [], + configPaths: Array.isArray(configPaths) + ? (configPaths as string[]) + : [], writeStats: Boolean(buildOptions.stats), webpack, }); @@ -136,9 +174,9 @@ export async function command(opts: OptionValues, cmd: Command): Promise { await buildBackend({ targetDir: pkg.dir, skipBuildDependencies: true, - minify: opts.minify ?? buildOptions.minify, + minify: minify ?? Boolean(buildOptions.minify), }); }, }); } -} +}; diff --git a/packages/cli/src/modules/build/commands/repo/start.ts b/packages/cli/src/modules/build/commands/repo/start.ts index f7faa46eb1..6acd4f1463 100644 --- a/packages/cli/src/modules/build/commands/repo/start.ts +++ b/packages/cli/src/modules/build/commands/repo/start.ts @@ -21,10 +21,12 @@ import { } from '@backstage/cli-node'; import { relative as relativePath } from 'node:path'; import { targetPaths } from '@backstage/cli-common'; +import { cli } from 'cleye'; import { resolveLinkedWorkspace } from '../package/start/resolveLinkedWorkspace'; import { startPackage } from '../package/start/startPackage'; import { parseArgs } from 'node:util'; +import type { CommandContext } from '../../../../wiring/types'; const ACCEPTED_PACKAGE_ROLES: Array = [ 'frontend', @@ -33,19 +35,61 @@ const ACCEPTED_PACKAGE_ROLES: Array = [ 'backend-plugin', ]; -type CommandOptions = { - plugin: string[]; - config: string[]; - inspect?: boolean | string; - inspectBrk?: boolean | string; - require?: string; - link?: string; -}; +export default async ({ args, info }: CommandContext) => { + const { + flags: { plugin, config, require: requirePath, link, inspect, inspectBrk }, + _: namesOrPaths, + } = cli( + { + help: { ...info, usage: `${info.usage} [packages...]` }, + parameters: ['[packages...]'], + flags: { + plugin: { + type: [String], + description: + 'Start the dev entry-point for any matching plugin package in the repo', + default: [], + }, + config: { + type: [String], + description: 'Config files to load instead of app-config.yaml', + default: [], + }, + require: { + type: String, + description: + 'Add a --require argument to the node process. Applies to backend package only', + }, + link: { + type: String, + description: 'Link an external workspace for module resolution', + }, + inspect: { + type: String, + description: + 'Enable the Node.js inspector, optionally at a specific host:port', + }, + inspectBrk: { + type: String, + description: + 'Enable the Node.js inspector and break before user code starts', + }, + }, + }, + undefined, + args, + ); -export async function command(namesOrPaths: string[], options: CommandOptions) { - const targetPackages = await findTargetPackages(namesOrPaths, options.plugin); + const targetPackages = await findTargetPackages(namesOrPaths, plugin); - const packageOptions = await resolvePackageOptions(targetPackages, options); + const packageOptions = await resolvePackageOptions(targetPackages, { + plugin, + config, + inspect: inspect || (inspect === '' ? true : undefined), + inspectBrk: inspectBrk || (inspectBrk === '' ? true : undefined), + require: requirePath, + link, + }); if (packageOptions.length === 0) { console.log('No packages found to start'); @@ -60,7 +104,7 @@ export async function command(namesOrPaths: string[], options: CommandOptions) { // Each of these block until interrupted by user await Promise.all(packageOptions.map(entry => startPackage(entry.options))); -} +}; export async function findTargetPackages( namesOrPaths: string[], @@ -165,6 +209,15 @@ export async function findTargetPackages( ); } +type CommandOptions = { + plugin: string[]; + config: string[]; + inspect?: boolean | string; + inspectBrk?: boolean | string; + require?: string; + link?: string; +}; + async function resolvePackageOptions( targetPackages: BackstagePackage[], options: CommandOptions, diff --git a/packages/cli/src/modules/build/index.ts b/packages/cli/src/modules/build/index.ts index 1e8d1d2060..139c591af2 100644 --- a/packages/cli/src/modules/build/index.ts +++ b/packages/cli/src/modules/build/index.ts @@ -14,46 +14,7 @@ * limitations under the License. */ -import { Command, Option } from 'commander'; import { createCliPlugin } from '../../wiring/factory'; -import { lazy } from '../../wiring/lazy'; - -const configOption = [ - '--config ', - 'Config files to load instead of app-config.yaml', - (opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]), - Array(), -] as const; - -export function registerPackageCommands(command: Command) { - command - .command('build') - .description('Build a package for production deployment or publishing') - .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(), - ) - .option( - '--module-federation', - 'Build a package as a module federation remote. Applies to frontend plugin packages only.', - ) - .action(lazy(() => import('./commands/package/build'), 'command')); -} export const buildPlugin = createCliPlugin({ pluginId: 'build', @@ -61,146 +22,26 @@ export const buildPlugin = createCliPlugin({ 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(), - ) - .option( - '--module-federation', - 'Build a package as a module federation remote. Applies to frontend plugin packages only.', - ) - .action(lazy(() => import('./commands/package/build'), 'command')); - await defaultCommand.parseAsync(args, { from: 'user' }); - }, + execute: { loader: () => import('./commands/package/build') }, }); 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' }); - }, + execute: { loader: () => import('./commands/repo/build') }, }); 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', - ) - .option( - '--entrypoint ', - 'The entrypoint to start from, relative to the package root. Can point to either a file (without extension) or a directory (in which case the index file in that directory is used). Defaults to "dev"', - ) - .action(lazy(() => import('./commands/package/start'), 'command')); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, + execute: { loader: () => import('./commands/package/start') }, }); 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' }); - }, + execute: { loader: () => import('./commands/repo/start') }, }); reg.addCommand({ @@ -239,25 +80,7 @@ export const buildPlugin = createCliPlugin({ 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' }); - }, + execute: { loader: () => import('./commands/buildWorkspace') }, }); }, }); diff --git a/packages/cli/src/modules/build/lib/optionsParser.ts b/packages/cli/src/modules/build/lib/optionsParser.ts index 15561a2e42..62c3b7ce71 100644 --- a/packages/cli/src/modules/build/lib/optionsParser.ts +++ b/packages/cli/src/modules/build/lib/optionsParser.ts @@ -13,34 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Command } from 'commander'; +import { parseArgs, type ParseArgsConfig } from 'node:util'; +import { parse as parseShellArgs } from 'shell-quote'; export function createScriptOptionsParser( - anyCmd: Command, commandPath: string[], + options: ParseArgsConfig['options'], ) { - // Regardless of what command instance is passed in we want to find - // the root command and resolve the path from there - let rootCmd = anyCmd; - while (rootCmd.parent) { - rootCmd = rootCmd.parent; - } - - // Now find the command that was requested - let targetCmd = rootCmd as Command | undefined; - for (const name of commandPath) { - targetCmd = targetCmd?.commands.find(c => c.name() === name) as - | Command - | undefined; - } - - if (!targetCmd) { - throw new Error( - `Could not find package command '${commandPath.join(' ')}'`, - ); - } - const cmd = targetCmd; - const expectedScript = `backstage-cli ${commandPath.join(' ')}`; return (scriptStr?: string) => { @@ -49,22 +28,13 @@ export function createScriptOptionsParser( } const argsStr = scriptStr.slice(expectedScript.length).trim(); + const args = argsStr + ? parseShellArgs(argsStr).filter( + (e): e is string => typeof e === 'string', + ) + : []; - // Can't clone or copy or even use commands as prototype, so we mutate - // the necessary members instead, and then reset them once we're done - const currentOpts = (cmd as any)._optionValues; - const currentStore = (cmd as any)._storeOptionsAsProperties; - - const result: Record = {}; - (cmd as any)._storeOptionsAsProperties = false; - (cmd as any)._optionValues = result; - - // Triggers the writing of options to the result object - cmd.parseOptions(argsStr.split(' ')); - - (cmd as any)._optionValues = currentOpts; - (cmd as any)._storeOptionsAsProperties = currentStore; - - return result; + const { values } = parseArgs({ args, strict: false, options }); + return values; }; } diff --git a/packages/cli/src/modules/build/lib/role.test.ts b/packages/cli/src/modules/build/lib/role.test.ts index dbed4310fa..b046d42432 100644 --- a/packages/cli/src/modules/build/lib/role.test.ts +++ b/packages/cli/src/modules/build/lib/role.test.ts @@ -16,20 +16,12 @@ import { createMockDirectory } from '@backstage/backend-test-utils'; import { overrideTargetPaths } from '@backstage/cli-common/testUtils'; -import { Command } from 'commander'; import { findRoleFromCommand } from './role'; const mockDir = createMockDirectory(); overrideTargetPaths(mockDir.path); describe('findRoleFromCommand', () => { - function mkCommand(args?: string) { - const parsed = new Command() - .option('--role ', 'test role') - .parse(args?.split(' ') ?? [], { from: 'user' }); - return parsed.opts(); - } - beforeEach(() => { mockDir.setContent({ 'package.json': JSON.stringify({ @@ -42,16 +34,14 @@ describe('findRoleFromCommand', () => { }); it('provides role info by role', async () => { - await expect(findRoleFromCommand(mkCommand())).resolves.toEqual( - 'web-library', - ); + await expect(findRoleFromCommand({})).resolves.toEqual('web-library'); await expect( - findRoleFromCommand(mkCommand('--role node-library')), + findRoleFromCommand({ role: 'node-library' }), ).resolves.toEqual('node-library'); - await expect( - findRoleFromCommand(mkCommand('--role invalid')), - ).rejects.toThrow(`Unknown package role 'invalid'`); + await expect(findRoleFromCommand({ role: 'invalid' })).rejects.toThrow( + `Unknown package role 'invalid'`, + ); }); }); diff --git a/packages/cli/src/modules/build/lib/role.ts b/packages/cli/src/modules/build/lib/role.ts index 2f3960433b..26c9da7cd0 100644 --- a/packages/cli/src/modules/build/lib/role.ts +++ b/packages/cli/src/modules/build/lib/role.ts @@ -15,16 +15,15 @@ */ import fs from 'fs-extra'; -import { OptionValues } from 'commander'; import { targetPaths } from '@backstage/cli-common'; import { PackageRoles, PackageRole } from '@backstage/cli-node'; -export async function findRoleFromCommand( - opts: OptionValues, -): Promise { +export async function findRoleFromCommand(opts: { + role?: string; +}): Promise { if (opts.role) { - return PackageRoles.getRoleInfo(opts.role)?.role; + return PackageRoles.getRoleInfo(opts.role).role; } const pkg = await fs.readJson(targetPaths.resolve('package.json')); diff --git a/packages/cli/src/modules/create-github-app/commands/create-github-app/index.ts b/packages/cli/src/modules/create-github-app/commands/create-github-app/index.ts index 57aca5674f..1cd51e541b 100644 --- a/packages/cli/src/modules/create-github-app/commands/create-github-app/index.ts +++ b/packages/cli/src/modules/create-github-app/commands/create-github-app/index.ts @@ -19,14 +19,27 @@ import chalk from 'chalk'; import { stringify as stringifyYaml } from 'yaml'; import inquirer, { Question, Answers } from 'inquirer'; import { targetPaths } from '@backstage/cli-common'; +import { cli } from 'cleye'; import { GithubCreateAppServer } from './GithubCreateAppServer'; import openBrowser from 'react-dev-utils/openBrowser'; +import type { CommandContext } from '../../../../wiring/types'; // This is an experimental command that at this point does not support GitHub Enterprise // due to lacking support for creating apps from manifests. // https://docs.github.com/en/free-pro-team@latest/developers/apps/creating-a-github-app-from-a-manifest -export default async (org: string) => { +export default async ({ args, info }: CommandContext) => { + const { _: positionals } = cli( + { + help: { ...info, usage: `${info.usage} ` }, + parameters: [''], + }, + undefined, + args, + ); + + const org = positionals[0]; + const answers: Answers = await inquirer.prompt({ name: 'appType', type: 'checkbox', diff --git a/packages/cli/src/modules/create-github-app/index.ts b/packages/cli/src/modules/create-github-app/index.ts index 5a3e30e870..9dad46577e 100644 --- a/packages/cli/src/modules/create-github-app/index.ts +++ b/packages/cli/src/modules/create-github-app/index.ts @@ -14,8 +14,6 @@ * limitations under the License. */ import { createCliPlugin } from '../../wiring/factory'; -import { Command } from 'commander'; -import { lazy } from '../../wiring/lazy'; export default createCliPlugin({ pluginId: 'new', @@ -23,16 +21,7 @@ export default createCliPlugin({ 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' }); - }, + execute: { loader: () => import('./commands/create-github-app') }, }); }, }); diff --git a/packages/cli/src/modules/lint/commands/package/lint.ts b/packages/cli/src/modules/lint/commands/package/lint.ts index 0a453ac71a..40062726f1 100644 --- a/packages/cli/src/modules/lint/commands/package/lint.ts +++ b/packages/cli/src/modules/lint/commands/package/lint.ts @@ -15,15 +15,47 @@ */ import fs from 'fs-extra'; -import { OptionValues } from 'commander'; +import { cli } from 'cleye'; import { targetPaths } from '@backstage/cli-common'; - import { ESLint } from 'eslint'; +import type { CommandContext } from '../../../../wiring/types'; + +export default async ({ args, info }: CommandContext) => { + const { + flags: { fix, format, outputFile, maxWarnings }, + _: directories, + } = cli( + { + help: { ...info, usage: `${info.usage} [directories...]` }, + parameters: ['[directories...]'], + flags: { + fix: { + type: Boolean, + description: 'Attempt to automatically fix violations', + }, + format: { + type: String, + description: 'Lint report output format', + default: 'eslint-formatter-friendly', + }, + outputFile: { + type: String, + description: 'Write the lint report to a file instead of stdout', + }, + maxWarnings: { + type: String, + description: + 'Fail if more than this number of warnings. -1 allows warnings. (default: -1)', + }, + }, + }, + undefined, + args, + ); -export default async (directories: string[], opts: OptionValues) => { const eslint = new ESLint({ cwd: targetPaths.dir, - fix: opts.fix, + fix, extensions: ['js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs'], }); @@ -31,31 +63,31 @@ export default async (directories: string[], opts: OptionValues) => { directories.length ? directories : ['.'], ); - const maxWarnings = opts.maxWarnings ?? -1; - const ignoreWarnings = +maxWarnings === -1; + const maxWarningsNum = maxWarnings ? +maxWarnings : -1; + const ignoreWarnings = maxWarningsNum === -1; const failed = results.some(r => r.errorCount > 0) || (!ignoreWarnings && results.reduce((current, next) => current + next.warningCount, 0) > - maxWarnings); + maxWarningsNum); - if (opts.fix) { + if (fix) { await ESLint.outputFixes(results); } - const formatter = await eslint.loadFormatter(opts.format); + const formatter = await eslint.loadFormatter(format); // This formatter uses the cwd to format file paths, so let's have that happen from the root instead - if (opts.format === 'eslint-formatter-friendly') { + if (format === 'eslint-formatter-friendly') { process.chdir(targetPaths.rootDir); } const resultText = await formatter.format(results); if (resultText) { - if (opts.outputFile) { - await fs.writeFile(targetPaths.resolve(opts.outputFile), resultText); + if (outputFile) { + await fs.writeFile(targetPaths.resolve(outputFile), resultText); } else { console.log(resultText); } diff --git a/packages/cli/src/modules/lint/commands/repo/lint.ts b/packages/cli/src/modules/lint/commands/repo/lint.ts index a3ae69302c..174282ffcf 100644 --- a/packages/cli/src/modules/lint/commands/repo/lint.ts +++ b/packages/cli/src/modules/lint/commands/repo/lint.ts @@ -16,7 +16,7 @@ import chalk from 'chalk'; import fs from 'fs-extra'; -import { Command, OptionValues } from 'commander'; +import { cli } from 'cleye'; import { createHash } from 'node:crypto'; import { relative as relativePath } from 'node:path'; import { @@ -29,6 +29,7 @@ import { import { targetPaths } from '@backstage/cli-common'; import { createScriptOptionsParser } from '../../lib/optionsParser'; +import type { CommandContext } from '../../../../wiring/types'; function depCount(pkg: BackstagePackageJson) { const deps = pkg.dependencies ? Object.keys(pkg.dependencies).length : 0; @@ -38,24 +39,90 @@ function depCount(pkg: BackstagePackageJson) { return deps + devDeps; } -export async function command(opts: OptionValues, cmd: Command): Promise { +export default async ({ args, info }: CommandContext) => { + for (const flag of [ + 'outputFile', + 'successCache', + 'successCacheDir', + 'maxWarnings', + ]) { + if (args.some(a => a === `--${flag}` || a.startsWith(`--${flag}=`))) { + process.stderr.write( + `DEPRECATION WARNING: --${flag} is deprecated, use the kebab-case form instead\n`, + ); + } + } + + const { + flags: { + fix, + format, + outputFile, + successCache: useSuccessCache, + successCacheDir, + since, + maxWarnings, + }, + } = cli( + { + help: info, + flags: { + fix: { + type: Boolean, + description: 'Attempt to automatically fix violations', + }, + format: { + type: String, + description: 'Lint report output format', + default: 'eslint-formatter-friendly', + }, + outputFile: { + type: String, + description: 'Write the lint report to a file instead of stdout', + }, + successCache: { + type: Boolean, + description: + 'Enable success caching, which skips running tests for unchanged packages that were successful in the previous run', + }, + successCacheDir: { + type: String, + description: + 'Set the success cache location, (default: node_modules/.cache/backstage-cli)', + }, + since: { + type: String, + description: + 'Only lint packages that changed since the specified ref', + }, + maxWarnings: { + type: String, + description: + 'Fail if more than this number of warnings. -1 allows warnings. (default: -1)', + }, + }, + }, + undefined, + args, + ); + let packages = await PackageGraph.listTargetPackages(); const cache = SuccessCache.create({ name: 'lint', - basePath: opts.successCacheDir, + basePath: successCacheDir, }); - const cacheContext = opts.successCache + const cacheContext = useSuccessCache ? { entries: await cache.read(), lockfile: await Lockfile.load(targetPaths.resolveRoot('yarn.lock')), } : undefined; - if (opts.since) { + if (since) { const graph = PackageGraph.fromPackages(packages); packages = await graph.listChangedPackages({ - ref: opts.since, + ref: since, analyzeLockfile: true, }); } @@ -65,7 +132,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { packages.sort((a, b) => depCount(b.packageJson) - depCount(a.packageJson)); // This formatter uses the cwd to format file paths, so let's have that happen from the root instead - if (opts.format === 'eslint-formatter-friendly') { + if (format === 'eslint-formatter-friendly') { process.chdir(targetPaths.rootDir); } @@ -74,7 +141,12 @@ export async function command(opts: OptionValues, cmd: Command): Promise { process.env.FORCE_COLOR = '1'; } - const parseLintScript = createScriptOptionsParser(cmd, ['package', 'lint']); + const parseLintScript = createScriptOptionsParser(['package', 'lint'], { + fix: { type: 'boolean' }, + format: { type: 'string' }, + 'output-file': { type: 'string' }, + 'max-warnings': { type: 'string' }, + }); const items = await Promise.all( packages.map(async pkg => { @@ -112,20 +184,20 @@ export async function command(opts: OptionValues, cmd: Command): Promise { const { results: resultsList } = await runWorkerQueueThreads({ items: items.filter(item => item.lintOptions), // Filter out packages without lint script context: { - fix: Boolean(opts.fix), - format: opts.format as string | undefined, + fix: Boolean(fix), + format: format as string | undefined, shouldCache: Boolean(cacheContext), - maxWarnings: opts.maxWarnings ?? -1, + maxWarnings: maxWarnings ?? '-1', successCache: cacheContext?.entries, rootDir: targetPaths.rootDir, }, workerFactory: async ({ - fix, - format, + fix: workerFix, + format: workerFormat, shouldCache, successCache, rootDir, - maxWarnings, + maxWarnings: workerMaxWarnings, }) => { const { ESLint } = require('eslint') as typeof import('eslint'); const crypto = require('node:crypto') as typeof import('crypto'); @@ -151,7 +223,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { const start = Date.now(); const eslint = new ESLint({ cwd: fullDir, - fix, + fix: workerFix, extensions: ['js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs'], }); @@ -192,7 +264,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { } } - const formatter = await eslint.loadFormatter(format); + const formatter = await eslint.loadFormatter(workerFormat); const results = await eslint.lintFiles(['.']); @@ -200,18 +272,18 @@ export async function command(opts: OptionValues, cmd: Command): Promise { const time = ((Date.now() - start) / 1000).toFixed(2); console.log(`Checked ${count} files in ${relativeDir} ${time}s`); - if (fix) { + if (workerFix) { await ESLint.outputFixes(results); } - const ignoreWarnings = +maxWarnings === -1; + const ignoreWarnings = +workerMaxWarnings === -1; const resultText = formatter.format(results) as string; const failed = results.some(r => r.errorCount > 0) || (!ignoreWarnings && results.reduce((current, next) => current + next.warningCount, 0) > - maxWarnings); + +workerMaxWarnings); return { relativeDir, @@ -242,8 +314,8 @@ export async function command(opts: OptionValues, cmd: Command): Promise { // When doing repo lint, only list the results if the lint failed to avoid a log // dump of all warnings that might be irrelevant if (resultText) { - if (opts.outputFile) { - if (opts.format === 'json') { + if (outputFile) { + if (format === 'json') { jsonResults.push(resultText); } else { errorOutput += `${resultText}\n`; @@ -258,7 +330,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { } } - if (opts.format === 'json') { + if (format === 'json') { let mergedJsonResults: any[] = []; for (const jsonResult of jsonResults) { mergedJsonResults = mergedJsonResults.concat(JSON.parse(jsonResult)); @@ -266,8 +338,8 @@ export async function command(opts: OptionValues, cmd: Command): Promise { errorOutput = JSON.stringify(mergedJsonResults, null, 2); } - if (opts.outputFile && errorOutput) { - await fs.writeFile(targetPaths.resolveRoot(opts.outputFile), errorOutput); + if (outputFile && errorOutput) { + await fs.writeFile(targetPaths.resolveRoot(outputFile), errorOutput); } if (cacheContext) { @@ -277,4 +349,4 @@ export async function command(opts: OptionValues, cmd: Command): Promise { if (failed) { process.exit(1); } -} +}; diff --git a/packages/cli/src/modules/lint/index.ts b/packages/cli/src/modules/lint/index.ts index 40c374aa3b..8e02b7653f 100644 --- a/packages/cli/src/modules/lint/index.ts +++ b/packages/cli/src/modules/lint/index.ts @@ -14,28 +14,6 @@ * limitations under the License. */ import { createCliPlugin } from '../../wiring/factory'; -import { Command } from 'commander'; -import { lazy } from '../../wiring/lazy'; - -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 default createCliPlugin({ pluginId: 'lint', @@ -43,53 +21,13 @@ export default createCliPlugin({ reg.addCommand({ path: ['package', 'lint'], description: 'Lint a package', - execute: async ({ args }) => { - const command = new Command(); - registerPackageLintCommand(command); - - await command.parseAsync(args, { from: 'user' }); - }, + execute: { loader: () => import('./commands/package/lint') }, }); 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' }); - }, + execute: { loader: () => import('./commands/repo/lint') }, }); }, }); diff --git a/packages/cli/src/modules/lint/lib/optionsParser.ts b/packages/cli/src/modules/lint/lib/optionsParser.ts index 15561a2e42..62c3b7ce71 100644 --- a/packages/cli/src/modules/lint/lib/optionsParser.ts +++ b/packages/cli/src/modules/lint/lib/optionsParser.ts @@ -13,34 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Command } from 'commander'; +import { parseArgs, type ParseArgsConfig } from 'node:util'; +import { parse as parseShellArgs } from 'shell-quote'; export function createScriptOptionsParser( - anyCmd: Command, commandPath: string[], + options: ParseArgsConfig['options'], ) { - // Regardless of what command instance is passed in we want to find - // the root command and resolve the path from there - let rootCmd = anyCmd; - while (rootCmd.parent) { - rootCmd = rootCmd.parent; - } - - // Now find the command that was requested - let targetCmd = rootCmd as Command | undefined; - for (const name of commandPath) { - targetCmd = targetCmd?.commands.find(c => c.name() === name) as - | Command - | undefined; - } - - if (!targetCmd) { - throw new Error( - `Could not find package command '${commandPath.join(' ')}'`, - ); - } - const cmd = targetCmd; - const expectedScript = `backstage-cli ${commandPath.join(' ')}`; return (scriptStr?: string) => { @@ -49,22 +28,13 @@ export function createScriptOptionsParser( } const argsStr = scriptStr.slice(expectedScript.length).trim(); + const args = argsStr + ? parseShellArgs(argsStr).filter( + (e): e is string => typeof e === 'string', + ) + : []; - // Can't clone or copy or even use commands as prototype, so we mutate - // the necessary members instead, and then reset them once we're done - const currentOpts = (cmd as any)._optionValues; - const currentStore = (cmd as any)._storeOptionsAsProperties; - - const result: Record = {}; - (cmd as any)._storeOptionsAsProperties = false; - (cmd as any)._optionValues = result; - - // Triggers the writing of options to the result object - cmd.parseOptions(argsStr.split(' ')); - - (cmd as any)._optionValues = currentOpts; - (cmd as any)._storeOptionsAsProperties = currentStore; - - return result; + const { values } = parseArgs({ args, strict: false, options }); + return values; }; } diff --git a/packages/cli/src/modules/maintenance/commands/repo/fix.ts b/packages/cli/src/modules/maintenance/commands/repo/fix.ts index de09d8b490..ea367d813a 100644 --- a/packages/cli/src/modules/maintenance/commands/repo/fix.ts +++ b/packages/cli/src/modules/maintenance/commands/repo/fix.ts @@ -21,7 +21,7 @@ import { PackageRole, PackageRoles, } from '@backstage/cli-node'; -import { OptionValues } from 'commander'; +import { cli } from 'cleye'; import fs from 'fs-extra'; import { resolve as resolvePath, @@ -492,14 +492,38 @@ export function fixPeerModules(pkg: FixablePackage) { type PackageFixer = (pkg: FixablePackage, packages: FixablePackage[]) => void; -export async function command(opts: OptionValues): Promise { +export default async ({ + args, + info, +}: import('../../../../wiring/types').CommandContext) => { + const { + flags: { publish, check }, + } = cli( + { + help: info, + flags: { + publish: { + type: Boolean, + description: + 'Enable additional fixes that only apply when publishing packages', + }, + check: { + type: Boolean, + description: + 'Fail if any packages would have been changed by the command', + }, + }, + }, + undefined, + args, + ); + const packages = await readFixablePackages(); const fixRepositoryField = createRepositoryFieldFixer(); const fixers: PackageFixer[] = [fixPackageExports, fixSideEffects]; - // Fixers that only apply to repos that publish packages - if (opts.publish) { + if (publish) { fixers.push( fixRepositoryField, fixPluginId, @@ -514,11 +538,11 @@ export async function command(opts: OptionValues): Promise { } } - if (opts.check) { + if (check) { if (printPackageFixHint(packages)) { process.exit(1); } } else { await writeFixedPackages(packages); } -} +}; diff --git a/packages/cli/src/modules/maintenance/commands/repo/list-deprecations.ts b/packages/cli/src/modules/maintenance/commands/repo/list-deprecations.ts index f4114838a1..3b081119ee 100644 --- a/packages/cli/src/modules/maintenance/commands/repo/list-deprecations.ts +++ b/packages/cli/src/modules/maintenance/commands/repo/list-deprecations.ts @@ -16,12 +16,26 @@ import chalk from 'chalk'; import { ESLint } from 'eslint'; -import { OptionValues } from 'commander'; +import { cli } from 'cleye'; import { relative as relativePath } from 'node:path'; import { PackageGraph } from '@backstage/cli-node'; import { targetPaths } from '@backstage/cli-common'; +import type { CommandContext } from '../../../../wiring/types'; + +export default async ({ args, info }: CommandContext) => { + const { + flags: { json }, + } = cli( + { + help: info, + flags: { + json: { type: Boolean, description: 'Output as JSON' }, + }, + }, + undefined, + args, + ); -export async function command(opts: OptionValues) { const packages = await PackageGraph.listTargetPackages(); const eslint = new ESLint({ @@ -74,7 +88,7 @@ export async function command(opts: OptionValues) { stderr.cursorTo(0); } - if (opts.json) { + if (json) { console.log(JSON.stringify(deprecations, null, 2)); } else { for (const d of deprecations) { @@ -87,4 +101,4 @@ export async function command(opts: OptionValues) { if (deprecations.length > 0) { process.exit(1); } -} +}; diff --git a/packages/cli/src/modules/maintenance/index.ts b/packages/cli/src/modules/maintenance/index.ts index e2584527f0..a6597559f8 100644 --- a/packages/cli/src/modules/maintenance/index.ts +++ b/packages/cli/src/modules/maintenance/index.ts @@ -13,9 +13,7 @@ * 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 '../../wiring/lazy'; export default createCliPlugin({ pluginId: 'maintenance', @@ -23,36 +21,13 @@ export default createCliPlugin({ 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' }); - }, + execute: { loader: () => import('./commands/repo/fix') }, }); 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' }); - }, + execute: { loader: () => import('./commands/repo/list-deprecations') }, }); }, }); diff --git a/packages/cli/src/modules/migrate/commands/versions/bump.test.ts b/packages/cli/src/modules/migrate/commands/versions/bump.test.ts index c80af07c16..f119a0bedb 100644 --- a/packages/cli/src/modules/migrate/commands/versions/bump.test.ts +++ b/packages/cli/src/modules/migrate/commands/versions/bump.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ import fs from 'fs-extra'; -import { Command } from 'commander'; import * as runObj from '@backstage/cli-common'; import { overrideTargetPaths } from '@backstage/cli-common/testUtils'; import bump, { bumpBackstageJsonVersion, createVersionFinder } from './bump'; @@ -126,6 +125,8 @@ const expectLogsToMatch = ( expect(receivedLogs.filter(Boolean).sort()).toEqual(expected.sort()); }; +const info = { usage: 'backstage-cli versions:bump', description: '' }; + describe('bump', () => { const mockDir = createMockDirectory(); @@ -190,7 +191,7 @@ describe('bump', () => { ), ); const { log: logs } = await withLogCollector(['log', 'warn'], async () => { - await bump({ pattern: null, release: 'main' } as unknown as Command); + await bump({ args: ['--release', 'main'], info }); }); expectLogsToMatch(logs, [ 'Using default pattern glob @backstage/*', @@ -283,11 +284,7 @@ describe('bump', () => { ), ); const { log: logs } = await withLogCollector(['log', 'warn'], async () => { - await bump({ - pattern: null, - release: 'main', - skipInstall: true, - } as unknown as Command); + await bump({ args: ['--release', 'main', '--skip-install'], info }); }); expectLogsToMatch(logs, [ 'Using default pattern glob @backstage/*', @@ -389,7 +386,7 @@ describe('bump', () => { ), ); const { log: logs } = await withLogCollector(['log', 'warn'], async () => { - await bump({ pattern: null, release: 'main' } as unknown as Command); + await bump({ args: ['--release', 'main'], info }); }); expectLogsToMatch(logs, [ 'Using default pattern glob @backstage/*', @@ -493,7 +490,7 @@ describe('bump', () => { ), ); const { log: logs } = await withLogCollector(['log', 'warn'], async () => { - await bump({ pattern: null, release: 'main' } as unknown as Command); + await bump({ args: ['--release', 'main'], info }); }); expectLogsToMatch(logs, [ 'Using default pattern glob @backstage/*', @@ -589,7 +586,7 @@ describe('bump', () => { ); const { log: logs } = await withLogCollector(['log', 'warn'], async () => { await expect( - bump({ pattern: null, release: '999.0.1' } as unknown as Command), + bump({ args: ['--release', '999.0.1'], info }), ).rejects.toThrow('No release found for 999.0.1 version'); }); expect(logs.filter(Boolean)).toEqual([ @@ -694,7 +691,7 @@ describe('bump', () => { ), ); const { log: logs } = await withLogCollector(['log', 'warn'], async () => { - await bump({ pattern: null, release: 'next' } as unknown as Command); + await bump({ args: ['--release', 'next'], info }); }); expectLogsToMatch(logs, [ 'Using default pattern glob @backstage/*', @@ -774,9 +771,14 @@ describe('bump', () => { ); const { log: logs } = await withLogCollector(['log', 'warn'], async () => { await bump({ - pattern: '@{backstage,backstage-extra}/*', - release: 'main', - } as any); + args: [ + '--pattern', + '@{backstage,backstage-extra}/*', + '--release', + 'main', + ], + info, + }); }); expectLogsToMatch(logs, [ 'Using custom pattern glob @{backstage,backstage-extra}/*', @@ -882,7 +884,7 @@ describe('bump', () => { ), ); const { log: logs } = await withLogCollector(['log', 'warn'], async () => { - await bump({ pattern: null, release: 'main' } as unknown as Command); + await bump({ args: ['--release', 'main'], info }); }); expectLogsToMatch(logs, [ 'Using default pattern glob @backstage/*', @@ -1121,7 +1123,7 @@ describe('environment variables', () => { ); const { log: logs } = await withLogCollector(['log', 'warn'], async () => { - await bump({ pattern: null, release: 'main' } as unknown as Command); + await bump({ args: ['--release', 'main'], info }); }); expectLogsToMatch(logs, [ @@ -1195,7 +1197,7 @@ describe('environment variables', () => { } as any); const { log: logs } = await withLogCollector(['log', 'warn'], async () => { - await bump({ pattern: null, release: 'main' } as unknown as Command); + await bump({ args: ['--release', 'main'], info }); }); expectLogsToMatch(logs, [ @@ -1280,7 +1282,7 @@ describe('environment variables', () => { ); const { log: logs } = await withLogCollector(['log', 'warn'], async () => { - await bump({ pattern: null, release: 'main' } as unknown as Command); + await bump({ args: ['--release', 'main'], info }); }); expectLogsToMatch(logs, [ @@ -1329,9 +1331,7 @@ describe('environment variables', () => { }, }); - await expect( - bump({ pattern: null, release: 'main' } as unknown as Command), - ).rejects.toThrow(); + await expect(bump({ args: ['--release', 'main'], info })).rejects.toThrow(); }); it('should handle network errors when using custom base URL', async () => { @@ -1359,8 +1359,6 @@ describe('environment variables', () => { ), ); - await expect( - bump({ pattern: null, release: 'main' } as unknown as Command), - ).rejects.toThrow(); + await expect(bump({ args: ['--release', 'main'], info })).rejects.toThrow(); }); }); diff --git a/packages/cli/src/modules/migrate/commands/versions/bump.ts b/packages/cli/src/modules/migrate/commands/versions/bump.ts index 8fe7013ac8..a20cf1494a 100644 --- a/packages/cli/src/modules/migrate/commands/versions/bump.ts +++ b/packages/cli/src/modules/migrate/commands/versions/bump.ts @@ -26,7 +26,7 @@ import fs from 'fs-extra'; import chalk from 'chalk'; import { minimatch } from 'minimatch'; import semver from 'semver'; -import { OptionValues } from 'commander'; +import { cli } from 'cleye'; import { isError, NotFoundError } from '@backstage/errors'; import { resolve as resolvePath } from 'node:path'; @@ -48,6 +48,7 @@ import { import { migrateMovedPackages } from './migrate'; import { runYarnInstall } from '../../lib/utils'; import { run } from '@backstage/cli-common'; +import type { CommandContext } from '../../../../wiring/types'; const DEP_TYPES = [ 'dependencies', @@ -73,12 +74,41 @@ function extendsDefaultPattern(pattern: string): boolean { return minimatch('@backstage/', pattern.slice(0, -1)); } -export default async (opts: OptionValues) => { +export default async ({ args, info }: CommandContext) => { + const { + flags: { pattern: patternFlag, release, skipInstall, skipMigrate }, + } = cli( + { + help: info, + flags: { + pattern: { + type: String, + description: 'Override glob for matching packages to upgrade', + }, + release: { + type: String, + description: 'Bump to a specific Backstage release line or version', + default: 'main', + }, + skipInstall: { + type: Boolean, + description: 'Skips yarn install step', + }, + skipMigrate: { + type: Boolean, + description: 'Skips migration of any moved packages', + }, + }, + }, + undefined, + args, + ); + const lockfilePath = targetPaths.resolveRoot('yarn.lock'); const lockfile = await Lockfile.load(lockfilePath); const yarnPluginEnabled = await hasBackstageYarnPlugin(); - let pattern = opts.pattern; + let pattern = patternFlag; if (!pattern) { console.log(`Using default pattern glob ${DEFAULT_PATTERN_GLOB}`); @@ -97,15 +127,15 @@ export default async (opts: OptionValues) => { findTargetVersion = createStrictVersionFinder({ releaseManifest, }); - } else if (semver.valid(opts.release)) { + } else if (semver.valid(release)) { // Specific release specified. Be strict when resolving versions - releaseManifest = await getManifestByVersion({ version: opts.release }); + releaseManifest = await getManifestByVersion({ version: release! }); findTargetVersion = createStrictVersionFinder({ releaseManifest, }); } else { // Release line specified. Be lenient when resolving versions. - if (opts.release === 'next') { + if (release === 'next') { const next = await getManifestByReleaseLine({ releaseLine: 'next', versionsBaseUrl: env.BACKSTAGE_VERSIONS_BASE_URL, @@ -120,12 +150,12 @@ export default async (opts: OptionValues) => { : main; } else { releaseManifest = await getManifestByReleaseLine({ - releaseLine: opts.release, + releaseLine: release!, versionsBaseUrl: env.BACKSTAGE_VERSIONS_BASE_URL, }); } findTargetVersion = createVersionFinder({ - releaseLine: opts.releaseLine, + releaseLine: release, releaseManifest, }); } @@ -264,7 +294,7 @@ export default async (opts: OptionValues) => { ); } - if (!opts.skipInstall) { + if (!skipInstall) { await runYarnInstall(); } else { console.log(); @@ -272,14 +302,14 @@ export default async (opts: OptionValues) => { console.log(chalk.yellow(`Skipping yarn install`)); } - if (!opts.skipMigrate) { + if (!skipMigrate) { console.log(); const changed = await migrateMovedPackages({ - pattern: opts.pattern, + pattern: patternFlag, }); - if (changed && !opts.skipInstall) { + if (changed && !skipInstall) { await runYarnInstall(); } } diff --git a/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts b/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts index 6748f8c1ff..fc50a5c491 100644 --- a/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts +++ b/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts @@ -123,7 +123,7 @@ describe('versions:migrate', () => { }); const { warn, log: logs } = await withLogCollector(async () => { - await migrate({}); + await migrate({ args: [], info: { usage: 'test', description: 'test' } }); }); expectLogsToMatch(logs, [ @@ -229,7 +229,7 @@ describe('versions:migrate', () => { }); await withLogCollector(async () => { - await migrate({}); + await migrate({ args: [], info: { usage: 'test', description: 'test' } }); }); expect(runObj.run).toHaveBeenCalledTimes(1); @@ -311,7 +311,7 @@ describe('versions:migrate', () => { }); await withLogCollector(async () => { - await migrate({}); + await migrate({ args: [], info: { usage: 'test', description: 'test' } }); }); expect(runObj.run).toHaveBeenCalledTimes(1); diff --git a/packages/cli/src/modules/migrate/commands/versions/migrate.ts b/packages/cli/src/modules/migrate/commands/versions/migrate.ts index e5d4f1b2d3..4db800641a 100644 --- a/packages/cli/src/modules/migrate/commands/versions/migrate.ts +++ b/packages/cli/src/modules/migrate/commands/versions/migrate.ts @@ -16,11 +16,12 @@ import { BackstagePackageJson, PackageGraph } from '@backstage/cli-node'; import chalk from 'chalk'; import { resolve as resolvePath, join as joinPath } from 'node:path'; -import { OptionValues } from 'commander'; +import { cli } from 'cleye'; import { readJson, writeJson } from 'fs-extra'; import { minimatch } from 'minimatch'; import { runYarnInstall } from '../../lib/utils'; import replace from 'replace-in-file'; +import type { CommandContext } from '../../../../wiring/types'; declare module 'replace-in-file' { export default function (config: { @@ -38,10 +39,30 @@ declare module 'replace-in-file' { >; } -export default async (options: OptionValues) => { +export default async ({ args, info }: CommandContext) => { + const { + flags: { pattern, skipCodeChanges }, + } = cli( + { + help: info, + flags: { + pattern: { + type: String, + description: 'Override glob for matching packages to upgrade', + }, + skipCodeChanges: { + type: Boolean, + description: 'Skip code changes and only update package.json files', + }, + }, + }, + undefined, + args, + ); + const changed = await migrateMovedPackages({ - pattern: options.pattern, - skipCodeChanges: options.skipCodeChanges, + pattern, + skipCodeChanges, }); if (changed) { diff --git a/packages/cli/src/modules/migrate/index.ts b/packages/cli/src/modules/migrate/index.ts index 2dc4ed452d..c731479a51 100644 --- a/packages/cli/src/modules/migrate/index.ts +++ b/packages/cli/src/modules/migrate/index.ts @@ -14,8 +14,6 @@ * limitations under the License. */ import { createCliPlugin } from '../../wiring/factory'; -import { Command } from 'commander'; -import { lazy } from '../../wiring/lazy'; export default createCliPlugin({ pluginId: 'migrate', @@ -24,45 +22,13 @@ export default createCliPlugin({ 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' }); - }, + execute: { loader: () => import('./commands/versions/migrate') }, }); 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' }); - }, + execute: { loader: () => import('./commands/versions/bump') }, }); reg.addCommand({ diff --git a/packages/cli/src/modules/new/commands/new.test.ts b/packages/cli/src/modules/new/commands/new.test.ts index 2183d80062..0da42e7019 100644 --- a/packages/cli/src/modules/new/commands/new.test.ts +++ b/packages/cli/src/modules/new/commands/new.test.ts @@ -16,6 +16,7 @@ import { createNewPackage } from '../lib/createNewPackage'; import { default as newCommand } from './new'; +import type { CommandContext } from '../../../wiring/types'; jest.mock('../lib/createNewPackage'); @@ -34,13 +35,21 @@ describe.each([ }); it(`should generate naming options for --scope=${scope}`, async () => { - await newCommand({ scope, option: [], skipInstall: false }); + const args = ['--skip-install']; + if (scope) { + args.push('--scope', scope); + } + const context: CommandContext = { + args, + info: { usage: 'backstage-cli new', description: 'test' }, + }; + await newCommand(context); expect(createNewPackage).toHaveBeenCalledWith( expect.objectContaining({ - configOverrides: { + configOverrides: expect.objectContaining({ packageNamePrefix: prefix, packageNamePluginInfix: infix, - }, + }), }), ); }); diff --git a/packages/cli/src/modules/new/commands/new.ts b/packages/cli/src/modules/new/commands/new.ts index a38316dd62..082d0ca028 100644 --- a/packages/cli/src/modules/new/commands/new.ts +++ b/packages/cli/src/modules/new/commands/new.ts @@ -14,28 +14,75 @@ * limitations under the License. */ +import { cli } from 'cleye'; import { createNewPackage } from '../lib/createNewPackage'; +import type { CommandContext } from '../../../wiring/types'; -type ArgOptions = { - option: string[]; - select?: string; - skipInstall: boolean; - private?: boolean; - npmRegistry?: string; - scope?: string; - license?: string; - baseVersion?: string; -}; +export default async ({ args, info }: CommandContext) => { + for (const flag of ['skipInstall', 'npmRegistry', 'baseVersion']) { + if (args.some(a => a === `--${flag}` || a.startsWith(`--${flag}=`))) { + process.stderr.write( + `DEPRECATION WARNING: --${flag} is deprecated, use the kebab-case form instead\n`, + ); + } + } -export default async (opts: ArgOptions) => { const { - option: rawArgOptions, - select: preselectedTemplateId, - skipInstall, - scope, - private: isPrivate, - ...otherGlobals - } = opts; + flags: { + select, + option: rawArgOptions, + skipInstall, + scope, + npmRegistry, + baseVersion, + license, + private: isPrivate, + }, + } = cli( + { + help: info, + flags: { + select: { + type: String, + description: 'Select the thing you want to be creating upfront', + }, + option: { + type: [String] as const, + description: 'Pre-fill options for the creation process', + default: [] as string[], + }, + skipInstall: { + type: Boolean, + description: `Skips running 'yarn install' and 'yarn lint --fix'`, + }, + scope: { + type: String, + description: 'The scope to use for new packages', + }, + npmRegistry: { + type: String, + description: 'The package registry to use for new packages', + }, + baseVersion: { + type: String, + description: + 'The version to use for any new packages (default: 0.1.0)', + }, + license: { + type: String, + description: + 'The license to use for any new packages (default: Apache-2.0)', + }, + private: { + type: Boolean, + description: 'Mark new packages as private', + default: true, + }, + }, + }, + undefined, + args, + ); const prefilledParams = parseParams(rawArgOptions); @@ -50,8 +97,8 @@ export default async (opts: ArgOptions) => { } if ( - isPrivate === false || // set to false with --no-private flag - Object.values(otherGlobals).filter(Boolean).length !== 0 + isPrivate === false || + [npmRegistry, baseVersion, license].filter(Boolean).length !== 0 ) { console.warn( `Global template configuration via CLI flags is deprecated, see https://backstage.io/docs/cli/new for information on how to configure package templating`, @@ -60,16 +107,16 @@ export default async (opts: ArgOptions) => { await createNewPackage({ prefilledParams, - preselectedTemplateId, + preselectedTemplateId: select, configOverrides: { - license: otherGlobals.license, - version: otherGlobals.baseVersion, + license, + version: baseVersion, private: isPrivate, - publishRegistry: otherGlobals.npmRegistry, + publishRegistry: npmRegistry, packageNamePrefix: packagePrefix, packageNamePluginInfix: pluginInfix, }, - skipInstall, + skipInstall: Boolean(skipInstall), }); }; diff --git a/packages/cli/src/modules/new/index.ts b/packages/cli/src/modules/new/index.ts index ac21b825fe..5b2a6562ea 100644 --- a/packages/cli/src/modules/new/index.ts +++ b/packages/cli/src/modules/new/index.ts @@ -14,8 +14,6 @@ * limitations under the License. */ import { createCliPlugin } from '../../wiring/factory'; -import { Command } from 'commander'; -import { lazy } from '../../wiring/lazy'; import { NotImplementedError } from '@backstage/errors'; export default createCliPlugin({ @@ -25,45 +23,7 @@ export default createCliPlugin({ 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' }); - }, + execute: { loader: () => import('./commands/new') }, }); reg.addCommand({ diff --git a/packages/cli/src/modules/test/commands/package/test.ts b/packages/cli/src/modules/test/commands/package/test.ts index 9c13019bb5..687b66dbfd 100644 --- a/packages/cli/src/modules/test/commands/package/test.ts +++ b/packages/cli/src/modules/test/commands/package/test.ts @@ -14,9 +14,8 @@ * limitations under the License. */ -import { Command, OptionValues } from 'commander'; - import { runCheck, findOwnPaths } from '@backstage/cli-common'; +import type { CommandContext } from '../../../../wiring/types'; function includesAnyOf(hayStack: string[], ...needles: string[]) { for (const needle of needles) { @@ -27,15 +26,7 @@ function includesAnyOf(hayStack: string[], ...needles: string[]) { return false; } -export default async (_opts: OptionValues, cmd: Command) => { - // all args are forwarded to jest - let parent = cmd; - while (parent.parent) { - parent = parent.parent; - } - const allArgs = parent.args as string[]; - const args = allArgs.slice(allArgs.indexOf('test') + 1); - +export default async ({ args }: CommandContext) => { // Only include our config if caller isn't passing their own config if (!includesAnyOf(args, '-c', '--config')) { /* eslint-disable-next-line no-restricted-syntax */ diff --git a/packages/cli/src/modules/test/commands/repo/test.test.ts b/packages/cli/src/modules/test/commands/repo/test.test.ts index d2f2485717..fe0e733f8c 100644 --- a/packages/cli/src/modules/test/commands/repo/test.test.ts +++ b/packages/cli/src/modules/test/commands/repo/test.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { cli } from 'cleye'; import { createFlagFinder } from './test'; describe('createFlagFinder', () => { @@ -45,3 +46,58 @@ describe('createFlagFinder', () => { expect(find('--qux')).toBe(true); }); }); + +describe('repo test arg forwarding', () => { + // Mirrors the cleye configuration used in the repo test command handler + function parseRepoTestArgs(args: string[]) { + return cli( + { + help: false, + flags: { + since: { type: String }, + successCache: { type: Boolean }, + successCacheDir: { type: String }, + jestHelp: { type: Boolean }, + }, + ignoreArgv: type => type === 'unknown-flag' || type === 'argument', + }, + undefined, + args, + ); + } + + it('strips Backstage flags from args while preserving Jest flags and arguments', () => { + const args = [ + '--since', + 'main', + '--success-cache', + '--coverage', + '--watch', + 'path/to/test', + ]; + + const { flags } = parseRepoTestArgs(args); + + expect(flags.since).toBe('main'); + expect(flags.successCache).toBe(true); + expect(args).toEqual(['--coverage', '--watch', 'path/to/test']); + }); + + it('supports legacy camelCase flag names', () => { + const args = ['--successCache', '--successCacheDir', '/tmp/cache']; + + const { flags } = parseRepoTestArgs(args); + + expect(flags.successCache).toBe(true); + expect(flags.successCacheDir).toBe('/tmp/cache'); + expect(args).toEqual([]); + }); + + it('leaves args untouched when no Backstage flags are present', () => { + const args = ['--coverage', '--verbose', '--bail']; + + parseRepoTestArgs(args); + + expect(args).toEqual(['--coverage', '--verbose', '--bail']); + }); +}); diff --git a/packages/cli/src/modules/test/commands/repo/test.ts b/packages/cli/src/modules/test/commands/repo/test.ts index bb614798b4..c331caf994 100644 --- a/packages/cli/src/modules/test/commands/repo/test.ts +++ b/packages/cli/src/modules/test/commands/repo/test.ts @@ -16,12 +16,12 @@ import os from 'node:os'; import crypto from 'node:crypto'; +import { cli } from 'cleye'; import yargs from 'yargs'; // 'jest-cli' is included with jest and should be kept in sync with the installed jest version // eslint-disable-next-line @backstage/no-undeclared-imports import { run as runJest, yargsOptions as jestYargsOptions } from 'jest-cli'; import { relative as relativePath } from 'node:path'; -import { Command, OptionValues } from 'commander'; import { Lockfile, PackageGraph, SuccessCache } from '@backstage/cli-node'; import { @@ -31,6 +31,7 @@ import { findOwnPaths, isChildPath, } from '@backstage/cli-common'; +import type { CommandContext } from '../../../../wiring/types'; type JestProject = { displayName: string; @@ -130,36 +131,49 @@ export function createFlagFinder(args: string[]) { }; } -function removeOptionArg(args: string[], option: string, size: number = 2) { - let changed = false; - do { - changed = false; - - const index = args.indexOf(option); - if (index >= 0) { - changed = true; - args.splice(index, size); - } - const indexEq = args.findIndex(arg => arg.startsWith(`${option}=`)); - if (indexEq >= 0) { - changed = true; - args.splice(indexEq, 1); - } - } while (changed); -} - -export async function command(opts: OptionValues, cmd: Command): Promise { +export default async ({ args, info }: CommandContext) => { const testGlobal = global as TestGlobal; - // all args are forwarded to jest - let parent = cmd; - while (parent.parent) { - parent = parent.parent; + for (const flag of ['successCache', 'successCacheDir', 'jestHelp']) { + if (args.some(a => a === `--${flag}` || a.startsWith(`--${flag}=`))) { + process.stderr.write( + `DEPRECATION WARNING: --${flag} is deprecated, use the kebab-case form instead\n`, + ); + } } - const allArgs = parent.args as string[]; - const args = allArgs.slice(allArgs.indexOf('test') + 1); + + // Parse Backstage-specific flags; unknown flags and arguments are left in + // args so they can be forwarded to Jest. + const { flags: opts } = cli( + { + help: info, + flags: { + since: { + type: String, + description: + 'Only include test packages changed since the specified ref', + }, + successCache: { + type: Boolean, + description: 'Cache and skip tests for unchanged packages', + }, + successCacheDir: { + type: String, + description: 'Directory for the success cache', + }, + jestHelp: { + type: Boolean, + description: "Show Jest's own help output", + }, + }, + ignoreArgv: type => type === 'unknown-flag' || type === 'argument', + }, + undefined, + args, + ); const hasFlags = createFlagFinder(args); + const sinceRef = opts.since || undefined; // Parse the args to ensure that no file filters are provided, in which case we refuse to run const { _: parsedArgs } = await yargs(args).options(jestYargsOptions).argv; @@ -177,7 +191,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { // Run in watch mode unless in CI, coverage mode, or running all tests let isSingleWatchMode = args.includes('--watch'); if ( - !opts.since && + !sinceRef && !process.env.CI && !hasFlags('--coverage', '--watch', '--watchAll') ) { @@ -246,10 +260,6 @@ export async function command(opts: OptionValues, cmd: Command): Promise { args.push('--maxWorkers=2'); } - if (opts.since) { - removeOptionArg(args, '--since'); - } - let packageGraph: PackageGraph | undefined; async function getPackageGraph() { if (packageGraph) { @@ -261,10 +271,10 @@ export async function command(opts: OptionValues, cmd: Command): Promise { } let selectedProjects: string[] | undefined = undefined; - if (opts.since && !hasFlags('--selectProjects')) { + if (sinceRef && !hasFlags('--selectProjects')) { const graph = await getPackageGraph(); const changedPackages = await graph.listChangedPackages({ - ref: opts.since, + ref: sinceRef, analyzeLockfile: true, }); @@ -304,17 +314,13 @@ export async function command(opts: OptionValues, cmd: Command): Promise { }--no-node-snapshot`; } - if (args.includes('--jest-help')) { - removeOptionArg(args, '--jest-help'); + if (opts.jestHelp) { args.push('--help'); } // This code path is enabled by the --successCache flag, which is specific to // the `repo test` command in the Backstage CLI. if (opts.successCache) { - removeOptionArg(args, '--successCache', 1); - removeOptionArg(args, '--successCacheDir'); - // Refuse to run if file filters are provided if (parsedArgs.length > 0) { throw new Error( @@ -439,4 +445,4 @@ export async function command(opts: OptionValues, cmd: Command): Promise { } await runJest(args); -} +}; diff --git a/packages/cli/src/modules/test/index.ts b/packages/cli/src/modules/test/index.ts index f003d9c054..627648332b 100644 --- a/packages/cli/src/modules/test/index.ts +++ b/packages/cli/src/modules/test/index.ts @@ -14,8 +14,6 @@ * limitations under the License. */ import { createCliPlugin } from '../../wiring/factory'; -import { Command } from 'commander'; -import { lazy } from '../../wiring/lazy'; export default createCliPlugin({ pluginId: 'test', @@ -24,43 +22,14 @@ export default createCliPlugin({ 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.allowExcessArguments(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' }); - }, + execute: { loader: () => import('./commands/repo/test') }, }); 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.allowExcessArguments(true); - command.helpOption('--backstage-cli-help'); - command.action( - lazy(() => import('./commands/package/test'), 'default'), - ); - await command.parseAsync(args, { from: 'user' }); - }, + execute: { loader: () => import('./commands/package/test') }, }); }, }); diff --git a/packages/cli/src/modules/translations/lib/extractTranslations.test.ts b/packages/cli/src/modules/translations/lib/extractTranslations.test.ts index 27b67db052..20ff05b69e 100644 --- a/packages/cli/src/modules/translations/lib/extractTranslations.test.ts +++ b/packages/cli/src/modules/translations/lib/extractTranslations.test.ts @@ -52,7 +52,7 @@ describe('extractTranslations', () => { expect(refs[0].messages).toHaveProperty(['membersListCard.title']); // Verify interpolation placeholders are preserved - expect(refs[0].messages['membersListCard.subtitle']).toContain( + expect(refs[0].messages['membersListCard.title']).toContain( '{{groupName}}', ); }); diff --git a/packages/cli/src/wiring/CliInitializer.test.ts b/packages/cli/src/wiring/CliInitializer.test.ts index e04a429ede..10de6d8e09 100644 --- a/packages/cli/src/wiring/CliInitializer.test.ts +++ b/packages/cli/src/wiring/CliInitializer.test.ts @@ -92,6 +92,124 @@ describe('CliInitializer', () => { expect(process.exit).toHaveBeenCalledWith(0); }); + it('should run experimental commands but exclude them from help output', async () => { + expect.assertions(3); + process.argv = ['node', 'cli', 'secret']; + const initializer = new CliInitializer(); + initializer.add( + createCliPlugin({ + pluginId: 'test', + init: async reg => { + reg.addCommand({ + path: ['visible'], + description: 'A visible command', + execute: () => Promise.resolve(), + }); + reg.addCommand({ + path: ['secret'], + description: 'An experimental command', + experimental: true, + execute: ({ args }) => { + expect(args).toEqual([]); + return Promise.resolve(); + }, + }); + }, + }), + ); + await initializer.run(); + expect(process.exit).toHaveBeenCalledWith(0); + + process.argv = ['node', 'cli', '--help']; + const writeSpy = jest.spyOn(process.stdout, 'write'); + const initializer2 = new CliInitializer(); + initializer2.add( + createCliPlugin({ + pluginId: 'test', + init: async reg => { + reg.addCommand({ + path: ['visible'], + description: 'A visible command', + execute: () => Promise.resolve(), + }); + reg.addCommand({ + path: ['secret'], + description: 'An experimental command', + experimental: true, + execute: () => Promise.resolve(), + }); + }, + }), + ); + await initializer2.run(); + const helpOutput = writeSpy.mock.calls.map(c => c[0]).join(''); + expect(helpOutput).not.toContain('secret'); + writeSpy.mockRestore(); + }); + + it('should hide tree nodes when all children are experimental', async () => { + process.argv = ['node', 'cli', '--help']; + const writeSpy = jest.spyOn(process.stdout, 'write'); + const initializer = new CliInitializer(); + initializer.add( + createCliPlugin({ + pluginId: 'test', + init: async reg => { + reg.addCommand({ + path: ['visible'], + description: 'A visible command', + execute: () => Promise.resolve(), + }); + reg.addCommand({ + path: ['group', 'alpha'], + description: 'First experimental command', + experimental: true, + execute: () => Promise.resolve(), + }); + reg.addCommand({ + path: ['group', 'beta'], + description: 'Second experimental command', + experimental: true, + execute: () => Promise.resolve(), + }); + }, + }), + ); + await initializer.run(); + const helpOutput = writeSpy.mock.calls.map(c => c[0]).join(''); + expect(helpOutput).toContain('visible'); + expect(helpOutput).not.toContain('group'); + writeSpy.mockRestore(); + }); + + it('should show tree nodes when some children are visible', async () => { + process.argv = ['node', 'cli', '--help']; + const writeSpy = jest.spyOn(process.stdout, 'write'); + const initializer = new CliInitializer(); + initializer.add( + createCliPlugin({ + pluginId: 'test', + init: async reg => { + reg.addCommand({ + path: ['group', 'alpha'], + description: 'A visible nested command', + execute: () => Promise.resolve(), + }); + reg.addCommand({ + path: ['group', 'beta'], + description: 'An experimental nested command', + experimental: true, + execute: () => Promise.resolve(), + }); + }, + }), + ); + await initializer.run(); + const helpOutput = writeSpy.mock.calls.map(c => c[0]).join(''); + expect(helpOutput).toContain('group'); + writeSpy.mockRestore(); + }); + it('should pass positional args to the subcommand if nested', async () => { expect.assertions(2); process.argv = [ diff --git a/packages/cli/src/wiring/CliInitializer.ts b/packages/cli/src/wiring/CliInitializer.ts index c07fbb26f3..d9af1e8a4d 100644 --- a/packages/cli/src/wiring/CliInitializer.ts +++ b/packages/cli/src/wiring/CliInitializer.ts @@ -15,7 +15,7 @@ */ import { CommandGraph } from './CommandGraph'; -import { CliFeature, OpaqueCliPlugin } from './types'; +import { BackstageCommand, CliFeature, OpaqueCliPlugin } from './types'; import { CommandRegistry } from './CommandRegistry'; import { Command } from 'commander'; import { version } from './version'; @@ -24,6 +24,17 @@ import { exitWithError } from './errors'; import { ForwardedError } from '@backstage/errors'; import { isPromise } from 'node:util/types'; +function isNodeHidden( + node: + | { $$type: '@tree/leaf'; command: BackstageCommand } + | { $$type: '@tree/root'; children: unknown[] }, +): boolean { + if (node.$$type === '@tree/leaf') { + return !!node.command.deprecated || !!node.command.experimental; + } + return node.children.every(child => isNodeHidden(child as any)); +} + type UninitializedFeature = CliFeature | Promise<{ default: CliFeature }>; export class CliInitializer { @@ -80,7 +91,9 @@ export class CliInitializer { const { node, argParser } = queue.shift()!; if (node.$$type === '@tree/root') { const treeParser = argParser - .command(`${node.name} [command]`) + .command(`${node.name} [command]`, { + hidden: isNodeHidden(node), + }) .description(node.name); queue.push( @@ -91,7 +104,9 @@ export class CliInitializer { ); } else { argParser - .command(node.name, { hidden: !!node.command.deprecated }) + .command(node.name, { + hidden: !!node.command.deprecated || !!node.command.experimental, + }) .description(node.command.description) .helpOption(false) .allowUnknownOption(true) diff --git a/packages/cli/src/wiring/types.ts b/packages/cli/src/wiring/types.ts index e0f697990f..fa3d2341f0 100644 --- a/packages/cli/src/wiring/types.ts +++ b/packages/cli/src/wiring/types.ts @@ -35,6 +35,7 @@ export interface BackstageCommand { path: string[]; description: string; deprecated?: boolean; + experimental?: boolean; execute: | CommandExecuteFn | { diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 231ee0f4c1..16ec1b1b70 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -49,6 +49,7 @@ "@backstage/config": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/types": "workspace:^", + "@backstage/ui": "workspace:^", "@backstage/version-bridge": "workspace:^", "@types/prop-types": "^15.7.3", "history": "^5.0.0", diff --git a/packages/core-app-api/src/apis/system/ApiProvider.test.tsx b/packages/core-app-api/src/apis/system/ApiProvider.test.tsx index 4273587c00..0a899ff88a 100644 --- a/packages/core-app-api/src/apis/system/ApiProvider.test.tsx +++ b/packages/core-app-api/src/apis/system/ApiProvider.test.tsx @@ -114,12 +114,16 @@ describe('ApiProvider', () => { withLogCollector(['error'], () => { expect(() => { render(); - }).toThrow(/^API context is not available/); + }).toThrow('No implementation available for apiRef{x}'); }).error, ).toEqual([ - expect.stringContaining('Error: API context is not available'), + expect.stringContaining( + 'Error: No implementation available for apiRef{x}', + ), expect.objectContaining({ type: 'unhandled-exception' }), - expect.stringContaining('Error: API context is not available'), + expect.stringContaining( + 'Error: No implementation available for apiRef{x}', + ), expect.objectContaining({ type: 'unhandled-exception' }), expect.stringContaining( 'The above error occurred in the component', @@ -130,12 +134,16 @@ describe('ApiProvider', () => { withLogCollector(['error'], () => { expect(() => { render(); - }).toThrow(/^API context is not available/); + }).toThrow('No implementation available for apiRef{x}'); }).error, ).toEqual([ - expect.stringContaining('Error: API context is not available'), + expect.stringContaining( + 'Error: No implementation available for apiRef{x}', + ), expect.objectContaining({ type: 'unhandled-exception' }), - expect.stringContaining('Error: API context is not available'), + expect.stringContaining( + 'Error: No implementation available for apiRef{x}', + ), expect.objectContaining({ type: 'unhandled-exception' }), expect.stringContaining( 'The above error occurred in the component', diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index bfaeb2f2db..4f0d2b2d4d 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -45,7 +45,9 @@ import { fetchApiRef, discoveryApiRef, errorApiRef, + useAnalytics, } from '@backstage/core-plugin-api'; +import { BUIProvider } from '@backstage/ui'; import { AppLanguageApi, appLanguageApiRef, @@ -339,6 +341,7 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be for (const flag of plugin.getFeatureFlags()) { featureFlagsApi.registerFlag({ name: flag.name, + description: flag.description, pluginId: plugin.getId(), }); } @@ -389,26 +392,28 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be return ( - - - - + + + - }>{children} - - - - + + }>{children} + + + + + ); }; diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 605c6b2bd9..67ad3f1c97 100644 --- a/packages/core-compat-api/package.json +++ b/packages/core-compat-api/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "@backstage/core-plugin-api": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-app-react": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", diff --git a/packages/core-compat-api/report.api.md b/packages/core-compat-api/report.api.md index ae202c96f7..13d0849000 100644 --- a/packages/core-compat-api/report.api.md +++ b/packages/core-compat-api/report.api.md @@ -22,11 +22,13 @@ import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; import { JSX as JSX_3 } from 'react/jsx-runtime'; +import { PropsWithChildren } from 'react'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; import { SubRouteRef } from '@backstage/core-plugin-api'; import { SubRouteRef as SubRouteRef_2 } from '@backstage/frontend-plugin-api'; +import { TypesToApiRefs } from '@backstage/frontend-plugin-api'; // @public export function compatWrapper(element: ReactNode): JSX_3.Element; @@ -143,5 +145,15 @@ export type ToNewRouteRef = ? ExternalRouteRef_2 : never; +// @public +export function withApis( + apis: TypesToApiRefs, +): ( + WrappedComponent: ComponentType, +) => { + (props: PropsWithChildren>): JSX_3.Element; + displayName: string; +}; + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/core-compat-api/src/collectEntityPageContents.test.tsx b/packages/core-compat-api/src/collectEntityPageContents.test.tsx index fd74a7e89b..5ee1439360 100644 --- a/packages/core-compat-api/src/collectEntityPageContents.test.tsx +++ b/packages/core-compat-api/src/collectEntityPageContents.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ExtensionAttachToSpec } from '@backstage/frontend-plugin-api'; +import { ExtensionAttachTo } from '@backstage/frontend-plugin-api'; import { EntityLayout, EntitySwitch, isKind } from '@backstage/plugin-catalog'; import { JSX } from 'react'; import { collectEntityPageContents } from './collectEntityPageContents'; @@ -73,7 +73,7 @@ const otherTestContent = ( function collect(element: JSX.Element) { const result = new Array<{ id: string; - attachTo: ExtensionAttachToSpec; + attachTo: ExtensionAttachTo; }>(); collectEntityPageContents(element, { diff --git a/packages/core-compat-api/src/index.ts b/packages/core-compat-api/src/index.ts index 6d8041ef44..b273fae9e4 100644 --- a/packages/core-compat-api/src/index.ts +++ b/packages/core-compat-api/src/index.ts @@ -31,3 +31,4 @@ export { convertLegacyRouteRefs, type ToNewRouteRef, } from './convertLegacyRouteRef'; +export { withApis } from './withApis'; diff --git a/packages/core-compat-api/src/withApis.test.tsx b/packages/core-compat-api/src/withApis.test.tsx new file mode 100644 index 0000000000..eb13ec76ee --- /dev/null +++ b/packages/core-compat-api/src/withApis.test.tsx @@ -0,0 +1,85 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createApiRef } from '@backstage/frontend-plugin-api'; +import { + TestApiProvider, + withLogCollector, +} from '@backstage/frontend-test-utils'; +import { render, screen } from '@testing-library/react'; +import { withApis } from './withApis'; + +describe('withApis', () => { + type MyApi = () => string; + const myApiRef = createApiRef({ id: 'my-api' }); + + const MyComponent = withApis({ getMessage: myApiRef })(({ getMessage }) => { + return

message: {getMessage()}

; + }); + + it('should inject APIs as props and set display name', () => { + render( + 'hello']]}> + + , + ); + + expect(screen.getByText('message: hello')).toBeInTheDocument(); + expect(MyComponent.displayName).toBe('withApis(Component)'); + }); + + it('should ignore properties from the prototype', () => { + const otherRef = createApiRef({ id: 'other' }); + const proto = { other: otherRef }; + const props = { getMessage: { enumerable: true, value: myApiRef } }; + const obj = Object.create(proto, props) as { + getMessage: typeof myApiRef; + other: typeof otherRef; + }; + + const WeirdComponent = withApis(obj)(({ getMessage }) => { + return

message: {getMessage()}

; + }); + + render( + 'hello']]}> + + , + ); + + expect(screen.getByText('message: hello')).toBeInTheDocument(); + }); + + it('should throw NotImplementedError if the API is not available', () => { + expect( + withLogCollector(['error'], () => { + expect(() => { + render( + + + , + ); + }).toThrow('No implementation available for apiRef{my-api}'); + }).error, + ).toEqual( + expect.arrayContaining([ + expect.stringContaining( + 'No implementation available for apiRef{my-api}', + ), + ]), + ); + }); +}); diff --git a/packages/core-compat-api/src/withApis.tsx b/packages/core-compat-api/src/withApis.tsx new file mode 100644 index 0000000000..fc7fc548ee --- /dev/null +++ b/packages/core-compat-api/src/withApis.tsx @@ -0,0 +1,59 @@ +/* + * 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 { ComponentType, PropsWithChildren } from 'react'; +import { TypesToApiRefs, useApiHolder } from '@backstage/frontend-plugin-api'; +import { NotImplementedError } from '@backstage/errors'; + +/** + * Wrapper for giving component an API context. + * + * @param apis - APIs for the context. + * @public + */ +export function withApis(apis: TypesToApiRefs) { + return function withApisWrapper( + WrappedComponent: ComponentType, + ) { + const Hoc = (props: PropsWithChildren>) => { + const apiHolder = useApiHolder(); + + const impls = {} as T; + + for (const key in apis) { + if (Object.hasOwn(apis, key)) { + const ref = apis[key]; + + const api = apiHolder.get(ref); + if (!api) { + throw new NotImplementedError( + `No implementation available for ${ref}`, + ); + } + impls[key] = api; + } + } + + return ; + }; + const displayName = + WrappedComponent.displayName || WrappedComponent.name || 'Component'; + + Hoc.displayName = `withApis(${displayName})`; + + return Hoc; + }; +} diff --git a/packages/core-components/report-alpha.api.md b/packages/core-components/report-alpha.api.md index e6a54e32a4..420e54df52 100644 --- a/packages/core-components/report-alpha.api.md +++ b/packages/core-components/report-alpha.api.md @@ -21,6 +21,9 @@ export const coreComponentsTranslationRef: TranslationRef< readonly 'table.pagination.lastTooltip': 'Last Page'; readonly 'table.pagination.nextTooltip': 'Next Page'; readonly 'table.pagination.previousTooltip': 'Previous Page'; + readonly 'emptyState.missingAnnotation.title': 'Missing Annotation'; + readonly 'emptyState.missingAnnotation.actionTitle': 'Add the annotation to your component YAML as shown in the highlighted example below:'; + readonly 'emptyState.missingAnnotation.readMore': 'Read more'; readonly 'signIn.title': 'Sign In'; readonly 'signIn.loginFailed': 'Login failed'; readonly 'signIn.customProvider.title': 'Custom User'; @@ -44,9 +47,6 @@ export const coreComponentsTranslationRef: TranslationRef< readonly 'errorPage.goBack': 'Go back'; readonly 'errorPage.showMoreDetails': 'Show more details'; readonly 'errorPage.showLessDetails': 'Show less details'; - readonly 'emptyState.missingAnnotation.title': 'Missing Annotation'; - readonly 'emptyState.missingAnnotation.actionTitle': 'Add the annotation to your component YAML as shown in the highlighted example below:'; - readonly 'emptyState.missingAnnotation.readMore': 'Read more'; readonly 'supportConfig.default.title': 'Support Not Configured'; readonly 'supportConfig.default.linkTitle': 'Add `app.support` config key'; readonly 'errorBoundary.title': 'Please contact {{slackChannel}} for help.'; diff --git a/packages/core-plugin-api/report.api.md b/packages/core-plugin-api/report.api.md index 41887f7e63..654bbe94d1 100644 --- a/packages/core-plugin-api/report.api.md +++ b/packages/core-plugin-api/report.api.md @@ -500,6 +500,7 @@ export type PluginConfig< // @public export type PluginFeatureFlagConfig = { name: string; + description?: string; }; export { ProfileInfo }; diff --git a/packages/core-plugin-api/src/plugin/types.ts b/packages/core-plugin-api/src/plugin/types.ts index 3ba7706e16..057f493530 100644 --- a/packages/core-plugin-api/src/plugin/types.ts +++ b/packages/core-plugin-api/src/plugin/types.ts @@ -73,6 +73,8 @@ export type BackstagePlugin< export type PluginFeatureFlagConfig = { /** Feature flag name */ name: string; + /** Feature flag description */ + description?: string; }; /** diff --git a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx index 750dfe6cea..367652b70d 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx @@ -131,7 +131,7 @@ const overviewContent = (
- + @@ -266,7 +266,7 @@ const apiPage = ( - + @@ -301,7 +301,7 @@ const userPage = ( - + @@ -317,7 +317,7 @@ const groupPage = ( - + @@ -339,7 +339,7 @@ const systemPage = ( - + @@ -357,7 +357,6 @@ const systemPage = ( - + diff --git a/packages/frontend-app-api/report.api.md b/packages/frontend-app-api/report.api.md index e8fc019007..bf69cc83e6 100644 --- a/packages/frontend-app-api/report.api.md +++ b/packages/frontend-app-api/report.api.md @@ -4,10 +4,13 @@ ```ts import { ApiHolder } from '@backstage/core-plugin-api'; +import { ApiHolder as ApiHolder_2 } from '@backstage/frontend-plugin-api'; import { AppNode } from '@backstage/frontend-plugin-api'; import { AppTree } from '@backstage/frontend-plugin-api'; import { ConfigApi } from '@backstage/core-plugin-api'; -import { ExtensionFactoryMiddleware } from '@backstage/frontend-plugin-api'; +import { ExtensionDataContainer } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDataValue } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { FrontendFeature } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -169,7 +172,6 @@ export type CreateSpecializedAppOptions = { bindRoutes?(context: { bind: CreateAppRouteBinder }): void; advanced?: { apis?: ApiHolder; - allowUnknownExtensionConfig?: boolean; extensionFactoryMiddleware?: | ExtensionFactoryMiddleware | ExtensionFactoryMiddleware[]; @@ -177,6 +179,18 @@ export type CreateSpecializedAppOptions = { }; }; +// @public (undocumented) +export type ExtensionFactoryMiddleware = ( + originalFactory: (contextOverrides?: { + config?: JsonObject; + }) => ExtensionDataContainer, + context: { + node: AppNode; + apis: ApiHolder_2; + config?: JsonObject; + }, +) => Iterable>; + // @public export type FrontendPluginInfoResolver = (ctx: { packageJson(): Promise; diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts index b1da35313e..fcef41daec 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts @@ -19,7 +19,6 @@ import { Extension, ExtensionDataRef, ExtensionDefinition, - ExtensionFactoryMiddleware, ExtensionInput, PortableSchema, ResolvedExtensionInput, @@ -29,6 +28,7 @@ import { createExtensionInput, createFrontendPlugin, } from '@backstage/frontend-plugin-api'; +import { ExtensionFactoryMiddleware } from '../wiring/types'; import { createAppNodeInstance, instantiateAppNodeTree, diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts index 070cf1d687..9e8e3cc2eb 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts @@ -18,10 +18,10 @@ import { ApiHolder, ExtensionDataContainer, ExtensionDataRef, - ExtensionFactoryMiddleware, ExtensionInput, ResolvedExtensionInputs, } from '@backstage/frontend-plugin-api'; +import { ExtensionFactoryMiddleware } from '../wiring/types'; import mapValues from 'lodash/mapValues'; import { AppNode, AppNodeInstance } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports diff --git a/packages/frontend-app-api/src/tree/resolveAppTree.test.ts b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts index bffc156509..a8e63f82e0 100644 --- a/packages/frontend-app-api/src/tree/resolveAppTree.test.ts +++ b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts @@ -149,7 +149,7 @@ describe('buildAppTree', () => { attachTo: [ { id: 'a', input: 'x' }, { id: 'b', input: 'x' }, - ], + ] as any, }, { ...baseSpec, @@ -157,7 +157,7 @@ describe('buildAppTree', () => { attachTo: [ { id: 'b', input: 'x' }, { id: 'c', input: 'x' }, - ], + ] as any, }, ], collector, diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx index e21befc80e..51c31aca25 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx @@ -103,7 +103,10 @@ describe('createSpecializedApp', () => { features: [ createFrontendPlugin({ pluginId: 'test', - featureFlags: [{ name: 'a' }, { name: 'b' }], + featureFlags: [ + { name: 'a' }, + { name: 'b', description: 'Feature B description' }, + ], extensions: [ createExtension({ attachTo: { id: 'root', input: 'app' }, @@ -146,6 +149,11 @@ describe('createSpecializedApp', () => { expect(screen.getByText('flags:test=a,test=b')).toBeInTheDocument(); + expect(flags).toEqual([ + { name: 'a', pluginId: 'test' }, + { name: 'b', pluginId: 'test', description: 'Feature B description' }, + ]); + expect(app.apis).toMatchInlineSnapshot(` ApiResolver { "apis": Map { diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx index d56a609bb9..fda00b70b1 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx @@ -29,9 +29,9 @@ import { createApiFactory, routeResolutionApiRef, AppNode, - ExtensionFactoryMiddleware, FrontendFeature, } from '@backstage/frontend-plugin-api'; +import { ExtensionFactoryMiddleware } from './types'; import { AnyApiFactory, ApiHolder, @@ -255,17 +255,6 @@ export type CreateSpecializedAppOptions = { */ apis?: ApiHolder; - /** - * If set to true, the system will silently accept and move on if - * encountering config for extensions that do not exist. The default is to - * reject such config to help catch simple mistakes. - * - * This flag can be useful in some scenarios where you have a dynamic set of - * extensions enabled at different times, but also increases the risk of - * accidentally missing e.g. simple typos in your config. - */ - allowUnknownExtensionConfig?: boolean; - /** * Applies one or more middleware on every extension, as they are added to * the application. @@ -357,6 +346,7 @@ export function createSpecializedApp(options?: CreateSpecializedAppOptions): { OpaqueFrontendPlugin.toInternal(feature).featureFlags.forEach(flag => featureFlagApi.registerFlag({ name: flag.name, + description: flag.description, pluginId: feature.id, }), ); @@ -365,6 +355,7 @@ export function createSpecializedApp(options?: CreateSpecializedAppOptions): { toInternalFrontendModule(feature).featureFlags.forEach(flag => featureFlagApi.registerFlag({ name: flag.name, + description: flag.description, pluginId: feature.pluginId, }), ); diff --git a/packages/frontend-app-api/src/wiring/index.ts b/packages/frontend-app-api/src/wiring/index.ts index 1e18859ddd..cae2117df5 100644 --- a/packages/frontend-app-api/src/wiring/index.ts +++ b/packages/frontend-app-api/src/wiring/index.ts @@ -20,3 +20,4 @@ export { } from './createSpecializedApp'; export { type FrontendPluginInfoResolver } from './createPluginInfoAttacher'; export { type AppError, type AppErrorTypes } from './createErrorCollector'; +export { type ExtensionFactoryMiddleware } from './types'; diff --git a/packages/frontend-app-api/src/wiring/types.ts b/packages/frontend-app-api/src/wiring/types.ts new file mode 100644 index 0000000000..0bb7497179 --- /dev/null +++ b/packages/frontend-app-api/src/wiring/types.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { JsonObject } from '@backstage/types'; +import { + ApiHolder, + AppNode, + ExtensionDataContainer, + ExtensionDataRef, + ExtensionDataValue, +} from '@backstage/frontend-plugin-api'; + +/** @public */ +export type ExtensionFactoryMiddleware = ( + originalFactory: (contextOverrides?: { + config?: JsonObject; + }) => ExtensionDataContainer, + context: { + node: AppNode; + apis: ApiHolder; + config?: JsonObject; + }, +) => Iterable>; diff --git a/packages/frontend-defaults/report.api.md b/packages/frontend-defaults/report.api.md index 75392d2dc1..72bd0d785e 100644 --- a/packages/frontend-defaults/report.api.md +++ b/packages/frontend-defaults/report.api.md @@ -8,7 +8,7 @@ import { AppErrorTypes } from '@backstage/frontend-app-api'; import { Config } from '@backstage/config'; import { ConfigApi } from '@backstage/frontend-plugin-api'; import { CreateAppRouteBinder } from '@backstage/frontend-app-api'; -import { ExtensionFactoryMiddleware } from '@backstage/frontend-plugin-api'; +import { ExtensionFactoryMiddleware } from '@backstage/frontend-app-api'; import { FrontendFeature } from '@backstage/frontend-plugin-api'; import { FrontendFeatureLoader } from '@backstage/frontend-plugin-api'; import { FrontendPluginInfoResolver } from '@backstage/frontend-app-api'; @@ -23,7 +23,6 @@ export function createApp(options?: CreateAppOptions): { // @public export interface CreateAppOptions { advanced?: { - allowUnknownExtensionConfig?: boolean; configLoader?: () => Promise<{ config: ConfigApi; }>; @@ -37,11 +36,6 @@ export interface CreateAppOptions { features?: (FrontendFeature | FrontendFeatureLoader)[]; } -// @public @deprecated (undocumented) -export function createPublicSignInApp(options?: CreateAppOptions): { - createRoot(): JSX_2; -}; - // @public (undocumented) export function discoverAvailableFeatures(config: Config): { features: (FrontendFeature | FrontendFeatureLoader)[]; diff --git a/packages/frontend-defaults/src/createApp.test.tsx b/packages/frontend-defaults/src/createApp.test.tsx index 55ca0b3772..1d1c5ebf53 100644 --- a/packages/frontend-defaults/src/createApp.test.tsx +++ b/packages/frontend-defaults/src/createApp.test.tsx @@ -283,7 +283,9 @@ describe('createApp', () => { ).resolves.toBeInTheDocument(); }); - it('should allow unknown extension config if the flag is set', async () => { + it('should warn about unknown extension config', async () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const app = createApp({ features: [ appPlugin, @@ -300,7 +302,6 @@ describe('createApp', () => { }), ], advanced: { - allowUnknownExtensionConfig: true, configLoader: async () => ({ config: mockApis.config({ data: { @@ -316,6 +317,12 @@ describe('createApp', () => { await renderWithEffects(app.createRoot()); await expect(screen.findByText('Derp')).resolves.toBeInTheDocument(); + expect(warnSpy).toHaveBeenCalledWith('App startup encountered warnings:'); + expect(warnSpy).toHaveBeenCalledWith( + 'INVALID_EXTENSION_CONFIG_KEY: Extension unknown:lols/wut does not exist', + ); + + warnSpy.mockRestore(); }); it('should make the app structure available through the AppTreeApi', async () => { let appTreeApi: AppTreeApi | undefined = undefined; diff --git a/packages/frontend-defaults/src/createApp.tsx b/packages/frontend-defaults/src/createApp.tsx index 4ff67f6d5a..220a7b743f 100644 --- a/packages/frontend-defaults/src/createApp.tsx +++ b/packages/frontend-defaults/src/createApp.tsx @@ -18,7 +18,6 @@ import { JSX, lazy, ReactNode, Suspense } from 'react'; import { ConfigApi, coreExtensionData, - ExtensionFactoryMiddleware, FrontendFeature, FrontendFeatureLoader, } from '@backstage/frontend-plugin-api'; @@ -31,6 +30,7 @@ import { ConfigReader } from '@backstage/config'; import { CreateAppRouteBinder, createSpecializedApp, + ExtensionFactoryMiddleware, FrontendPluginInfoResolver, } from '@backstage/frontend-app-api'; import appPlugin from '@backstage/plugin-app'; @@ -58,17 +58,6 @@ export interface CreateAppOptions { * Advanced, more rarely used options. */ advanced?: { - /** - * If set to true, the system will silently accept and move on if - * encountering config for extensions that do not exist. The default is to - * reject such config to help catch simple mistakes. - * - * This flag can be useful in some scenarios where you have a dynamic set of - * extensions enabled at different times, but also increases the risk of - * accidentally missing e.g. simple typos in your config. - */ - allowUnknownExtensionConfig?: boolean; - /** * Sets a custom config loader, replacing the builtin one. * diff --git a/packages/frontend-defaults/src/createPublicSignInApp.test.tsx b/packages/frontend-defaults/src/createPublicSignInApp.test.tsx deleted file mode 100644 index 398dbcbf4a..0000000000 --- a/packages/frontend-defaults/src/createPublicSignInApp.test.tsx +++ /dev/null @@ -1,124 +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 { createFrontendModule } from '@backstage/frontend-plugin-api'; -import { SignInPageBlueprint } from '@backstage/plugin-app-react'; -import { render, screen, waitFor } from '@testing-library/react'; -import { useEffect } from 'react'; -import { createPublicSignInApp } from './createPublicSignInApp'; -import { mockApis } from '@backstage/test-utils'; - -describe('createPublicSignInApp', () => { - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('should render a sign-in page', async () => { - const app = createPublicSignInApp({ - advanced: { - configLoader: async () => ({ config: mockApis.config() }), - }, - features: [ - createFrontendModule({ - pluginId: 'app', - extensions: [ - SignInPageBlueprint.make({ - params: { - loader: async () => () =>
Sign in page
, - }, - }), - ], - }), - ], - }); - - render(app.createRoot()); - - await expect( - screen.findByText('Sign in page'), - ).resolves.toBeInTheDocument(); - }); - - it('should render the form redirect on sign-in', async () => { - const submitSpy = jest - .spyOn(HTMLFormElement.prototype, 'submit') - .mockReturnValue(); - - const app = createPublicSignInApp({ - advanced: { - configLoader: async () => ({ config: mockApis.config() }), - }, - features: [ - createFrontendModule({ - pluginId: 'app', - extensions: [ - SignInPageBlueprint.make({ - params: { - loader: - async () => - ({ onSignInSuccess }) => { - useEffect(() => { - onSignInSuccess( - mockApis.identity({ token: 'mock-token' }), - ); - }, [onSignInSuccess]); - return
; - }, - }, - }), - ], - }), - ], - }); - - const { baseElement } = render(app.createRoot()); - - await waitFor(() => { - expect(submitSpy).toHaveBeenCalled(); - }); - - expect(baseElement).toMatchInlineSnapshot(` - -
-
- - - -
-
- - `); - }); -}); diff --git a/packages/frontend-defaults/src/index.ts b/packages/frontend-defaults/src/index.ts index 37ec62e794..c7f3e468a2 100644 --- a/packages/frontend-defaults/src/index.ts +++ b/packages/frontend-defaults/src/index.ts @@ -21,7 +21,6 @@ */ export { createApp, type CreateAppOptions } from './createApp'; -export { createPublicSignInApp } from './createPublicSignInApp'; export { discoverAvailableFeatures } from './discovery'; export { resolveAsyncFeatures } from './resolution'; export { maybeCreateErrorPage } from './maybeCreateErrorPage'; diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index c83511fc47..4624e68dda 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -81,7 +81,7 @@ export type AnalyticsImplementation = { captureEvent(event: AnalyticsEvent): void; }; -// @public +// @public @deprecated export const AnalyticsImplementationBlueprint: ExtensionBlueprint_2<{ kind: 'analytics'; params: ( @@ -104,7 +104,7 @@ export const AnalyticsImplementationBlueprint: ExtensionBlueprint_2<{ }; }>; -// @public (undocumented) +// @public @deprecated (undocumented) export type AnalyticsImplementationFactory< Deps extends { [name in string]: unknown; @@ -138,9 +138,6 @@ export type AnyApiFactory = ApiFactory< // @public export type AnyApiRef = ApiRef; -// @public @deprecated (undocumented) -export type AnyExtensionDataRef = ExtensionDataRef; - // @public export type AnyRouteRefParams = | { @@ -866,7 +863,7 @@ export interface DialogApiDialog { result(): Promise; update( elementOrComponent: - | React.JSX.Element + | JSX.Element | ((props: { dialog: DialogApiDialog }) => JSX.Element), ): void; } @@ -924,7 +921,7 @@ export interface Extension { // (undocumented) $$type: '@backstage/Extension'; // (undocumented) - readonly attachTo: ExtensionAttachToSpec; + readonly attachTo: ExtensionAttachTo; // (undocumented) readonly configSchema?: PortableSchema; // (undocumented) @@ -934,18 +931,10 @@ export interface Extension { } // @public (undocumented) -export type ExtensionAttachTo = - | { - id: string; - input: string; - } - | Array<{ - id: string; - input: string; - }>; - -// @public @deprecated (undocumented) -export type ExtensionAttachToSpec = ExtensionAttachTo; +export type ExtensionAttachTo = { + id: string; + input: string; +}; // @public (undocumented) export interface ExtensionBlueprint< @@ -1168,7 +1157,7 @@ export type ExtensionDataRef< }; // @public (undocumented) -export type ExtensionDataRefToValue = +export type ExtensionDataRefToValue = TDataRef extends ExtensionDataRef ? ExtensionDataValue : never; @@ -1226,7 +1215,7 @@ export type ExtensionDefinitionParameters = { params?: object | ExtensionBlueprintDefineParams; }; -// @public (undocumented) +// @public @deprecated (undocumented) export type ExtensionFactoryMiddleware = ( originalFactory: (contextOverrides?: { config?: JsonObject; @@ -1290,6 +1279,7 @@ export type FeatureFlag = { // @public export type FeatureFlagConfig = { name: string; + description?: string; }; // @public @@ -1470,7 +1460,7 @@ export const microsoftAuthApiRef: ApiRef< SessionApi >; -// @public +// @public @deprecated export const NavItemBlueprint: ExtensionBlueprint_2<{ kind: 'nav-item'; params: { @@ -1722,7 +1712,6 @@ export interface OverridableFrontendPlugin< export const PageBlueprint: ExtensionBlueprint_2<{ kind: 'page'; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; @@ -1814,13 +1803,13 @@ export interface PageLayoutProps { // (undocumented) noHeader?: boolean; // (undocumented) - tabs?: PageTab[]; + tabs?: PageLayoutTab[]; // (undocumented) title?: string; } // @public -export interface PageTab { +export interface PageLayoutTab { // (undocumented) href: string; // (undocumented) @@ -1831,6 +1820,9 @@ export interface PageTab { label: string; } +// @public @deprecated (undocumented) +export type PageTab = PageLayoutTab; + // @public export type PendingOAuthRequest = { provider: AuthProviderInfo; @@ -2313,7 +2305,7 @@ export const vmwareCloudAuthApiRef: ApiRef< SessionApi >; -// @public +// @public @deprecated export function withApis( apis: TypesToApiRefs, ): ( diff --git a/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts b/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts index 85151b05f6..d7ab2e5d75 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/DialogApi.ts @@ -42,7 +42,7 @@ export interface DialogApiDialog { */ update( elementOrComponent: - | React.JSX.Element + | JSX.Element | ((props: { dialog: DialogApiDialog }) => JSX.Element), ): void; diff --git a/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx b/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx index 4b9d80fb62..7596105810 100644 --- a/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx +++ b/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx @@ -17,7 +17,30 @@ import { renderHook } from '@testing-library/react'; import { createVersionedContextForTesting } from '@backstage/version-bridge'; import { createApiRef } from './ApiRef'; -import { useApi } from './useApi'; +import { useApi, useApiHolder } from './useApi'; + +describe('useApiHolder', () => { + const context = createVersionedContextForTesting('api-context'); + + afterEach(() => { + context.reset(); + }); + + it('should return the API holder from context', () => { + const holder = { get: jest.fn() }; + context.set({ 1: holder }); + + const renderedHook = renderHook(() => useApiHolder()); + expect(renderedHook.result.current).toBe(holder); + }); + + it('should return an empty API holder when there is no context', () => { + const renderedHook = renderHook(() => useApiHolder()); + + const holder = renderedHook.result.current; + expect(holder.get(createApiRef({ id: 'x' }))).toBeUndefined(); + }); +}); describe('useApi', () => { const context = createVersionedContextForTesting('api-context'); diff --git a/packages/frontend-plugin-api/src/apis/system/useApi.tsx b/packages/frontend-plugin-api/src/apis/system/useApi.tsx index 30e39033f7..2808f13f1f 100644 --- a/packages/frontend-plugin-api/src/apis/system/useApi.tsx +++ b/packages/frontend-plugin-api/src/apis/system/useApi.tsx @@ -19,6 +19,8 @@ import { ApiRef, ApiHolder, TypesToApiRefs } from './types'; import { useVersionedContext } from '@backstage/version-bridge'; import { NotImplementedError } from '@backstage/errors'; +const emptyApiHolder: ApiHolder = Object.freeze({ get: () => undefined }); + /** * React hook for retrieving {@link ApiHolder}, an API catalog. * @@ -27,7 +29,7 @@ import { NotImplementedError } from '@backstage/errors'; export function useApiHolder(): ApiHolder { const versionedHolder = useVersionedContext<{ 1: ApiHolder }>('api-context'); if (!versionedHolder) { - throw new NotImplementedError('API context is not available'); + return emptyApiHolder; } const apiHolder = versionedHolder.atVersion(1); @@ -57,6 +59,7 @@ export function useApi(apiRef: ApiRef): T { * Wrapper for giving component an API context. * * @param apis - APIs for the context. + * @deprecated Use `withApis` from `@backstage/core-compat-api` instead. * @public */ export function withApis(apis: TypesToApiRefs) { diff --git a/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts index 23b5982738..e9961c3d48 100644 --- a/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/AnalyticsImplementationBlueprint.ts @@ -21,7 +21,10 @@ import { createExtensionDataRef, } from '../wiring'; -/** @public */ +/** + * @public + * @deprecated Use `AnalyticsImplementationFactory` from `@backstage/plugin-app-react` instead. + */ export type AnalyticsImplementationFactory< Deps extends { [name in string]: unknown } = {}, > = { @@ -38,6 +41,7 @@ const factoryDataRef = * Creates analytics implementations. * * @public + * @deprecated Use `AnalyticsImplementationBlueprint` from `@backstage/plugin-app-react` instead. */ export const AnalyticsImplementationBlueprint = createExtensionBlueprint({ kind: 'analytics', diff --git a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts index ce59a9439b..057bb7ca54 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.ts @@ -29,6 +29,10 @@ const targetDataRef = createExtensionDataRef<{ * Creates extensions that make up the items of the nav bar. * * @public + * @deprecated Nav items are now automatically inferred from `PageBlueprint` + * extensions based on their `title` and `icon` params. You can remove your + * `NavItemBlueprint` usage and instead pass `title` and `icon` directly to + * the `PageBlueprint`. */ export const NavItemBlueprint = createExtensionBlueprint({ kind: 'nav-item', diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx index e439d177e2..513da27faa 100644 --- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.tsx @@ -23,7 +23,7 @@ import { createExtensionBlueprint, createExtensionInput, } from '../wiring'; -import { ExtensionBoundary, PageLayout, PageTab } from '../components'; +import { ExtensionBoundary, PageLayout, PageLayoutTab } from '../components'; import { useApi } from '../apis/system'; import { pluginHeaderActionsApiRef } from '../apis/definitions/PluginHeaderActionsApi'; @@ -59,10 +59,6 @@ export const PageBlueprint = createExtensionBlueprint({ }, *factory( params: { - /** - * @deprecated Use the `path` param instead. - */ - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; @@ -101,7 +97,7 @@ export const PageBlueprint = createExtensionBlueprint({ yield coreExtensionData.reactElement(); } else if (inputs.pages.length > 0) { // Parent page with sub-pages - render header with tabs - const tabs: PageTab[] = inputs.pages.map(page => { + const tabs: PageLayoutTab[] = inputs.pages.map(page => { const path = page.get(coreExtensionData.routePath); const tabTitle = page.get(coreExtensionData.title); const tabIcon = page.get(coreExtensionData.icon); diff --git a/packages/frontend-plugin-api/src/components/PageLayout.tsx b/packages/frontend-plugin-api/src/components/PageLayout.tsx index 0eadc07ffd..d97d746665 100644 --- a/packages/frontend-plugin-api/src/components/PageLayout.tsx +++ b/packages/frontend-plugin-api/src/components/PageLayout.tsx @@ -22,13 +22,19 @@ import { createSwappableComponent } from './createSwappableComponent'; * Tab configuration for page navigation * @public */ -export interface PageTab { +export interface PageLayoutTab { id: string; label: string; icon?: IconElement; href: string; } +/** + * @deprecated Use {@link PageLayoutTab} instead + * @public + */ +export type PageTab = PageLayoutTab; + /** * Props for the PageLayout component * @public @@ -38,7 +44,7 @@ export interface PageLayoutProps { icon?: IconElement; noHeader?: boolean; headerActions?: Array; - tabs?: PageTab[]; + tabs?: PageLayoutTab[]; children?: ReactNode; } diff --git a/packages/frontend-plugin-api/src/components/index.ts b/packages/frontend-plugin-api/src/components/index.ts index e04cbb3c65..7cb109932a 100644 --- a/packages/frontend-plugin-api/src/components/index.ts +++ b/packages/frontend-plugin-api/src/components/index.ts @@ -25,4 +25,9 @@ export { } from './createSwappableComponent'; export { useAppNode } from './AppNodeProvider'; export * from './DefaultSwappableComponents'; -export { PageLayout, type PageLayoutProps, type PageTab } from './PageLayout'; +export { + PageLayout, + type PageLayoutProps, + type PageLayoutTab, + type PageTab, +} from './PageLayout'; diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts index 1bfbb01740..27b12deb84 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts @@ -34,17 +34,11 @@ export type ExtensionDataRef< }; /** @public */ -export type ExtensionDataRefToValue = +export type ExtensionDataRefToValue = TDataRef extends ExtensionDataRef ? ExtensionDataValue : never; -/** - * @deprecated Use `ExtensionDataRef` without type parameters instead. - * @public - */ -export type AnyExtensionDataRef = ExtensionDataRef; - /** @public */ export interface ConfigurableExtensionDataRef< TData, diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index 6ce1bb4998..044bf03b67 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -31,7 +31,6 @@ export { } from './createExtensionInput'; export { createExtensionDataRef, - type AnyExtensionDataRef, type ExtensionDataRef, type ExtensionDataRefToValue, type ExtensionDataValue, @@ -58,7 +57,6 @@ export { export { type Extension, type ExtensionAttachTo, - type ExtensionAttachToSpec, } from './resolveExtensionDefinition'; export { type ExtensionDataContainer, diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts index 605e75bfe8..70a0bd7837 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts @@ -30,21 +30,13 @@ import { } from '@internal/frontend'; /** @public */ -export type ExtensionAttachTo = - | { id: string; input: string } - | Array<{ id: string; input: string }>; - -/** - * @deprecated Use {@link ExtensionAttachTo} instead. - * @public - */ -export type ExtensionAttachToSpec = ExtensionAttachTo; +export type ExtensionAttachTo = { id: string; input: string }; /** @public */ export interface Extension { $$type: '@backstage/Extension'; readonly id: string; - readonly attachTo: ExtensionAttachToSpec; + readonly attachTo: ExtensionAttachTo; readonly disabled: boolean; readonly configSchema?: PortableSchema; } @@ -151,7 +143,7 @@ function resolveExtensionId( function resolveAttachTo( attachTo: ExtensionDefinitionAttachTo | ExtensionDefinitionAttachTo[], namespace?: string, -): ExtensionAttachToSpec { +): ExtensionAttachTo | ExtensionAttachTo[] { const resolveSpec = ( spec: ExtensionDefinitionAttachTo, ): { id: string; input: string } => { @@ -213,7 +205,7 @@ export function resolveExtensionDefinition< return { ...rest, - attachTo: resolveAttachTo(attachTo, namespace), + attachTo: resolveAttachTo(attachTo, namespace) as ExtensionAttachTo, $$type: '@backstage/Extension', version: internalDefinition.version, id, diff --git a/packages/frontend-plugin-api/src/wiring/types.ts b/packages/frontend-plugin-api/src/wiring/types.ts index 0b59e7a7fb..39f0c5b03c 100644 --- a/packages/frontend-plugin-api/src/wiring/types.ts +++ b/packages/frontend-plugin-api/src/wiring/types.ts @@ -29,6 +29,8 @@ import { FrontendPlugin } from './createFrontendPlugin'; export type FeatureFlagConfig = { /** Feature flag name */ name: string; + /** Feature flag description */ + description?: string; }; /** @public */ @@ -60,7 +62,10 @@ export type ExtensionDataContainer = : never; }; -/** @public */ +/** + * @public + * @deprecated Moved to {@link @backstage/frontend-app-api#ExtensionFactoryMiddleware} + */ export type ExtensionFactoryMiddleware = ( originalFactory: (contextOverrides?: { config?: JsonObject; diff --git a/packages/frontend-test-utils/report.api.md b/packages/frontend-test-utils/report.api.md index a17c2ed483..6d9f8964b1 100644 --- a/packages/frontend-test-utils/report.api.md +++ b/packages/frontend-test-utils/report.api.md @@ -86,7 +86,7 @@ export function createExtensionTester< }, ): ExtensionTester>; -// @public +// @public @deprecated export type ErrorWithContext = { error: ErrorApiError; context?: ErrorApiErrorContext; @@ -143,7 +143,7 @@ export class ExtensionTester { snapshot(): ExtensionSnapshotNode; } -// @public +// @public @deprecated export class MockAlertApi implements AlertApi { // (undocumented) alert$(): Observable; @@ -157,7 +157,7 @@ export class MockAlertApi implements AlertApi { ): Promise; } -// @public +// @public @deprecated export class MockAnalyticsApi implements AnalyticsApi { // (undocumented) captureEvent(event: AnalyticsEvent): void; @@ -200,18 +200,18 @@ export namespace mockApis { partialImpl?: Partial | undefined, ) => ApiMock; } - export function error( - options?: MockErrorApiOptions, - ): MockErrorApi & MockWithApiFactory; + export function error(options?: { + collect?: boolean; + }): MockErrorApi & MockWithApiFactory; export namespace error { const // (undocumented) mock: ( partialImpl?: Partial | undefined, ) => ApiMock; } - export function featureFlags( - options?: MockFeatureFlagsApiOptions, - ): MockWithApiFactory; + export function featureFlags(options?: { + initialStates?: Record; + }): MockWithApiFactory; export namespace featureFlags { const mock: ( partialImpl?: Partial | undefined, @@ -272,7 +272,7 @@ export namespace mockApis { } } -// @public +// @public @deprecated export class MockConfigApi implements ConfigApi { constructor(input: { data: JsonObject }); get(key?: string): T; @@ -293,28 +293,37 @@ export class MockConfigApi implements ConfigApi { keys(): string[]; } -// @public +// @public @deprecated export class MockErrorApi implements ErrorApi { - constructor(options?: MockErrorApiOptions); + constructor(options?: { collect?: boolean }); // (undocumented) error$(): Observable<{ error: ErrorApiError; context?: ErrorApiErrorContext; }>; // (undocumented) - getErrors(): ErrorWithContext[]; + getErrors(): { + error: ErrorApiError; + context?: ErrorApiErrorContext; + }[]; // (undocumented) post(error: ErrorApiError, context?: ErrorApiErrorContext): void; // (undocumented) - waitForError(pattern: RegExp, timeoutMs?: number): Promise; + waitForError( + pattern: RegExp, + timeoutMs?: number, + ): Promise<{ + error: ErrorApiError; + context?: ErrorApiErrorContext; + }>; } -// @public +// @public @deprecated export type MockErrorApiOptions = { collect?: boolean; }; -// @public +// @public @deprecated export class MockFeatureFlagsApi implements FeatureFlagsApi { constructor(options?: MockFeatureFlagsApiOptions); clearState(): void; @@ -330,12 +339,12 @@ export class MockFeatureFlagsApi implements FeatureFlagsApi { setState(states: Record): void; } -// @public +// @public @deprecated export interface MockFeatureFlagsApiOptions { initialStates?: Record; } -// @public +// @public @deprecated export class MockFetchApi implements FetchApi { constructor(options?: MockFetchApiOptions); get fetch(): typeof fetch; @@ -359,7 +368,7 @@ export interface MockFetchApiOptions { }; } -// @public +// @public @deprecated export class MockPermissionApi implements PermissionApi { constructor( requestHandler?: ( @@ -372,7 +381,7 @@ export class MockPermissionApi implements PermissionApi { ): Promise; } -// @public +// @public @deprecated export class MockStorageApi implements StorageApi { // (undocumented) static create(data?: JsonObject): MockStorageApi; @@ -390,7 +399,7 @@ export class MockStorageApi implements StorageApi { snapshot(key: string): StorageValueSnapshot; } -// @public +// @public @deprecated export class MockTranslationApi implements TranslationApi { // (undocumented) static create(): MockTranslationApi; diff --git a/packages/frontend-test-utils/src/apis/AlertApi/MockAlertApi.ts b/packages/frontend-test-utils/src/apis/AlertApi/MockAlertApi.ts index 305a527b87..dcd39fdb78 100644 --- a/packages/frontend-test-utils/src/apis/AlertApi/MockAlertApi.ts +++ b/packages/frontend-test-utils/src/apis/AlertApi/MockAlertApi.ts @@ -23,9 +23,10 @@ import ObservableImpl from 'zen-observable'; * Mock implementation of {@link @backstage/frontend-plugin-api#AlertApi} for testing alert behavior. * * @public + * @deprecated Use `mockApis.alert()` instead. * @example * ```ts - * const alertApi = new MockAlertApi(); + * const alertApi = mockApis.alert(); * alertApi.post({ message: 'Test alert' }); * expect(alertApi.getAlerts()).toHaveLength(1); * ``` diff --git a/packages/frontend-test-utils/src/apis/AnalyticsApi/MockAnalyticsApi.ts b/packages/frontend-test-utils/src/apis/AnalyticsApi/MockAnalyticsApi.ts index f6ccf4e9d1..24117d980f 100644 --- a/packages/frontend-test-utils/src/apis/AnalyticsApi/MockAnalyticsApi.ts +++ b/packages/frontend-test-utils/src/apis/AnalyticsApi/MockAnalyticsApi.ts @@ -21,6 +21,7 @@ import { AnalyticsApi, AnalyticsEvent } from '@backstage/frontend-plugin-api'; * Use getEvents in tests to verify captured events. * * @public + * @deprecated Use `mockApis.analytics()` instead. */ export class MockAnalyticsApi implements AnalyticsApi { private events: AnalyticsEvent[] = []; diff --git a/packages/frontend-test-utils/src/apis/ConfigApi/MockConfigApi.ts b/packages/frontend-test-utils/src/apis/ConfigApi/MockConfigApi.ts index 349eee0d3a..ea445fac19 100644 --- a/packages/frontend-test-utils/src/apis/ConfigApi/MockConfigApi.ts +++ b/packages/frontend-test-utils/src/apis/ConfigApi/MockConfigApi.ts @@ -23,9 +23,10 @@ import { ConfigApi } from '@backstage/core-plugin-api'; * that can be used to mock configuration using a plain object. * * @public + * @deprecated Use `mockApis.config()` instead. * @example * ```tsx - * const mockConfig = new MockConfigApi({ + * const mockConfig = mockApis.config({ * data: { app: { baseUrl: 'https://example.com' } }, * }); * diff --git a/packages/frontend-test-utils/src/apis/ErrorApi/MockErrorApi.ts b/packages/frontend-test-utils/src/apis/ErrorApi/MockErrorApi.ts index c7c8556355..0b627d59e4 100644 --- a/packages/frontend-test-utils/src/apis/ErrorApi/MockErrorApi.ts +++ b/packages/frontend-test-utils/src/apis/ErrorApi/MockErrorApi.ts @@ -24,15 +24,16 @@ import { Observable } from '@backstage/types'; /** * Constructor arguments for {@link MockErrorApi} * @public + * @deprecated Use `mockApis.error()` instead. */ export type MockErrorApiOptions = { - // Need to be true if getErrors is used in testing. collect?: boolean; }; /** * ErrorWithContext contains error and ErrorApiErrorContext * @public + * @deprecated Use the return type of `MockErrorApi.getErrors` instead. */ export type ErrorWithContext = { error: ErrorApiError; @@ -41,7 +42,10 @@ export type ErrorWithContext = { type Waiter = { pattern: RegExp; - resolve: (err: ErrorWithContext) => void; + resolve: (err: { + error: ErrorApiError; + context?: ErrorApiErrorContext; + }) => void; }; const nullObservable = { @@ -56,12 +60,16 @@ const nullObservable = { * Mock implementation of the {@link core-plugin-api#ErrorApi} to be used in tests. * Includes withForError and getErrors methods for error testing. * @public + * @deprecated Use `mockApis.error()` instead. */ export class MockErrorApi implements ErrorApi { - private readonly errors = new Array(); + private readonly errors = new Array<{ + error: ErrorApiError; + context?: ErrorApiErrorContext; + }>(); private readonly waiters = new Set(); - constructor(private readonly options: MockErrorApiOptions = {}) {} + constructor(private readonly options: { collect?: boolean } = {}) {} post(error: ErrorApiError, context?: ErrorApiErrorContext) { if (this.options.collect) { @@ -87,15 +95,18 @@ export class MockErrorApi implements ErrorApi { return nullObservable; } - getErrors(): ErrorWithContext[] { + getErrors(): { error: ErrorApiError; context?: ErrorApiErrorContext }[] { return this.errors; } waitForError( pattern: RegExp, timeoutMs: number = 2000, - ): Promise { - return new Promise((resolve, reject) => { + ): Promise<{ error: ErrorApiError; context?: ErrorApiErrorContext }> { + return new Promise<{ + error: ErrorApiError; + context?: ErrorApiErrorContext; + }>((resolve, reject) => { setTimeout(() => { reject(new Error('Timed out waiting for error')); }, timeoutMs); diff --git a/packages/frontend-test-utils/src/apis/FeatureFlagsApi/MockFeatureFlagsApi.ts b/packages/frontend-test-utils/src/apis/FeatureFlagsApi/MockFeatureFlagsApi.ts index 5421687c65..591f6a2e95 100644 --- a/packages/frontend-test-utils/src/apis/FeatureFlagsApi/MockFeatureFlagsApi.ts +++ b/packages/frontend-test-utils/src/apis/FeatureFlagsApi/MockFeatureFlagsApi.ts @@ -25,6 +25,7 @@ import { * Options for configuring {@link MockFeatureFlagsApi}. * * @public + * @deprecated Use `mockApis.featureFlags()` instead. */ export interface MockFeatureFlagsApiOptions { /** @@ -37,10 +38,11 @@ export interface MockFeatureFlagsApiOptions { * Mock implementation of {@link @backstage/frontend-plugin-api#FeatureFlagsApi} for testing feature flag behavior. * * @public + * @deprecated Use `mockApis.featureFlags()` instead. * @example * ```ts - * const api = new MockFeatureFlagsApi({ - * initialStates: { 'my-feature': FeatureFlagState.Active } + * const api = mockApis.featureFlags({ + * initialStates: { 'my-feature': FeatureFlagState.Active }, * }); * expect(api.isActive('my-feature')).toBe(true); * ``` diff --git a/packages/frontend-test-utils/src/apis/FetchApi/MockFetchApi.ts b/packages/frontend-test-utils/src/apis/FetchApi/MockFetchApi.ts index b61ca680d1..0ea06924f0 100644 --- a/packages/frontend-test-utils/src/apis/FetchApi/MockFetchApi.ts +++ b/packages/frontend-test-utils/src/apis/FetchApi/MockFetchApi.ts @@ -89,6 +89,7 @@ export interface MockFetchApiOptions { * A test helper implementation of {@link @backstage/core-plugin-api#FetchApi}. * * @public + * @deprecated Use `mockApis.fetch()` instead. */ export class MockFetchApi implements FetchApi { private readonly implementation: FetchApi; diff --git a/packages/frontend-test-utils/src/apis/PermissionApi/MockPermissionApi.ts b/packages/frontend-test-utils/src/apis/PermissionApi/MockPermissionApi.ts index c54a3993d3..52f8fa4bb6 100644 --- a/packages/frontend-test-utils/src/apis/PermissionApi/MockPermissionApi.ts +++ b/packages/frontend-test-utils/src/apis/PermissionApi/MockPermissionApi.ts @@ -28,6 +28,7 @@ import { * request. * * @public + * @deprecated Use `mockApis.permission()` instead. */ export class MockPermissionApi implements PermissionApi { constructor( diff --git a/packages/frontend-test-utils/src/apis/StorageApi/MockStorageApi.ts b/packages/frontend-test-utils/src/apis/StorageApi/MockStorageApi.ts index b4cf762599..f7ad0fe39a 100644 --- a/packages/frontend-test-utils/src/apis/StorageApi/MockStorageApi.ts +++ b/packages/frontend-test-utils/src/apis/StorageApi/MockStorageApi.ts @@ -19,9 +19,10 @@ import { JsonObject, JsonValue, Observable } from '@backstage/types'; import ObservableImpl from 'zen-observable'; /** - * Mock implementation of the {@link core-plugin-api#StorageApi} to be used in tests + * Mock implementation of the {@link core-plugin-api#StorageApi} to be used in tests. * * @public + * @deprecated Use `mockApis.storage()` instead. */ export class MockStorageApi implements StorageApi { private readonly namespace: string; diff --git a/packages/frontend-test-utils/src/apis/TranslationApi/MockTranslationApi.ts b/packages/frontend-test-utils/src/apis/TranslationApi/MockTranslationApi.ts index f5112ca175..91708b0528 100644 --- a/packages/frontend-test-utils/src/apis/TranslationApi/MockTranslationApi.ts +++ b/packages/frontend-test-utils/src/apis/TranslationApi/MockTranslationApi.ts @@ -35,6 +35,7 @@ const DEFAULT_LANGUAGE = 'en'; * Mock implementation of {@link @backstage/frontend-plugin-api#TranslationApi}. * * @public + * @deprecated Use `mockApis.translation()` instead. */ export class MockTranslationApi implements TranslationApi { static create() { diff --git a/packages/frontend-test-utils/src/apis/index.ts b/packages/frontend-test-utils/src/apis/index.ts index 6e09833298..cee5c47635 100644 --- a/packages/frontend-test-utils/src/apis/index.ts +++ b/packages/frontend-test-utils/src/apis/index.ts @@ -28,59 +28,79 @@ export { type TestApiPairs, } from './TestApiProvider'; -/** - * Mock API classes are exported as types only to prevent direct instantiation. - * Always use the `mockApis` namespace to create mock instances (e.g., `mockApis.alert()`). - */ - /** * @public + * @deprecated Use `mockApis.alert()` instead. */ export type { MockAlertApi } from './AlertApi'; /** * @public + * @deprecated Use `mockApis.analytics()` instead. */ export type { MockAnalyticsApi } from './AnalyticsApi'; /** * @public + * @deprecated Use `mockApis.config()` instead. */ export type { MockConfigApi } from './ConfigApi'; /** * @public + * @deprecated Use `mockApis.error()` instead. */ -export type { - MockErrorApi, - MockErrorApiOptions, - ErrorWithContext, -} from './ErrorApi'; +export type { MockErrorApi } from './ErrorApi'; + +/** + * @public + * @deprecated Use `mockApis.error()` instead. + */ +export type { MockErrorApiOptions } from './ErrorApi/MockErrorApi'; + +/** + * @public + * @deprecated Use the return type of `MockErrorApi.getErrors` instead. + */ +export type { ErrorWithContext } from './ErrorApi/MockErrorApi'; + +/** + * @public + * @deprecated Use `mockApis.fetch()` instead. + */ +export type { MockFetchApi } from './FetchApi'; /** * @public */ -export type { MockFetchApi, MockFetchApiOptions } from './FetchApi'; +export type { MockFetchApiOptions } from './FetchApi/MockFetchApi'; /** * @public + * @deprecated Use `mockApis.featureFlags()` instead. */ -export type { - MockFeatureFlagsApi, - MockFeatureFlagsApiOptions, -} from './FeatureFlagsApi'; +export type { MockFeatureFlagsApi } from './FeatureFlagsApi'; /** * @public + * @deprecated Use `mockApis.featureFlags()` instead. + */ +export type { MockFeatureFlagsApiOptions } from './FeatureFlagsApi/MockFeatureFlagsApi'; + +/** + * @public + * @deprecated Use `mockApis.permission()` instead. */ export type { MockPermissionApi } from './PermissionApi'; /** * @public + * @deprecated Use `mockApis.storage()` instead. */ export type { MockStorageApi } from './StorageApi'; /** * @public + * @deprecated Use `mockApis.translation()` instead. */ export type { MockTranslationApi } from './TranslationApi'; diff --git a/packages/frontend-test-utils/src/apis/mockApis.ts b/packages/frontend-test-utils/src/apis/mockApis.ts index 78d5c2bf19..96932318bb 100644 --- a/packages/frontend-test-utils/src/apis/mockApis.ts +++ b/packages/frontend-test-utils/src/apis/mockApis.ts @@ -30,6 +30,7 @@ import { type DiscoveryApi, type ErrorApi, type FetchApi, + type FeatureFlagState, type IdentityApi, type StorageApi, type TranslationApi, @@ -44,13 +45,10 @@ import { EvaluatePermissionRequest, } from '@backstage/plugin-permission-common'; import { MockAlertApi } from './AlertApi'; -import { - MockFeatureFlagsApi, - MockFeatureFlagsApiOptions, -} from './FeatureFlagsApi'; +import { MockFeatureFlagsApi } from './FeatureFlagsApi'; import { MockAnalyticsApi } from './AnalyticsApi'; import { MockConfigApi } from './ConfigApi'; -import { MockErrorApi, MockErrorApiOptions } from './ErrorApi'; +import { MockErrorApi } from './ErrorApi'; import { MockFetchApi, MockFetchApiOptions } from './FetchApi'; import { MockStorageApi } from './StorageApi'; import { MockPermissionApi } from './PermissionApi'; @@ -114,7 +112,6 @@ export namespace mockApis { /** * Mock helpers for {@link @backstage/frontend-plugin-api#AlertApi}. * - * @see {@link @backstage/frontend-plugin-api#mockApis.alert} * @public */ export namespace alert { @@ -145,9 +142,9 @@ export namespace mockApis { * expect(featureFlagsApi.isActive('my-feature')).toBe(true); * ``` */ - export function featureFlags( - options?: MockFeatureFlagsApiOptions, - ): MockWithApiFactory { + export function featureFlags(options?: { + initialStates?: Record; + }): MockWithApiFactory { const instance = new MockFeatureFlagsApi(options); return mockWithApiFactory( featureFlagsApiRef, @@ -157,7 +154,6 @@ export namespace mockApis { /** * Mock helpers for {@link @backstage/frontend-plugin-api#FeatureFlagsApi}. * - * @see {@link @backstage/frontend-plugin-api#mockApis.featureFlags} * @public */ export namespace featureFlags { @@ -218,7 +214,6 @@ export namespace mockApis { /** * Mock helpers for {@link @backstage/frontend-plugin-api#TranslationApi}. * - * @see {@link @backstage/frontend-plugin-api#mockApis.translation} * @public */ export namespace translation { @@ -419,9 +414,9 @@ export namespace mockApis { * * @public */ - export function error( - options?: MockErrorApiOptions, - ): MockErrorApi & MockWithApiFactory { + export function error(options?: { + collect?: boolean; + }): MockErrorApi & MockWithApiFactory { const instance = new MockErrorApi(options); return mockWithApiFactory(errorApiRef, instance) as MockErrorApi & MockWithApiFactory; diff --git a/packages/repo-tools/src/commands/package-docs/Cache.test.ts b/packages/repo-tools/src/commands/package-docs/Cache.test.ts index d0293c09b0..02c1f5f427 100644 --- a/packages/repo-tools/src/commands/package-docs/Cache.test.ts +++ b/packages/repo-tools/src/commands/package-docs/Cache.test.ts @@ -24,11 +24,13 @@ import { readFile } from 'node:fs/promises'; import { join as joinPath } from 'node:path'; jest.mock('crypto', () => { + const actual = jest.requireActual('crypto'); const hash = { update: jest.fn(), digest: jest.fn().mockReturnValue('test'), }; return { + ...actual, createHash: jest.fn().mockReturnValue(hash), }; }); diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 8dd55be56c..0c9559e74a 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -20,6 +20,7 @@ import type { DisclosureProps } from 'react-aria-components'; import type { ElementType } from 'react'; import { ForwardRefExoticComponent } from 'react'; import type { HeadingProps } from 'react-aria-components'; +import type { HTMLAttributes } from 'react'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import type { LinkProps as LinkProps_2 } from 'react-aria-components'; import type { ListBoxItemProps } from 'react-aria-components'; @@ -255,6 +256,23 @@ export interface AlertProps // @public (undocumented) export type AlignItems = 'stretch' | 'start' | 'center' | 'end'; +// @public +export type AnalyticsEventAttributes = { + [key: string]: string | boolean | number; +}; + +// @public +export type AnalyticsTracker = { + captureEvent: ( + action: string, + subject: string, + options?: { + value?: number; + attributes?: AnalyticsEventAttributes; + }, + ) => void; +}; + // @public (undocumented) export const Avatar: ForwardRefExoticComponent< AvatarProps & RefAttributes @@ -413,6 +431,15 @@ export type BoxUtilityProps = { // @public (undocumented) export type Breakpoint = 'initial' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'; +// @public +export function BUIProvider(props: BUIProviderProps): JSX_2.Element; + +// @public (undocumented) +export type BUIProviderProps = { + useAnalytics?: UseAnalyticsFn; + children: ReactNode; +}; + // @public export const Button: ForwardRefExoticComponent< ButtonProps & RefAttributes @@ -513,7 +540,9 @@ export const ButtonLinkDefinition: { readonly content: 'bui-ButtonLinkContent'; }; readonly bg: 'consumer'; + readonly analytics: true; readonly propDefs: { + readonly noTrack: {}; readonly size: { readonly dataAttribute: true; readonly default: 'small'; @@ -531,6 +560,7 @@ export const ButtonLinkDefinition: { // @public (undocumented) export type ButtonLinkOwnProps = { + noTrack?: boolean; size?: Responsive<'small' | 'medium'>; variant?: Responsive<'primary' | 'secondary' | 'tertiary'>; iconStart?: ReactElement; @@ -619,7 +649,7 @@ export const CardDefinition: { }; readonly classNames: { readonly root: 'bui-Card'; - readonly overlay: 'bui-CardOverlay'; + readonly trigger: 'bui-CardTrigger'; }; readonly propDefs: { readonly children: {}; @@ -718,7 +748,7 @@ export type CardOwnProps = Pick< // @public export type CardProps = CardBaseProps & - Omit, 'onPress'> & + Omit, 'onClick'> & (CardButtonVariant | CardLinkVariant | CardStaticVariant); // @public (undocumented) @@ -1215,7 +1245,10 @@ export type FlexOwnProps = { }; // @public (undocumented) -export interface FlexProps extends SpaceProps, FlexOwnProps { +export interface FlexProps + extends SpaceProps, + FlexOwnProps, + Omit, 'children'> { // (undocumented) align?: Responsive<'start' | 'center' | 'end' | 'baseline' | 'stretch'>; // (undocumented) @@ -1257,6 +1290,11 @@ export interface FullPageProps extends Omit, 'className'>, FullPageOwnProps {} +// @public +export function getNodeText( + node: ReactNode | ((...args: any[]) => ReactNode), +): string | undefined; + // @public (undocumented) export const Grid: { Root: ForwardRefExoticComponent>; @@ -1331,7 +1369,9 @@ export type GridItemOwnProps = { }; // @public (undocumented) -export interface GridItemProps extends GridItemOwnProps { +export interface GridItemProps + extends GridItemOwnProps, + Omit, 'children'> { // (undocumented) colEnd?: Responsive; // (undocumented) @@ -1351,7 +1391,10 @@ export type GridOwnProps = { }; // @public (undocumented) -export interface GridProps extends SpaceProps, GridOwnProps { +export interface GridProps + extends SpaceProps, + GridOwnProps, + Omit, 'children'> { // (undocumented) columns?: Responsive; // (undocumented) @@ -1359,10 +1402,10 @@ export interface GridProps extends SpaceProps, GridOwnProps { } // @public -export const HeaderPage: (props: HeaderPageProps) => JSX_2.Element; +export const Header: (props: HeaderProps) => JSX_2.Element; // @public -export interface HeaderPageBreadcrumb { +export interface HeaderBreadcrumb { // (undocumented) href: string; // (undocumented) @@ -1370,7 +1413,7 @@ export interface HeaderPageBreadcrumb { } // @public -export const HeaderPageDefinition: { +export const HeaderDefinition: { readonly styles: { readonly [key: string]: string; }; @@ -1391,9 +1434,9 @@ export const HeaderPageDefinition: { }; // @public -export interface HeaderPageOwnProps { +export interface HeaderOwnProps { // (undocumented) - breadcrumbs?: HeaderPageBreadcrumb[]; + breadcrumbs?: HeaderBreadcrumb[]; // (undocumented) className?: string; // (undocumented) @@ -1404,8 +1447,41 @@ export interface HeaderPageOwnProps { title?: string; } +// @public @deprecated (undocumented) +export const HeaderPage: (props: HeaderProps) => JSX_2.Element; + +// @public @deprecated (undocumented) +export type HeaderPageBreadcrumb = HeaderBreadcrumb; + +// @public @deprecated (undocumented) +export const HeaderPageDefinition: { + readonly styles: { + readonly [key: string]: string; + }; + readonly classNames: { + readonly root: 'bui-HeaderPage'; + readonly content: 'bui-HeaderPageContent'; + readonly breadcrumbs: 'bui-HeaderPageBreadcrumbs'; + readonly tabsWrapper: 'bui-HeaderPageTabsWrapper'; + readonly controls: 'bui-HeaderPageControls'; + }; + readonly propDefs: { + readonly title: {}; + readonly customActions: {}; + readonly tabs: {}; + readonly breadcrumbs: {}; + readonly className: {}; + }; +}; + +// @public @deprecated (undocumented) +export type HeaderPageOwnProps = HeaderOwnProps; + +// @public @deprecated (undocumented) +export type HeaderPageProps = HeaderProps; + // @public -export interface HeaderPageProps extends HeaderPageOwnProps {} +export interface HeaderProps extends HeaderOwnProps {} // @public export interface HeaderTab { @@ -1440,7 +1516,9 @@ export const LinkDefinition: { readonly classNames: { readonly root: 'bui-Link'; }; + readonly analytics: true; readonly propDefs: { + readonly noTrack: {}; readonly variant: { readonly dataAttribute: true; readonly default: 'body-medium'; @@ -1467,6 +1545,7 @@ export const LinkDefinition: { // @public (undocumented) export type LinkOwnProps = { + noTrack?: boolean; variant?: TextVariants | Partial>; weight?: TextWeights | Partial>; color?: @@ -1570,6 +1649,7 @@ export type MenuItemOwnProps = { children: React.ReactNode; color?: 'primary' | 'danger'; href?: MenuItemProps_2['href']; + noTrack?: boolean; className?: string; }; @@ -1989,6 +2069,7 @@ export type RowOwnProps = { columns?: RowProps_2['columns']; children?: RowProps_2['children']; href?: string; + noTrack?: boolean; }; // @public (undocumented) @@ -2466,6 +2547,7 @@ export type TabOwnProps = { matchStrategy?: TabMatchStrategy; href?: TabProps_2['href']; id?: TabProps_2['id']; + noTrack?: boolean; }; // @public @@ -2561,6 +2643,7 @@ export type TagOwnProps = { href?: TagProps_2['href']; children?: TagProps_2['children']; className?: string; + noTrack?: boolean; }; // @public @@ -2818,6 +2901,12 @@ export const TooltipTrigger: ( props: TooltipTriggerComponentProps, ) => JSX_2.Element; +// @public +export function useAnalytics(): AnalyticsTracker; + +// @public +export type UseAnalyticsFn = () => AnalyticsTracker; + // @public export function useBgConsumer(): BgContextValue; diff --git a/packages/ui/src/analytics/BUIProvider.tsx b/packages/ui/src/analytics/BUIProvider.tsx new file mode 100644 index 0000000000..3fcbfc7698 --- /dev/null +++ b/packages/ui/src/analytics/BUIProvider.tsx @@ -0,0 +1,57 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useMemo, type ReactNode } from 'react'; +import { createVersionedValueMap } from '@backstage/version-bridge'; +import { BUIContext } from './useAnalytics'; +import type { UseAnalyticsFn } from './types'; + +/** @public */ +export type BUIProviderProps = { + useAnalytics?: UseAnalyticsFn; + children: ReactNode; +}; + +/** + * Provides integration capabilities to all descendant BUI components. + * + * @example + * ```tsx + * import { BUIProvider } from '@backstage/ui'; + * import { useAnalytics as useBackstageAnalytics } from '@backstage/core-plugin-api'; + * + * function App() { + * return ( + * + * + * + * ); + * } + * ``` + * + * @public + */ +export function BUIProvider(props: BUIProviderProps) { + const { useAnalytics, children } = props; + const value = useMemo( + () => + createVersionedValueMap({ + 1: { useAnalytics }, + }), + [useAnalytics], + ); + return {children}; +} diff --git a/packages/ui/src/analytics/getNodeText.ts b/packages/ui/src/analytics/getNodeText.ts new file mode 100644 index 0000000000..a1a58c34b7 --- /dev/null +++ b/packages/ui/src/analytics/getNodeText.ts @@ -0,0 +1,43 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 ReactNode, isValidElement, Children } from 'react'; + +/** + * Recursively extracts text content from a React node tree. + * Returns undefined if no text content is found (e.g. icon-only children + * or render functions). + * + * @public + */ +export function getNodeText( + node: ReactNode | ((...args: any[]) => ReactNode), +): string | undefined { + if (typeof node === 'function') { + return undefined; + } + if (Array.isArray(node)) { + const text = Children.map(node, getNodeText)?.filter(Boolean).join(' '); + return text || undefined; + } + if (isValidElement(node)) { + return getNodeText(node.props.children); + } + if (typeof node === 'string' || typeof node === 'number') { + return String(node); + } + return undefined; +} diff --git a/packages/frontend-defaults/src/createPublicSignInApp.tsx b/packages/ui/src/analytics/index.ts similarity index 55% rename from packages/frontend-defaults/src/createPublicSignInApp.tsx rename to packages/ui/src/analytics/index.ts index 0015d9403f..a9b073afa7 100644 --- a/packages/frontend-defaults/src/createPublicSignInApp.tsx +++ b/packages/ui/src/analytics/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The 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,16 +14,12 @@ * limitations under the License. */ -import { appModulePublicSignIn } from '@backstage/plugin-app/alpha'; -import { CreateAppOptions, createApp } from './createApp'; - -/** - * @public - * @deprecated Use {@link @backstage/plugin-app/alpha#appModulePublicSignIn} instead. - */ -export function createPublicSignInApp(options?: CreateAppOptions) { - return createApp({ - ...options, - features: [...(options?.features ?? []), appModulePublicSignIn], - }); -} +export { useAnalytics } from './useAnalytics'; +export { BUIProvider } from './BUIProvider'; +export type { BUIProviderProps } from './BUIProvider'; +export { getNodeText } from './getNodeText'; +export type { + AnalyticsTracker, + AnalyticsEventAttributes, + UseAnalyticsFn, +} from './types'; diff --git a/packages/ui/src/analytics/types.ts b/packages/ui/src/analytics/types.ts new file mode 100644 index 0000000000..62b6eb6186 --- /dev/null +++ b/packages/ui/src/analytics/types.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Key-value metadata attached to analytics events. + * @public + */ +export type AnalyticsEventAttributes = { + [key: string]: string | boolean | number; +}; + +/** + * A generic interface for capturing analytics events. Consumers provide + * an implementation via `BUIProvider` — this allows `@backstage/ui` + * to fire analytics events without depending on any specific analytics + * system. The signature intentionally matches Backstage's own + * `AnalyticsTracker` so it can be wired through directly. + * @public + */ +export type AnalyticsTracker = { + captureEvent: ( + action: string, + subject: string, + options?: { + value?: number; + attributes?: AnalyticsEventAttributes; + }, + ) => void; +}; + +/** + * A hook function that returns an AnalyticsTracker. + * Provided via context by the consumer (e.g. a Backstage app). + * @public + */ +export type UseAnalyticsFn = () => AnalyticsTracker; diff --git a/packages/ui/src/analytics/useAnalytics.ts b/packages/ui/src/analytics/useAnalytics.ts new file mode 100644 index 0000000000..127b3aa4b6 --- /dev/null +++ b/packages/ui/src/analytics/useAnalytics.ts @@ -0,0 +1,68 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useRef } from 'react'; +import { + createVersionedContext, + useVersionedContext, +} from '@backstage/version-bridge'; +import type { AnalyticsTracker, UseAnalyticsFn } from './types'; + +/** @internal */ +export const noopTracker: AnalyticsTracker = { + captureEvent: () => {}, +}; + +const noopUseAnalytics: UseAnalyticsFn = () => noopTracker; + +/** @internal */ +export type BUIContextValue = { + useAnalytics?: UseAnalyticsFn; +}; + +/** @internal */ +export const BUIContext = createVersionedContext<{ + 1: BUIContextValue; +}>('bui'); + +/** + * Returns an AnalyticsTracker for capturing analytics events. + * + * By default returns a noop tracker. When a `BUIProvider` is present + * in the tree, returns the tracker provided by the consumer's hook. + * + * @public + */ +export function useAnalytics(): AnalyticsTracker { + const ctx = useVersionedContext<{ 1: BUIContextValue }>('bui')?.atVersion(1); + const impl = ctx?.useAnalytics ?? noopUseAnalytics; + + if (process.env.NODE_ENV !== 'production') { + const prevImpl = useRef(impl); + if ( + (prevImpl.current === noopUseAnalytics) !== + (impl === noopUseAnalytics) + ) { + throw new Error( + '@backstage/ui: The analytics hook changed between a noop and a real ' + + 'implementation. Ensure wraps all BUI components from first render.', + ); + } + prevImpl.current = impl; + } + + return impl(); +} diff --git a/packages/ui/src/components/ButtonLink/ButtonLink.tsx b/packages/ui/src/components/ButtonLink/ButtonLink.tsx index ac691897c4..96f477a4e8 100644 --- a/packages/ui/src/components/ButtonLink/ButtonLink.tsx +++ b/packages/ui/src/components/ButtonLink/ButtonLink.tsx @@ -20,16 +20,28 @@ import type { ButtonLinkProps } from './types'; import { useDefinition } from '../../hooks/useDefinition'; import { ButtonLinkDefinition } from './definition'; import { InternalLinkProvider } from '../InternalLinkProvider'; +import { getNodeText } from '../../analytics/getNodeText'; /** @public */ export const ButtonLink = forwardRef( (props: ButtonLinkProps, ref: Ref) => { - const { ownProps, restProps, dataAttributes } = useDefinition( + const { ownProps, restProps, dataAttributes, analytics } = useDefinition( ButtonLinkDefinition, props, ); const { classes, iconStart, iconEnd, children } = ownProps; + const handlePress: typeof restProps.onPress = e => { + restProps.onPress?.(e); + const text = + restProps['aria-label'] ?? + getNodeText(children) ?? + String(restProps.href ?? ''); + analytics.captureEvent('click', text, { + attributes: { to: String(restProps.href ?? '') }, + }); + }; + return ( {iconStart} diff --git a/packages/ui/src/components/ButtonLink/definition.ts b/packages/ui/src/components/ButtonLink/definition.ts index ea271a966e..a5f6a28489 100644 --- a/packages/ui/src/components/ButtonLink/definition.ts +++ b/packages/ui/src/components/ButtonLink/definition.ts @@ -29,7 +29,9 @@ export const ButtonLinkDefinition = defineComponent()({ content: 'bui-ButtonLinkContent', }, bg: 'consumer', + analytics: true, propDefs: { + noTrack: {}, size: { dataAttribute: true, default: 'small' }, variant: { dataAttribute: true, default: 'primary' }, iconStart: {}, diff --git a/packages/ui/src/components/ButtonLink/types.ts b/packages/ui/src/components/ButtonLink/types.ts index f2753eafe5..a4c5dbe65e 100644 --- a/packages/ui/src/components/ButtonLink/types.ts +++ b/packages/ui/src/components/ButtonLink/types.ts @@ -20,6 +20,7 @@ import type { Responsive } from '../../types'; /** @public */ export type ButtonLinkOwnProps = { + noTrack?: boolean; size?: Responsive<'small' | 'medium'>; variant?: Responsive<'primary' | 'secondary' | 'tertiary'>; iconStart?: ReactElement; diff --git a/packages/ui/src/components/Card/Card.module.css b/packages/ui/src/components/Card/Card.module.css index f0aaa6831f..231c6c55e0 100644 --- a/packages/ui/src/components/Card/Card.module.css +++ b/packages/ui/src/components/Card/Card.module.css @@ -47,11 +47,10 @@ /* * Cursor and hover tint are applied at the card level so they cover the - * entire surface. The overlay inherits the cursor via cursor: inherit. + * entire surface. */ .bui-Card[data-interactive] { cursor: pointer; - overflow: hidden; &::after { content: ''; @@ -70,72 +69,22 @@ } } - .bui-CardOverlay { + .bui-Card[data-interactive]:has(.bui-CardTrigger:focus-visible) { + outline: 2px solid var(--bui-ring); + outline-offset: -2px; + } + + .bui-CardTrigger { position: absolute; - inset: 0; - z-index: 1; - background: transparent; - border-radius: inherit; - border: none; + width: 1px; + height: 1px; padding: 0; - appearance: none; - display: block; - width: 100%; - cursor: inherit; - - &:focus-visible { - outline: 2px solid var(--bui-ring); - outline-offset: -2px; - } - - /* - * Keep focus tint for keyboard navigation (hover tint has moved to the - * card container above). - */ - &[data-focused]::after { - content: ''; - position: absolute; - inset: 0; - background: color-mix(in srgb, currentColor 5%, transparent); - border-radius: inherit; - pointer-events: none; - } - } - - /* - * Nested interactive elements must sit above the overlay (z-index: 1) so - * that buttons, links, and inputs remain independently clickable. - * CardBody is intentionally excluded: it sits beneath the overlay so that - * card-surface clicks route through the overlay natively (preserving link - * semantics such as target and rel). Scroll is not supported for interactive - * cards as a result. - */ - .bui-Card[data-interactive] - :is( - button, - a[href], - [role='button'], - [role='link'], - input, - select, - textarea, - .bui-Button - ):not(.bui-CardOverlay) { - position: relative; - z-index: 2; - } - - /* - * The bottom scroll-shadow pseudo-element uses a reversed scroll-driven - * animation whose fill-before state is opacity: 1. When the card has no - * height constraint (nothing to scroll), the animation is permanently stuck - * in that fill-before state, making the shadow visible even though there is - * no overflow. Interactive cards never scroll, so we suppress it entirely. - * The selector mirrors .bui-Card:has(.bui-CardFooter) .bui-CardBody::after - * with an added attribute to win the specificity race. - */ - .bui-Card[data-interactive]:has(.bui-CardFooter) .bui-CardBody::after { - display: none; + margin: -1px; + overflow: hidden; + clip: rect(0 0 0 0); + clip-path: inset(100%); + white-space: nowrap; + border: 0; } .bui-CardHeader { @@ -199,9 +148,11 @@ rgb(from var(--bui-card-bg) r g b / 0) ); pointer-events: none; - animation: bui-card-body-shadow linear both reverse; - animation-timeline: scroll(); - animation-range: calc(100% - 2.5rem) 100%; + opacity: 0; + animation: bui-card-body-shadow linear forwards, + bui-card-body-shadow linear forwards reverse; + animation-timeline: scroll(), scroll(); + animation-range: 0px 1px, calc(100% - 2.5rem) 100%; } } diff --git a/packages/ui/src/components/Card/Card.stories.tsx b/packages/ui/src/components/Card/Card.stories.tsx index 117859a1bb..bb756d312b 100644 --- a/packages/ui/src/components/Card/Card.stories.tsx +++ b/packages/ui/src/components/Card/Card.stories.tsx @@ -377,6 +377,26 @@ export const InteractiveWithNestedButtons = meta.story({ ), }); +export const InteractiveScrollable = meta.story({ + render: () => ( + alert('Card pressed')} + label="View card details" + > + + Scrollable Interactive Card + + {content} + + + Card body scrolls while card remains clickable + + + + ), +}); + export const CustomCardWithBox = meta.story({ render: () => ( diff --git a/packages/ui/src/components/Card/Card.tsx b/packages/ui/src/components/Card/Card.tsx index 2527a3a46b..70a2438a49 100644 --- a/packages/ui/src/components/Card/Card.tsx +++ b/packages/ui/src/components/Card/Card.tsx @@ -14,8 +14,8 @@ * limitations under the License. */ -import { forwardRef } from 'react'; -import { Button as RAButton, Link as RALink } from 'react-aria-components'; +import { forwardRef, useCallback, useRef } from 'react'; +import { Button as RAButton } from 'react-aria-components'; import { useDefinition } from '../../hooks/useDefinition'; import { CardDefinition, @@ -30,6 +30,10 @@ import type { CardFooterProps, } from './types'; import { Box } from '../Box/Box'; +import { Link } from '../Link'; + +const INTERACTIVE_ELEMENT_SELECTOR = + 'a[href],button,input,select,textarea,[role="button"],[role="link"],[tabindex]:not([tabindex="-1"])'; /** * Card component. @@ -41,24 +45,66 @@ export const Card = forwardRef((props, ref) => { CardDefinition, props, ); - const { classes, children, onPress, href, label, target, rel, download } = - ownProps; + const { + classes, + children, + onPress, + href, + label, + target: linkTarget, + rel, + download, + } = ownProps; const isInteractive = !!(onPress || href); + const triggerRef = useRef(null); + + const handleClick = useCallback( + (e: React.MouseEvent) => { + if (!isInteractive || !triggerRef.current) return; + + // Don't delegate if the click target is the trigger itself + if (triggerRef.current.contains(e.target as Node)) return; + + // Don't delegate if the user clicked a nested interactive element + const targetNode = e.target as Node | null; + const targetElement = + targetNode instanceof Element ? targetNode : targetNode?.parentElement; + if (targetElement?.closest(INTERACTIVE_ELEMENT_SELECTOR)) return; + + // Don't delegate if the user is selecting text + if (window.getSelection()?.toString()) return; + + triggerRef.current.dispatchEvent( + new MouseEvent('click', { + bubbles: false, + cancelable: true, + ctrlKey: e.ctrlKey, + metaKey: e.metaKey, + shiftKey: e.shiftKey, + }), + ); + }, + [isInteractive], + ); + return ( {href && ( - } + className={classes.trigger} href={href} - target={target} + target={linkTarget} rel={rel} download={download} aria-label={label} @@ -66,7 +112,8 @@ export const Card = forwardRef((props, ref) => { )} {onPress && !href && ( } + className={classes.trigger} onPress={onPress} aria-label={label} /> diff --git a/packages/ui/src/components/Card/definition.ts b/packages/ui/src/components/Card/definition.ts index 3408828a4d..5d69005b4c 100644 --- a/packages/ui/src/components/Card/definition.ts +++ b/packages/ui/src/components/Card/definition.ts @@ -31,7 +31,7 @@ export const CardDefinition = defineComponent()({ styles, classNames: { root: 'bui-Card', - overlay: 'bui-CardOverlay', + trigger: 'bui-CardTrigger', }, propDefs: { children: {}, diff --git a/packages/ui/src/components/Card/types.ts b/packages/ui/src/components/Card/types.ts index f1e91055e9..5809384c16 100644 --- a/packages/ui/src/components/Card/types.ts +++ b/packages/ui/src/components/Card/types.ts @@ -63,7 +63,7 @@ export type CardStaticVariant = { * @public */ export type CardProps = CardBaseProps & - Omit, 'onPress'> & + Omit, 'onClick'> & (CardButtonVariant | CardLinkVariant | CardStaticVariant); /** diff --git a/packages/ui/src/components/Flex/Flex.tsx b/packages/ui/src/components/Flex/Flex.tsx index 8700d52681..1e4e4fecb3 100644 --- a/packages/ui/src/components/Flex/Flex.tsx +++ b/packages/ui/src/components/Flex/Flex.tsx @@ -21,7 +21,7 @@ import { FlexDefinition } from './definition'; /** @public */ export const Flex = forwardRef((props, ref) => { - const { ownProps, dataAttributes, utilityStyle } = useDefinition( + const { ownProps, dataAttributes, utilityStyle, restProps } = useDefinition( FlexDefinition, { gap: '4', ...props }, ); @@ -33,6 +33,7 @@ export const Flex = forwardRef((props, ref) => { className={classes.root} style={{ ...utilityStyle, ...ownProps.style }} {...dataAttributes} + {...restProps} > {childrenWithBgProvider}
diff --git a/packages/ui/src/components/Flex/types.ts b/packages/ui/src/components/Flex/types.ts index 048d70affc..1c7f887e62 100644 --- a/packages/ui/src/components/Flex/types.ts +++ b/packages/ui/src/components/Flex/types.ts @@ -26,7 +26,10 @@ export type FlexOwnProps = { }; /** @public */ -export interface FlexProps extends SpaceProps, FlexOwnProps { +export interface FlexProps + extends SpaceProps, + FlexOwnProps, + Omit, 'children'> { gap?: Responsive; align?: Responsive<'start' | 'center' | 'end' | 'baseline' | 'stretch'>; justify?: Responsive<'start' | 'center' | 'end' | 'between'>; diff --git a/packages/ui/src/components/Grid/Grid.tsx b/packages/ui/src/components/Grid/Grid.tsx index 8d000c0925..f61cd67d6b 100644 --- a/packages/ui/src/components/Grid/Grid.tsx +++ b/packages/ui/src/components/Grid/Grid.tsx @@ -20,7 +20,7 @@ import { useDefinition } from '../../hooks/useDefinition'; import { GridDefinition, GridItemDefinition } from './definition'; const GridRoot = forwardRef((props, ref) => { - const { ownProps, dataAttributes, utilityStyle } = useDefinition( + const { ownProps, dataAttributes, utilityStyle, restProps } = useDefinition( GridDefinition, { columns: 'auto', gap: '4', ...props }, ); @@ -32,6 +32,7 @@ const GridRoot = forwardRef((props, ref) => { className={classes.root} style={{ ...utilityStyle, ...ownProps.style }} {...dataAttributes} + {...restProps} > {childrenWithBgProvider} @@ -39,7 +40,7 @@ const GridRoot = forwardRef((props, ref) => { }); const GridItem = forwardRef((props, ref) => { - const { ownProps, dataAttributes, utilityStyle } = useDefinition( + const { ownProps, dataAttributes, utilityStyle, restProps } = useDefinition( GridItemDefinition, props, ); @@ -51,6 +52,7 @@ const GridItem = forwardRef((props, ref) => { className={classes.root} style={{ ...utilityStyle, ...ownProps.style }} {...dataAttributes} + {...restProps} > {childrenWithBgProvider} diff --git a/packages/ui/src/components/Grid/types.ts b/packages/ui/src/components/Grid/types.ts index c5fb713d17..6ad5d564c6 100644 --- a/packages/ui/src/components/Grid/types.ts +++ b/packages/ui/src/components/Grid/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { ReactNode, CSSProperties } from 'react'; +import type { ReactNode, CSSProperties, HTMLAttributes } from 'react'; import type { Space, SpaceProps, @@ -32,7 +32,10 @@ export type GridOwnProps = { }; /** @public */ -export interface GridProps extends SpaceProps, GridOwnProps { +export interface GridProps + extends SpaceProps, + GridOwnProps, + Omit, 'children'> { columns?: Responsive; gap?: Responsive; } @@ -46,7 +49,9 @@ export type GridItemOwnProps = { }; /** @public */ -export interface GridItemProps extends GridItemOwnProps { +export interface GridItemProps + extends GridItemOwnProps, + Omit, 'children'> { colSpan?: Responsive; colEnd?: Responsive; colStart?: Responsive; diff --git a/packages/ui/src/components/HeaderPage/HeaderPage.module.css b/packages/ui/src/components/Header/Header.module.css similarity index 100% rename from packages/ui/src/components/HeaderPage/HeaderPage.module.css rename to packages/ui/src/components/Header/Header.module.css diff --git a/packages/ui/src/components/HeaderPage/HeaderPage.stories.tsx b/packages/ui/src/components/Header/Header.stories.tsx similarity index 97% rename from packages/ui/src/components/HeaderPage/HeaderPage.stories.tsx rename to packages/ui/src/components/Header/Header.stories.tsx index 0c027477e3..e605f2ed58 100644 --- a/packages/ui/src/components/HeaderPage/HeaderPage.stories.tsx +++ b/packages/ui/src/components/Header/Header.stories.tsx @@ -16,7 +16,7 @@ import preview from '../../../../../.storybook/preview'; import type { StoryFn } from '@storybook/react-vite'; -import { HeaderPage } from './HeaderPage'; +import { Header } from './Header'; import type { HeaderTab } from '../PluginHeader/types'; import { MemoryRouter } from 'react-router-dom'; import { @@ -31,8 +31,8 @@ import { import { RiMore2Line } from '@remixicon/react'; const meta = preview.meta({ - title: 'Backstage UI/HeaderPage', - component: HeaderPage, + title: 'Backstage UI/Header', + component: Header, parameters: { layout: 'fullscreen', }, @@ -145,7 +145,7 @@ export const WithTabs = meta.story({ export const WithCustomActions = meta.story({ decorators: [withRouter], render: () => ( - @@ -196,7 +196,7 @@ export const WithLongBreadcrumbs = meta.story({ export const WithEverything = meta.story({ decorators: [withRouter], render: () => ( - Custom action} @@ -239,7 +239,7 @@ export const WithTabsMatchingStrategies = meta.story({ }, render: args => ( - +
Current URL: /mentorship/events @@ -292,7 +292,7 @@ export const WithTabsExactMatching = meta.story({ }, render: args => ( - +
Current URL: /mentorship/events @@ -334,7 +334,7 @@ export const WithTabsPrefixMatchingDeep = meta.story({ }, render: args => ( - +
Current URL: /catalog/users/john/details diff --git a/packages/ui/src/components/HeaderPage/HeaderPage.tsx b/packages/ui/src/components/Header/Header.tsx similarity index 86% rename from packages/ui/src/components/HeaderPage/HeaderPage.tsx rename to packages/ui/src/components/Header/Header.tsx index d1cd146567..d9d96eb7af 100644 --- a/packages/ui/src/components/HeaderPage/HeaderPage.tsx +++ b/packages/ui/src/components/Header/Header.tsx @@ -14,23 +14,23 @@ * limitations under the License. */ -import type { HeaderPageProps } from './types'; +import type { HeaderProps } from './types'; import { Text } from '../Text'; import { RiArrowRightSLine } from '@remixicon/react'; import { Tabs, TabList, Tab } from '../Tabs'; import { useDefinition } from '../../hooks/useDefinition'; -import { HeaderPageDefinition } from './definition'; +import { HeaderDefinition } from './definition'; import { Container } from '../Container'; import { Link } from '../Link'; import { Fragment } from 'react/jsx-runtime'; /** - * A component that renders a header page. + * A secondary header with title, breadcrumbs, tabs, and actions. * * @public */ -export const HeaderPage = (props: HeaderPageProps) => { - const { ownProps } = useDefinition(HeaderPageDefinition, props); +export const Header = (props: HeaderProps) => { + const { ownProps } = useDefinition(HeaderDefinition, props); const { classes, title, tabs, customActions, breadcrumbs } = ownProps; return ( @@ -81,3 +81,9 @@ export const HeaderPage = (props: HeaderPageProps) => { ); }; + +/** + * @public + * @deprecated Use {@link Header} instead. + */ +export const HeaderPage = Header; diff --git a/packages/ui/src/components/HeaderPage/definition.ts b/packages/ui/src/components/Header/definition.ts similarity index 76% rename from packages/ui/src/components/HeaderPage/definition.ts rename to packages/ui/src/components/Header/definition.ts index 16fc5c9c67..0eb3ad33a3 100644 --- a/packages/ui/src/components/HeaderPage/definition.ts +++ b/packages/ui/src/components/Header/definition.ts @@ -15,14 +15,14 @@ */ import { defineComponent } from '../../hooks/useDefinition'; -import type { HeaderPageOwnProps } from './types'; -import styles from './HeaderPage.module.css'; +import type { HeaderOwnProps } from './types'; +import styles from './Header.module.css'; /** - * Component definition for HeaderPage + * Component definition for Header * @public */ -export const HeaderPageDefinition = defineComponent()({ +export const HeaderDefinition = defineComponent()({ styles, classNames: { root: 'bui-HeaderPage', @@ -39,3 +39,9 @@ export const HeaderPageDefinition = defineComponent()({ className: {}, }, }); + +/** + * @public + * @deprecated Use {@link HeaderDefinition} instead. + */ +export const HeaderPageDefinition = HeaderDefinition; diff --git a/packages/ui/src/components/HeaderPage/index.tsx b/packages/ui/src/components/Header/index.tsx similarity index 80% rename from packages/ui/src/components/HeaderPage/index.tsx rename to packages/ui/src/components/Header/index.tsx index 781a061a83..8738114e7d 100644 --- a/packages/ui/src/components/HeaderPage/index.tsx +++ b/packages/ui/src/components/Header/index.tsx @@ -14,9 +14,12 @@ * limitations under the License. */ -export { HeaderPage } from './HeaderPage'; -export { HeaderPageDefinition } from './definition'; +export { Header, HeaderPage } from './Header'; +export { HeaderDefinition, HeaderPageDefinition } from './definition'; export type { + HeaderOwnProps, + HeaderProps, + HeaderBreadcrumb, HeaderPageOwnProps, HeaderPageProps, HeaderPageBreadcrumb, diff --git a/packages/ui/src/components/HeaderPage/types.ts b/packages/ui/src/components/Header/types.ts similarity index 60% rename from packages/ui/src/components/HeaderPage/types.ts rename to packages/ui/src/components/Header/types.ts index b603cb7084..1b62ee2538 100644 --- a/packages/ui/src/components/HeaderPage/types.ts +++ b/packages/ui/src/components/Header/types.ts @@ -17,31 +17,49 @@ import type { HeaderTab } from '../PluginHeader/types'; /** - * Own props for the HeaderPage component. + * Own props for the Header component. * * @public */ -export interface HeaderPageOwnProps { +export interface HeaderOwnProps { title?: string; customActions?: React.ReactNode; tabs?: HeaderTab[]; - breadcrumbs?: HeaderPageBreadcrumb[]; + breadcrumbs?: HeaderBreadcrumb[]; className?: string; } /** - * Props for the main HeaderPage component. + * Props for the Header component. * * @public */ -export interface HeaderPageProps extends HeaderPageOwnProps {} +export interface HeaderProps extends HeaderOwnProps {} /** * Represents a breadcrumb item in the header. * * @public */ -export interface HeaderPageBreadcrumb { +export interface HeaderBreadcrumb { label: string; href: string; } + +/** + * @public + * @deprecated Use {@link HeaderOwnProps} instead. + */ +export type HeaderPageOwnProps = HeaderOwnProps; + +/** + * @public + * @deprecated Use {@link HeaderProps} instead. + */ +export type HeaderPageProps = HeaderProps; + +/** + * @public + * @deprecated Use {@link HeaderBreadcrumb} instead. + */ +export type HeaderPageBreadcrumb = HeaderBreadcrumb; diff --git a/packages/ui/src/components/Link/Link.tsx b/packages/ui/src/components/Link/Link.tsx index 500bbc4f59..c0de9c3087 100644 --- a/packages/ui/src/components/Link/Link.tsx +++ b/packages/ui/src/components/Link/Link.tsx @@ -20,9 +20,10 @@ import type { LinkProps } from './types'; import { useDefinition } from '../../hooks/useDefinition'; import { LinkDefinition } from './definition'; import { InternalLinkProvider } from '../InternalLinkProvider'; +import { getNodeText } from '../../analytics/getNodeText'; const LinkInternal = forwardRef((props, ref) => { - const { ownProps, restProps, dataAttributes } = useDefinition( + const { ownProps, restProps, dataAttributes, analytics } = useDefinition( LinkDefinition, props, ); @@ -33,6 +34,17 @@ const LinkInternal = forwardRef((props, ref) => { const { linkProps } = useLink(restProps, linkRef); + const handleClick = (e: React.MouseEvent) => { + linkProps.onClick?.(e); + const text = + restProps['aria-label'] ?? + getNodeText(children) ?? + String(restProps.href ?? ''); + analytics.captureEvent('click', text, { + attributes: { to: String(restProps.href ?? '') }, + }); + }; + return ( ((props, ref) => { ref={linkRef} title={title} className={classes.root} + onClick={handleClick} > {children} diff --git a/packages/ui/src/components/Link/definition.ts b/packages/ui/src/components/Link/definition.ts index c0814c8a9a..15d0d35e12 100644 --- a/packages/ui/src/components/Link/definition.ts +++ b/packages/ui/src/components/Link/definition.ts @@ -27,7 +27,9 @@ export const LinkDefinition = defineComponent()({ classNames: { root: 'bui-Link', }, + analytics: true, propDefs: { + noTrack: {}, variant: { dataAttribute: true, default: 'body-medium' }, weight: { dataAttribute: true, default: 'regular' }, color: { dataAttribute: true, default: 'primary' }, diff --git a/packages/ui/src/components/Link/types.ts b/packages/ui/src/components/Link/types.ts index a2f8163e79..75b059a42b 100644 --- a/packages/ui/src/components/Link/types.ts +++ b/packages/ui/src/components/Link/types.ts @@ -26,6 +26,7 @@ import type { ReactNode } from 'react'; /** @public */ export type LinkOwnProps = { + noTrack?: boolean; variant?: TextVariants | Partial>; weight?: TextWeights | Partial>; color?: diff --git a/packages/ui/src/components/Menu/Menu.tsx b/packages/ui/src/components/Menu/Menu.tsx index 354be33ce7..5ab55cf7d8 100644 --- a/packages/ui/src/components/Menu/Menu.tsx +++ b/packages/ui/src/components/Menu/Menu.tsx @@ -66,6 +66,7 @@ import { isInternalLink, createRoutingRegistration, } from '../InternalLinkProvider'; +import { getNodeText } from '../../analytics/getNodeText'; import { Box } from '../Box'; import { BgReset } from '../../hooks/useBg'; @@ -311,7 +312,7 @@ export const MenuAutocompleteListbox = ( /** @public */ export const MenuItem = (props: MenuItemProps) => { - const { ownProps, restProps, dataAttributes } = useDefinition( + const { ownProps, restProps, dataAttributes, analytics } = useDefinition( MenuItemDefinition, props, ); @@ -319,6 +320,16 @@ export const MenuItem = (props: MenuItemProps) => { useRoutingRegistrationEffect(href); + const handleAction = () => { + if (href) { + const text = + restProps['aria-label'] ?? getNodeText(children) ?? String(href); + analytics.captureEvent('click', text, { + attributes: { to: String(href) }, + }); + } + }; + // External links open in new tab via window.open instead of client-side routing if (href && !isInternalLink(href)) { return ( @@ -326,8 +337,12 @@ export const MenuItem = (props: MenuItemProps) => { className={classes.root} {...dataAttributes} textValue={typeof children === 'string' ? children : undefined} - onAction={() => window.open(href, '_blank', 'noopener,noreferrer')} {...restProps} + onAction={() => { + restProps.onAction?.(); + handleAction(); + window.open(href, '_blank', 'noopener,noreferrer'); + }} >
@@ -349,6 +364,10 @@ export const MenuItem = (props: MenuItemProps) => { href={href} textValue={typeof children === 'string' ? children : undefined} {...restProps} + onAction={() => { + restProps.onAction?.(); + handleAction(); + }} >
diff --git a/packages/ui/src/components/Menu/definition.ts b/packages/ui/src/components/Menu/definition.ts index ab65cf7dd1..076e9ce3ae 100644 --- a/packages/ui/src/components/Menu/definition.ts +++ b/packages/ui/src/components/Menu/definition.ts @@ -104,11 +104,13 @@ export const MenuItemDefinition = defineComponent()({ itemContent: 'bui-MenuItemContent', itemArrow: 'bui-MenuItemArrow', }, + analytics: true, propDefs: { iconStart: {}, children: {}, color: { dataAttribute: true, default: 'primary' }, href: {}, + noTrack: {}, className: {}, }, }); diff --git a/packages/ui/src/components/Menu/types.ts b/packages/ui/src/components/Menu/types.ts index 14c1baa5c3..8a40897ddd 100644 --- a/packages/ui/src/components/Menu/types.ts +++ b/packages/ui/src/components/Menu/types.ts @@ -91,6 +91,7 @@ export type MenuItemOwnProps = { children: React.ReactNode; color?: 'primary' | 'danger'; href?: RAMenuItemProps['href']; + noTrack?: boolean; className?: string; }; diff --git a/packages/ui/src/components/PluginHeader/PluginHeader.stories.tsx b/packages/ui/src/components/PluginHeader/PluginHeader.stories.tsx index 91cb69b2a8..151c48aab6 100644 --- a/packages/ui/src/components/PluginHeader/PluginHeader.stories.tsx +++ b/packages/ui/src/components/PluginHeader/PluginHeader.stories.tsx @@ -20,7 +20,7 @@ import { PluginHeader } from './PluginHeader'; import type { HeaderTab } from './types'; import { Button, - HeaderPage, + Header, Container, Text, ButtonIcon, @@ -35,7 +35,7 @@ import { RiCloudy2Line, RiMore2Line, } from '@remixicon/react'; -import { HeaderPageBreadcrumb } from '../HeaderPage/types'; +import { HeaderBreadcrumb } from '../Header/types'; const meta = preview.meta({ title: 'Backstage UI/PluginHeader', @@ -117,7 +117,7 @@ const menuItems = [ }, ]; -const breadcrumbs: HeaderPageBreadcrumb[] = [ +const breadcrumbs: HeaderBreadcrumb[] = [ { label: 'Home', href: '/', @@ -246,7 +246,7 @@ export const WithAllOptionsAndTabs = WithCustomActions.extend({ }, }); -export const WithHeaderPage = meta.story({ +export const WithHeader = meta.story({ args: { ...WithAllOptionsAndTabs.input.args, }, @@ -263,7 +263,7 @@ export const WithHeaderPage = meta.story({ } /> - Custom action} @@ -278,7 +278,7 @@ export const WithLayout = meta.story({ render: args => ( <> - Custom action} @@ -293,7 +293,7 @@ export const WithLayoutNoTabs = meta.story({ render: args => ( <> - +
), }); @@ -316,7 +316,7 @@ export const WithEverything = meta.story({ } /> - (props: RowProps) { - const { ownProps, restProps } = useDefinition(RowDefinition, props); + const { ownProps, restProps, analytics } = useDefinition( + RowDefinition, + props, + ); const { classes, columns, children, href } = ownProps; const hasInternalHref = !!href && !isExternalLink(href); + const handlePress = () => { + if (href) { + analytics.captureEvent('click', href, { + attributes: { to: String(href) }, + }); + } + }; + let { selectionBehavior, selectionMode } = useTableOptions(); const content = ( @@ -59,6 +70,10 @@ export function Row(props: RowProps) { className={classes.root} data-react-aria-pressable={hasInternalHref ? 'true' : undefined} {...restProps} + onAction={() => { + restProps.onAction?.(); + handlePress(); + }} > {content} diff --git a/packages/ui/src/components/Table/definition.ts b/packages/ui/src/components/Table/definition.ts index 496d4b6cfe..702e2aeb9a 100644 --- a/packages/ui/src/components/Table/definition.ts +++ b/packages/ui/src/components/Table/definition.ts @@ -75,6 +75,7 @@ export const TableBodyDefinition = defineComponent()({ */ export const RowDefinition = defineComponent()({ styles, + analytics: true, classNames: { root: 'bui-TableRow', cell: 'bui-TableCell', @@ -84,6 +85,7 @@ export const RowDefinition = defineComponent()({ columns: {}, children: {}, href: {}, + noTrack: {}, }, }); diff --git a/packages/ui/src/components/Table/types.ts b/packages/ui/src/components/Table/types.ts index d1cf5d1f13..099a855d33 100644 --- a/packages/ui/src/components/Table/types.ts +++ b/packages/ui/src/components/Table/types.ts @@ -73,6 +73,7 @@ export type RowOwnProps = { columns?: ReactAriaRowProps['columns']; children?: ReactAriaRowProps['children']; href?: string; + noTrack?: boolean; }; /** @public */ diff --git a/packages/ui/src/components/Tabs/Tabs.tsx b/packages/ui/src/components/Tabs/Tabs.tsx index d625d2abf0..fcab2c71b9 100644 --- a/packages/ui/src/components/Tabs/Tabs.tsx +++ b/packages/ui/src/components/Tabs/Tabs.tsx @@ -54,6 +54,7 @@ import { isInternalLink, createRoutingRegistration, } from '../InternalLinkProvider'; +import { getNodeText } from '../../analytics/getNodeText'; const { RoutingProvider, useRoutingRegistrationEffect } = createRoutingRegistration(); @@ -152,7 +153,7 @@ export const Tabs = (props: TabsProps) => { return ''; } - let selectedId: string | null = null; + let selectedId: string | undefined; let maxSegments = -1; activeTabs.forEach((segmentCount, id) => { @@ -331,10 +332,25 @@ function RoutedTabEffects({ * @public */ export const Tab = (props: TabProps) => { - const { ownProps, restProps } = useDefinition(TabDefinition, props); + const { ownProps, restProps, analytics } = useDefinition( + TabDefinition, + props, + ); const { classes, matchStrategy, href, id } = ownProps; const { setTabRef } = useTabsContext(); + const handlePress = () => { + if (href) { + const text = + restProps['aria-label'] ?? + getNodeText(restProps.children) ?? + String(href); + analytics.captureEvent('click', text, { + attributes: { to: String(href) }, + }); + } + }; + return ( <> {isInternalLink(href) && ( @@ -350,6 +366,10 @@ export const Tab = (props: TabProps) => { ref={el => setTabRef(id as string, el as HTMLDivElement)} href={href} {...restProps} + onPress={e => { + restProps.onPress?.(e); + handlePress(); + }} /> ); diff --git a/packages/ui/src/components/Tabs/definition.ts b/packages/ui/src/components/Tabs/definition.ts index f938c8b3f8..4563d5e8c8 100644 --- a/packages/ui/src/components/Tabs/definition.ts +++ b/packages/ui/src/components/Tabs/definition.ts @@ -58,11 +58,13 @@ export const TabDefinition = defineComponent()({ classNames: { root: 'bui-Tab', }, + analytics: true, propDefs: { className: {}, matchStrategy: {}, href: {}, id: {}, + noTrack: {}, }, }); diff --git a/packages/ui/src/components/Tabs/types.ts b/packages/ui/src/components/Tabs/types.ts index 09f7e2e944..b722f82011 100644 --- a/packages/ui/src/components/Tabs/types.ts +++ b/packages/ui/src/components/Tabs/types.ts @@ -82,6 +82,7 @@ export type TabOwnProps = { matchStrategy?: TabMatchStrategy; href?: AriaTabProps['href']; id?: AriaTabProps['id']; + noTrack?: boolean; }; /** diff --git a/packages/ui/src/components/TagGroup/TagGroup.tsx b/packages/ui/src/components/TagGroup/TagGroup.tsx index 5208647bd8..8258ab8554 100644 --- a/packages/ui/src/components/TagGroup/TagGroup.tsx +++ b/packages/ui/src/components/TagGroup/TagGroup.tsx @@ -26,6 +26,7 @@ import { RiCloseCircleLine } from '@remixicon/react'; import { useDefinition } from '../../hooks/useDefinition'; import { TagGroupDefinition, TagDefinition } from './definition'; import { createRoutingRegistration } from '../InternalLinkProvider'; +import { getNodeText } from '../../analytics/getNodeText'; const { RoutingProvider, useRoutingRegistrationEffect } = createRoutingRegistration(); @@ -60,7 +61,7 @@ export const TagGroup = (props: TagGroupProps) => { * @public */ export const Tag = forwardRef((props, ref) => { - const { ownProps, restProps, dataAttributes } = useDefinition( + const { ownProps, restProps, dataAttributes, analytics } = useDefinition( TagDefinition, props, ); @@ -69,6 +70,19 @@ export const Tag = forwardRef((props, ref) => { useRoutingRegistrationEffect(href); + const handlePress = () => { + if (href) { + const text = + (props as React.AriaAttributes)['aria-label'] ?? + textValue ?? + getNodeText(children) ?? + String(href); + analytics.captureEvent('click', text, { + attributes: { to: String(href) }, + }); + } + }; + return ( ((props, ref) => { href={href} {...dataAttributes} {...restProps} + onPress={e => { + restProps.onPress?.(e); + handlePress(); + }} > {({ allowsRemoving }) => ( <> diff --git a/packages/ui/src/components/TagGroup/definition.ts b/packages/ui/src/components/TagGroup/definition.ts index a4134645be..38f6bef0ba 100644 --- a/packages/ui/src/components/TagGroup/definition.ts +++ b/packages/ui/src/components/TagGroup/definition.ts @@ -44,7 +44,9 @@ export const TagDefinition = defineComponent()({ icon: 'bui-TagIcon', removeButton: 'bui-TagRemoveButton', }, + analytics: true, propDefs: { + noTrack: {}, icon: {}, size: { dataAttribute: true, default: 'small' }, href: {}, diff --git a/packages/ui/src/components/TagGroup/types.ts b/packages/ui/src/components/TagGroup/types.ts index daf146673d..3bcbdaafd1 100644 --- a/packages/ui/src/components/TagGroup/types.ts +++ b/packages/ui/src/components/TagGroup/types.ts @@ -58,6 +58,7 @@ export type TagOwnProps = { href?: ReactAriaTagProps['href']; children?: ReactAriaTagProps['children']; className?: string; + noTrack?: boolean; }; /** diff --git a/packages/ui/src/definitions.ts b/packages/ui/src/definitions.ts index 6a8ca28e29..96bd311caf 100644 --- a/packages/ui/src/definitions.ts +++ b/packages/ui/src/definitions.ts @@ -43,7 +43,10 @@ export { GridItemDefinition, } from './components/Grid/definition'; export { PluginHeaderDefinition } from './components/PluginHeader/definition'; -export { HeaderPageDefinition } from './components/HeaderPage/definition'; +export { + HeaderDefinition, + HeaderPageDefinition, +} from './components/Header/definition'; export { LinkDefinition } from './components/Link/definition'; export { MenuDefinition } from './components/Menu/definition'; export { PasswordFieldDefinition } from './components/PasswordField/definition'; diff --git a/packages/ui/src/guidelines/CardsWithTable.stories.tsx b/packages/ui/src/guidelines/CardsWithTable.stories.tsx index 51cc416423..a8ecaf0bef 100644 --- a/packages/ui/src/guidelines/CardsWithTable.stories.tsx +++ b/packages/ui/src/guidelines/CardsWithTable.stories.tsx @@ -32,7 +32,7 @@ import { useTable, type ColumnConfig, PluginHeader, - HeaderPage, + Header, Button, } from '..'; @@ -235,7 +235,7 @@ const CardsWithTableLayout = () => { return ( <> - Custom action} /> diff --git a/packages/ui/src/hooks/useDefinition/defineComponent.ts b/packages/ui/src/hooks/useDefinition/defineComponent.ts index 6dbfb5742b..fc58a6774e 100644 --- a/packages/ui/src/hooks/useDefinition/defineComponent.ts +++ b/packages/ui/src/hooks/useDefinition/defineComponent.ts @@ -14,13 +14,19 @@ * limitations under the License. */ -import type { ComponentConfig, BgPropsConstraint } from './types'; +import type { + ComponentConfig, + BgPropsConstraint, + AnalyticsPropsConstraint, +} from './types'; export function defineComponent

>() { return < const S extends Record, const C extends ComponentConfig, >( - config: C & BgPropsConstraint, + config: C & + BgPropsConstraint & + AnalyticsPropsConstraint, ): C => config; } diff --git a/packages/ui/src/hooks/useDefinition/types.ts b/packages/ui/src/hooks/useDefinition/types.ts index 2ddc0c88c5..a819cb5d35 100644 --- a/packages/ui/src/hooks/useDefinition/types.ts +++ b/packages/ui/src/hooks/useDefinition/types.ts @@ -16,6 +16,7 @@ import type { ReactNode } from 'react'; import type { Responsive } from '../../types'; +import type { AnalyticsTracker } from '../../analytics/types'; import type { utilityClassMap } from '../../utils/utilityClassMap'; export type UnwrapResponsive = T extends Responsive ? U : T; @@ -43,6 +44,13 @@ export interface ComponentConfig< * - `'consumer'` — calls `useBgConsumer`, sets `data-on-bg` */ bg?: 'provider' | 'consumer'; + /** + * Whether this component fires analytics events. + * When true, `useDefinition` will call `useAnalytics()` and return + * an `analytics` tracker. The component's own props type must include + * `noTrack?: boolean`. + */ + analytics?: boolean; } /** @@ -66,6 +74,18 @@ export type BgPropsConstraint = Bg extends 'provider' } : {}; +/** + * Type constraint that validates analytics props are present in the props type. + * Components with analytics: true must include 'noTrack' in their props. + */ +export type AnalyticsPropsConstraint = Analytics extends true + ? 'noTrack' extends keyof P + ? {} + : { + __error: 'Analytics components must include noTrack in own props type.'; + } + : {}; + export interface UseDefinitionOptions> { utilityTarget?: keyof D['classNames'] | null; classNameTarget?: keyof D['classNames'] | null; @@ -135,10 +155,10 @@ type ResolvedUtilityStyle> = UtilityStyle< UtilityKeys >; -export interface UseDefinitionResult< +export type UseDefinitionResult< D extends ComponentConfig, P extends Record, -> { +> = { ownProps: ResolveBgProps>; // Rest props excludes both propDefs keys AND utility prop keys @@ -149,4 +169,4 @@ export interface UseDefinitionResult< dataAttributes: DataAttributes; utilityStyle: ResolvedUtilityStyle; -} +} & (D['analytics'] extends true ? { analytics: AnalyticsTracker } : {}); diff --git a/packages/ui/src/hooks/useDefinition/useDefinition.tsx b/packages/ui/src/hooks/useDefinition/useDefinition.tsx index 6bbd08e340..20ce94e123 100644 --- a/packages/ui/src/hooks/useDefinition/useDefinition.tsx +++ b/packages/ui/src/hooks/useDefinition/useDefinition.tsx @@ -19,6 +19,8 @@ import clsx from 'clsx'; import { useBreakpoint } from '../useBreakpoint'; import { useBgProvider, useBgConsumer, BgProvider } from '../useBg'; import { resolveDefinitionProps, processUtilityProps } from './helpers'; +import { useAnalytics } from '../../analytics/useAnalytics'; +import { noopTracker } from '../../analytics/useAnalytics'; import type { ComponentConfig, UseDefinitionOptions, @@ -82,6 +84,14 @@ export function useDefinition< (definition.utilityProps ?? []) as readonly UtilityKeys[], ); + // Analytics: conditionally call useAnalytics based on definition flag + // Safe: definition is a module-level constant, condition never changes at runtime + let analytics = noopTracker; + if (definition.analytics) { + const tracker = useAnalytics(); + analytics = ownPropsResolved.noTrack ? noopTracker : tracker; + } + const utilityTarget = options?.utilityTarget ?? 'root'; const classNameTarget = options?.classNameTarget ?? 'root'; @@ -120,5 +130,6 @@ export function useDefinition< restProps, dataAttributes, utilityStyle, + ...(definition.analytics ? { analytics } : {}), } as unknown as UseDefinitionResult; } diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index cf02c39bdd..2a384be120 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -36,7 +36,7 @@ export * from './components/Card'; export * from './components/Dialog'; export * from './components/FieldLabel'; export * from './components/PluginHeader'; -export * from './components/HeaderPage'; +export * from './components/Header'; export * from './components/ButtonIcon'; export * from './components/ButtonLink'; export * from './components/Checkbox'; @@ -68,3 +68,12 @@ export * from './types'; export { useBreakpoint } from './hooks/useBreakpoint'; export { useBgProvider, useBgConsumer, BgProvider } from './hooks/useBg'; export type { BgContextValue, BgProviderProps } from './hooks/useBg'; + +// Analytics +export { useAnalytics, BUIProvider, getNodeText } from './analytics'; +export type { + AnalyticsTracker, + AnalyticsEventAttributes, + UseAnalyticsFn, + BUIProviderProps, +} from './analytics'; diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index ba2def9480..dfeb65ede2 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -62,13 +62,12 @@ "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-permission-react": "workspace:^", - "@graphiql/react": "^0.23.0", + "@graphiql/react": "0.29.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", - "graphiql": "3.1.1", + "graphiql": "^3.9.0", "graphql": "^16.0.0", - "graphql-config": "^5.0.2", "graphql-ws": "^5.4.1", "swagger-ui-react": "^5.27.1" }, @@ -85,6 +84,7 @@ "@types/highlightjs": "^10.1.0", "@types/react": "^18.0.0", "@types/swagger-ui-react": "^5.0.0", + "graphql-config": "^5.1.6", "react": "^18.0.2", "react-dom": "^18.0.2", "react-router-dom": "^6.30.2" diff --git a/plugins/api-docs/report-alpha.api.md b/plugins/api-docs/report-alpha.api.md index de6388ed2b..0ee7ac5460 100644 --- a/plugins/api-docs/report-alpha.api.md +++ b/plugins/api-docs/report-alpha.api.md @@ -563,7 +563,6 @@ const _default: OverridableFrontendPlugin< kind: 'page'; name: undefined; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; diff --git a/plugins/api-docs/src/setupTests.ts b/plugins/api-docs/src/setupTests.ts index 03227e1415..19b6d29795 100644 --- a/plugins/api-docs/src/setupTests.ts +++ b/plugins/api-docs/src/setupTests.ts @@ -23,3 +23,6 @@ Object.defineProperty(global, 'TextEncoder', { Object.defineProperty(global, 'TextDecoder', { value: require('node:util').TextDecoder, }); + +// Use a 15s timeout to accommodate the slowest API docs rendering tests under concurrency. +jest.setTimeout(15_000); diff --git a/plugins/app-react/report.api.md b/plugins/app-react/report.api.md index 98e977ef20..a4e63e254b 100644 --- a/plugins/app-react/report.api.md +++ b/plugins/app-react/report.api.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnalyticsImplementation } from '@backstage/frontend-plugin-api'; import { AppNode } from '@backstage/frontend-plugin-api'; import { AppTheme } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; @@ -18,6 +19,40 @@ import { RouteRef } from '@backstage/frontend-plugin-api'; import { SwappableComponentRef } from '@backstage/frontend-plugin-api'; import { TranslationMessages } from '@backstage/frontend-plugin-api'; import { TranslationResource } from '@backstage/frontend-plugin-api'; +import { TypesToApiRefs } from '@backstage/frontend-plugin-api'; + +// @public +export const AnalyticsImplementationBlueprint: ExtensionBlueprint<{ + kind: 'analytics'; + params: ( + params: AnalyticsImplementationFactory, + ) => ExtensionBlueprintParams>; + output: ExtensionDataRef< + AnalyticsImplementationFactory<{}>, + 'core.analytics.factory', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + factory: ConfigurableExtensionDataRef< + AnalyticsImplementationFactory<{}>, + 'core.analytics.factory', + {} + >; + }; +}>; + +// @public (undocumented) +export type AnalyticsImplementationFactory< + Deps extends { + [name in string]: unknown; + } = {}, +> = { + deps: TypesToApiRefs; + factory(deps: Deps): AnalyticsImplementation; +}; // @public export const AppRootWrapperBlueprint: ExtensionBlueprint<{ diff --git a/plugins/app-react/src/blueprints/AnalyticsImplementationBlueprint.test.ts b/plugins/app-react/src/blueprints/AnalyticsImplementationBlueprint.test.ts new file mode 100644 index 0000000000..06ad9b877e --- /dev/null +++ b/plugins/app-react/src/blueprints/AnalyticsImplementationBlueprint.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AnalyticsImplementationBlueprint } from './AnalyticsImplementationBlueprint'; + +describe('AnalyticsBlueprint', () => { + it('should create an extension with sensible defaults', () => { + const factory = { + deps: {}, + factory: () => ({ captureEvent: () => {} }), + }; + + const extension = AnalyticsImplementationBlueprint.make({ + params: define => define(factory), + name: 'test', + }); + + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "T": undefined, + "attachTo": { + "id": "api:app/analytics", + "input": "implementations", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "analytics", + "name": "test", + "output": [ + [Function], + ], + "override": [Function], + "toString": [Function], + "version": "v2", + } + `); + }); +}); diff --git a/plugins/app-react/src/blueprints/AnalyticsImplementationBlueprint.ts b/plugins/app-react/src/blueprints/AnalyticsImplementationBlueprint.ts new file mode 100644 index 0000000000..d85c926ee0 --- /dev/null +++ b/plugins/app-react/src/blueprints/AnalyticsImplementationBlueprint.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 { + AnalyticsImplementation, + TypesToApiRefs, + createExtensionBlueprint, + createExtensionBlueprintParams, + createExtensionDataRef, +} from '@backstage/frontend-plugin-api'; + +/** @public */ +export type AnalyticsImplementationFactory< + Deps extends { [name in string]: unknown } = {}, +> = { + deps: TypesToApiRefs; + factory(deps: Deps): AnalyticsImplementation; +}; + +const factoryDataRef = + createExtensionDataRef().with({ + id: 'core.analytics.factory', + }); + +/** + * Creates analytics implementations. + * + * @public + */ +export const AnalyticsImplementationBlueprint = createExtensionBlueprint({ + kind: 'analytics', + attachTo: { id: 'api:app/analytics', input: 'implementations' }, + output: [factoryDataRef], + dataRefs: { + factory: factoryDataRef, + }, + defineParams: ( + params: AnalyticsImplementationFactory, + ) => createExtensionBlueprintParams(params as AnalyticsImplementationFactory), + *factory(params) { + yield factoryDataRef(params); + }, +}); diff --git a/plugins/app-react/src/blueprints/index.ts b/plugins/app-react/src/blueprints/index.ts index 318691c8db..ad9226f2a6 100644 --- a/plugins/app-react/src/blueprints/index.ts +++ b/plugins/app-react/src/blueprints/index.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +export { + AnalyticsImplementationBlueprint, + type AnalyticsImplementationFactory, +} from './AnalyticsImplementationBlueprint'; export { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint'; export { IconBundleBlueprint } from './IconBundleBlueprint'; export { NavContentBlueprint } from './NavContentBlueprint'; diff --git a/plugins/app-visualizer/report.api.md b/plugins/app-visualizer/report.api.md index e0e3f26dd2..9ee06eccb0 100644 --- a/plugins/app-visualizer/report.api.md +++ b/plugins/app-visualizer/report.api.md @@ -109,7 +109,6 @@ const visualizerPlugin: OverridableFrontendPlugin< >; }; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; diff --git a/plugins/app/src/extensions/AppRoot.tsx b/plugins/app/src/extensions/AppRoot.tsx index 08eaa50bd9..e43e4b46e4 100644 --- a/plugins/app/src/extensions/AppRoot.tsx +++ b/plugins/app/src/extensions/AppRoot.tsx @@ -29,12 +29,14 @@ import { createExtension, createExtensionInput, routeResolutionApiRef, + useAnalytics, } from '@backstage/frontend-plugin-api'; import { AppRootWrapperBlueprint, RouterBlueprint, SignInPageBlueprint, } from '@backstage/plugin-app-react'; +import { BUIProvider } from '@backstage/ui'; import { DiscoveryApi, ErrorApi, @@ -115,19 +117,21 @@ export const AppRoot = createExtension({ return [ coreExtensionData.reactElement( - - el.get(coreExtensionData.reactElement), - )} - > - {content} - , + + + el.get(coreExtensionData.reactElement), + )} + > + {content} + + , ), ]; }, diff --git a/plugins/auth-backend/src/service/OidcRouter.test.ts b/plugins/auth-backend/src/service/OidcRouter.test.ts index fb621fb6f9..dea9efbaaa 100644 --- a/plugins/auth-backend/src/service/OidcRouter.test.ts +++ b/plugins/auth-backend/src/service/OidcRouter.test.ts @@ -1120,6 +1120,124 @@ describe('OidcRouter', () => { }); }); + describe('CIMD metadata endpoint', () => { + it('should return 404 when CIMD is not enabled', async () => { + const { router } = await createRouter(databaseId); + + const { server } = await startTestBackend({ + features: [ + createBackendPlugin({ + pluginId: 'auth', + register(reg) { + reg.registerInit({ + deps: { httpRouter: coreServices.httpRouter }, + async init({ httpRouter }) { + httpRouter.use(router.getRouter()); + httpRouter.addAuthPolicy({ + path: '/', + allow: 'unauthenticated', + }); + }, + }); + }, + }), + ], + }); + + const response = await request(server) + .get('/api/auth/.well-known/oauth-client/cli.json') + .expect(404); + + expect(response.body).toEqual({ + error: 'not_found', + error_description: 'Client ID metadata documents not enabled', + }); + }); + + it('should return CIMD document when enabled', async () => { + const knex = await databases.init(databaseId); + + await knex.migrate.latest({ + directory: resolvePackagePath( + '@backstage/plugin-auth-backend', + 'migrations', + ), + }); + + const authDatabase = AuthDatabase.create({ + getClient: async () => knex, + }); + + const oidcDatabase = await OidcDatabase.create({ + database: authDatabase, + }); + + const userInfoDatabase = await UserInfoDatabase.create({ + database: authDatabase, + }); + + const mockTokenIssuer = { + issueToken: jest.fn(), + listPublicKeys: jest.fn(), + } as unknown as jest.Mocked; + + const oidcRouter = OidcRouter.create({ + auth: mockServices.auth.mock(), + tokenIssuer: mockTokenIssuer, + baseUrl: 'http://localhost:7007/api/auth', + appUrl: 'http://localhost:3000', + logger: mockServices.logger.mock(), + userInfo: userInfoDatabase, + oidc: oidcDatabase, + httpAuth: mockServices.httpAuth.mock(), + config: mockServices.rootConfig({ + data: { + auth: { + experimentalClientIdMetadataDocuments: { + enabled: true, + }, + }, + }, + }), + }); + + const { server } = await startTestBackend({ + features: [ + createBackendPlugin({ + pluginId: 'auth', + register(reg) { + reg.registerInit({ + deps: { httpRouter: coreServices.httpRouter }, + async init({ httpRouter }) { + httpRouter.use(oidcRouter.getRouter()); + httpRouter.addAuthPolicy({ + path: '/', + allow: 'unauthenticated', + }); + }, + }); + }, + }), + ], + }); + + const response = await request(server) + .get('/api/auth/.well-known/oauth-client/cli.json') + .expect(200); + + expect(response.body).toEqual({ + client_id: + 'http://localhost:7007/api/auth/.well-known/oauth-client/cli.json', + client_name: 'Backstage CLI', + redirect_uris: ['http://127.0.0.1:8055/callback'], + response_types: ['code'], + grant_types: ['authorization_code'], + token_endpoint_auth_method: 'none', + scope: 'openid offline_access', + }); + }); + }); + describe('CIMD authorization', () => { it('should enable authorization routes when only CIMD is enabled (not DCR)', async () => { const cimdClientId = 'https://example.com/oauth-client'; diff --git a/plugins/auth-backend/src/service/OidcRouter.ts b/plugins/auth-backend/src/service/OidcRouter.ts index 746036806c..dc921a9489 100644 --- a/plugins/auth-backend/src/service/OidcRouter.ts +++ b/plugins/auth-backend/src/service/OidcRouter.ts @@ -145,6 +145,7 @@ export class OidcRouter { private readonly appUrl: string; private readonly httpAuth: HttpAuthService; private readonly config: RootConfigService; + private readonly baseUrl: string; private constructor( oidc: OidcService, @@ -153,6 +154,7 @@ export class OidcRouter { appUrl: string, httpAuth: HttpAuthService, config: RootConfigService, + baseUrl: string, ) { this.oidc = oidc; this.logger = logger; @@ -160,6 +162,7 @@ export class OidcRouter { this.appUrl = appUrl; this.httpAuth = httpAuth; this.config = config; + this.baseUrl = baseUrl; } static create(options: { @@ -181,6 +184,7 @@ export class OidcRouter { options.appUrl, options.httpAuth, options.config, + options.baseUrl, ); } @@ -204,6 +208,32 @@ export class OidcRouter { res.json({ keys }); }); + // CIMD metadata endpoint for the Backstage CLI + // Automatically available when CIMD is enabled + router.get('/.well-known/oauth-client/cli.json', (_req, res) => { + const cimdEnabled = this.config.getOptionalBoolean( + 'auth.experimentalClientIdMetadataDocuments.enabled', + ); + + if (!cimdEnabled) { + res.status(404).json({ + error: 'not_found', + error_description: 'Client ID metadata documents not enabled', + }); + return; + } + + res.json({ + client_id: `${this.baseUrl}/.well-known/oauth-client/cli.json`, + client_name: 'Backstage CLI', + redirect_uris: ['http://127.0.0.1:8055/callback'], + response_types: ['code'], + grant_types: ['authorization_code'], + token_endpoint_auth_method: 'none', + scope: 'openid offline_access', + }); + }); + // UserInfo endpoint // https://openid.net/specs/openid-connect-core-1_0.html#UserInfo // Returns claims about the authenticated user using an access token diff --git a/plugins/auth/report.api.md b/plugins/auth/report.api.md index a281d265dc..8e8ac71d31 100644 --- a/plugins/auth/report.api.md +++ b/plugins/auth/report.api.md @@ -88,7 +88,6 @@ const _default: OverridableFrontendPlugin< >; }; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; diff --git a/plugins/catalog-backend-module-gitea/src/providers/GiteaEntityProvider.test.ts b/plugins/catalog-backend-module-gitea/src/providers/GiteaEntityProvider.test.ts index d6a31899f1..539d432793 100644 --- a/plugins/catalog-backend-module-gitea/src/providers/GiteaEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitea/src/providers/GiteaEntityProvider.test.ts @@ -34,6 +34,7 @@ describe('GiteaEntityProvider', () => { triggerTask: jest.fn(), scheduleTask: jest.fn(), getScheduledTasks: jest.fn(), + cancelTask: jest.fn(), }; const mockTaskRunner = { run: jest.fn(), diff --git a/plugins/catalog-backend-module-msgraph/report.api.md b/plugins/catalog-backend-module-msgraph/report.api.md index c6687a638a..7799def397 100644 --- a/plugins/catalog-backend-module-msgraph/report.api.md +++ b/plugins/catalog-backend-module-msgraph/report.api.md @@ -78,6 +78,7 @@ export class MicrosoftGraphClient { groupId: string, query?: ODataQuery, queryMode?: 'basic' | 'advanced', + signal?: AbortSignal, ): AsyncIterable; // (undocumented) getGroupPhoto(groupId: string, sizeId?: string): Promise; @@ -89,13 +90,18 @@ export class MicrosoftGraphClient { query?: ODataQuery, queryMode?: 'basic' | 'advanced', path?: string, + signal?: AbortSignal, ): AsyncIterable; getGroupUserMembers( groupId: string, query?: ODataQuery, queryMode?: 'basic' | 'advanced', + signal?: AbortSignal, ): AsyncIterable; - getOrganization(tenantId: string): Promise; + getOrganization( + tenantId: string, + signal?: AbortSignal, + ): Promise; // (undocumented) getUserPhoto(userId: string, sizeId?: string): Promise; getUserPhotoWithSizeLimit( @@ -106,21 +112,25 @@ export class MicrosoftGraphClient { query?: ODataQuery, queryMode?: 'basic' | 'advanced', path?: string, + signal?: AbortSignal, ): AsyncIterable; requestApi( path: string, query?: ODataQuery, headers?: Record, + signal?: AbortSignal, ): Promise; requestCollection( path: string, query?: ODataQuery, queryMode?: 'basic' | 'advanced', + signal?: AbortSignal, ): AsyncIterable; requestRaw( url: string, headers?: Record, retryCount?: number, + signal?: AbortSignal, ): Promise; } @@ -142,7 +152,10 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { options: MicrosoftGraphOrgEntityProviderOptions, ): MicrosoftGraphOrgEntityProvider[]; getProviderName(): string; - read(options?: { logger?: LoggerService }): Promise; + read(options?: { + logger?: LoggerService; + signal?: AbortSignal; + }): Promise; } // @public @deprecated @@ -304,6 +317,7 @@ export function readMicrosoftGraphOrg( groupTransformer?: GroupTransformer; organizationTransformer?: OrganizationTransformer; logger: LoggerService; + signal?: AbortSignal; }, ): Promise<{ users: UserEntity[]; diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts index 94f4e88df1..1889fc130b 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts @@ -128,6 +128,7 @@ export class MicrosoftGraphClient { path: string, query?: ODataQuery, queryMode?: 'basic' | 'advanced', + signal?: AbortSignal, ): AsyncIterable { // upgrade to advanced query mode transparently when "search" is used // to stay backwards compatible. @@ -151,7 +152,7 @@ export class MicrosoftGraphClient { } : {}; - let response = await this.requestApi(path, query, headers); + let response = await this.requestApi(path, query, headers, signal); for (;;) { if (response.status !== 200) { @@ -170,7 +171,12 @@ export class MicrosoftGraphClient { return; } - response = await this.requestRaw(result['@odata.nextLink'], headers); + response = await this.requestRaw( + result['@odata.nextLink'], + headers, + 2, + signal, + ); } } @@ -186,6 +192,7 @@ export class MicrosoftGraphClient { path: string, query?: ODataQuery, headers?: Record, + signal?: AbortSignal, ): Promise { const queryString = qs.stringify( { @@ -205,6 +212,8 @@ export class MicrosoftGraphClient { return await this.requestRaw( `${this.baseUrl}/${path}${queryString}`, headers, + 2, + signal, ); } @@ -218,6 +227,7 @@ export class MicrosoftGraphClient { url: string, headers?: Record, retryCount = 2, + signal?: AbortSignal, ): Promise { // Make sure that we always have a valid access token (might be cached) const urlObj = new URL(url); @@ -235,10 +245,11 @@ export class MicrosoftGraphClient { ...headers, Authorization: `Bearer ${token.token}`, }, + signal, }); } catch (e: any) { if (e?.code === 'ETIMEDOUT' && retryCount > 0) { - return this.requestRaw(url, headers, retryCount - 1); + return this.requestRaw(url, headers, retryCount - 1, signal); } throw e; } @@ -280,8 +291,14 @@ export class MicrosoftGraphClient { query?: ODataQuery, queryMode?: 'basic' | 'advanced', path: string = 'users', + signal?: AbortSignal, ): AsyncIterable { - yield* this.requestCollection(path, query, queryMode); + yield* this.requestCollection( + path, + query, + queryMode, + signal, + ); } /** @@ -320,8 +337,14 @@ export class MicrosoftGraphClient { query?: ODataQuery, queryMode?: 'basic' | 'advanced', path: string = 'groups', + signal?: AbortSignal, ): AsyncIterable { - yield* this.requestCollection(path, query, queryMode); + yield* this.requestCollection( + path, + query, + queryMode, + signal, + ); } /** @@ -336,11 +359,13 @@ export class MicrosoftGraphClient { groupId: string, query?: ODataQuery, queryMode?: 'basic' | 'advanced', + signal?: AbortSignal, ): AsyncIterable { yield* this.requestCollection( `groups/${groupId}/members`, query, queryMode, + signal, ); } @@ -357,11 +382,13 @@ export class MicrosoftGraphClient { groupId: string, query?: ODataQuery, queryMode?: 'basic' | 'advanced', + signal?: AbortSignal, ): AsyncIterable { yield* this.requestCollection( `groups/${groupId}/members/microsoft.graph.user/`, query, queryMode, + signal, ); } @@ -374,8 +401,14 @@ export class MicrosoftGraphClient { */ async getOrganization( tenantId: string, + signal?: AbortSignal, ): Promise { - const response = await this.requestApi(`organization/${tenantId}`); + const response = await this.requestApi( + `organization/${tenantId}`, + undefined, + undefined, + signal, + ); if (response.status !== 200) { await this.handleError(`organization/${tenantId}`, response); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts index bd58881728..d4fe3783db 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts @@ -140,6 +140,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledWith( @@ -188,6 +189,7 @@ describe('read microsoft graph', () => { }, 'advanced', undefined, + undefined, ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledWith( @@ -232,6 +234,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledWith( @@ -280,6 +283,7 @@ describe('read microsoft graph', () => { }, undefined, '/users/x/y', + undefined, ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledWith( @@ -332,6 +336,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroupUserMembers).toHaveBeenCalledTimes(1); @@ -339,6 +344,7 @@ describe('read microsoft graph', () => { 'groupid', { top: 999 }, undefined, + undefined, ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); @@ -391,6 +397,7 @@ describe('read microsoft graph', () => { }, 'advanced', undefined, + undefined, ); expect(client.getGroupUserMembers).toHaveBeenCalledTimes(1); @@ -398,6 +405,7 @@ describe('read microsoft graph', () => { 'groupid', { top: 999 }, 'advanced', + undefined, ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); @@ -447,6 +455,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroupUserMembers).toHaveBeenCalledTimes(1); @@ -457,6 +466,7 @@ describe('read microsoft graph', () => { top: 999, }, undefined, + undefined, ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); @@ -509,6 +519,7 @@ describe('read microsoft graph', () => { }, undefined, '/groups/x/y', + undefined, ); }); }); @@ -545,7 +556,10 @@ describe('read microsoft graph', () => { ); expect(client.getOrganization).toHaveBeenCalledTimes(1); - expect(client.getOrganization).toHaveBeenCalledWith('tenantid'); + expect(client.getOrganization).toHaveBeenCalledWith( + 'tenantid', + undefined, + ); }); it('should read organization with custom transformer', async () => { @@ -563,7 +577,10 @@ describe('read microsoft graph', () => { expect(rootGroup).toEqual(undefined); expect(client.getOrganization).toHaveBeenCalledTimes(1); - expect(client.getOrganization).toHaveBeenCalledWith('tenantid'); + expect(client.getOrganization).toHaveBeenCalledWith( + 'tenantid', + undefined, + ); }); }); @@ -636,11 +653,17 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { - top: 999, - }); + expect(client.getGroupMembers).toHaveBeenCalledWith( + 'groupid', + { + top: 999, + }, + undefined, + undefined, + ); // TODO: Loading groups photos doesn't work right now as Microsoft Graph // doesn't allows this yet // expect(client.getGroupPhotoWithSizeLimit).toBeCalledTimes(1); @@ -716,11 +739,17 @@ describe('read microsoft graph', () => { }, 'advanced', undefined, + undefined, ); expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { - top: 999, - }); + expect(client.getGroupMembers).toHaveBeenCalledWith( + 'groupid', + { + top: 999, + }, + undefined, + undefined, + ); // TODO: Loading groups photos doesn't work right now as Microsoft Graph // doesn't allows this yet // expect(client.getGroupPhotoWithSizeLimit).toBeCalledTimes(1); @@ -797,11 +826,17 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { - top: 999, - }); + expect(client.getGroupMembers).toHaveBeenCalledWith( + 'groupid', + { + top: 999, + }, + undefined, + undefined, + ); // TODO: Loading groups photos doesn't work right now as Microsoft Graph // doesn't allows this yet // expect(client.getGroupPhotoWithSizeLimit).toBeCalledTimes(1); @@ -871,11 +906,17 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { - top: 999, - }); + expect(client.getGroupMembers).toHaveBeenCalledWith( + 'groupid', + { + top: 999, + }, + undefined, + undefined, + ); }); it('should read groups and their sub groups', async () => { @@ -976,14 +1017,25 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroupMembers).toHaveBeenCalledTimes(2); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { - top: 999, - }); - expect(client.getGroupMembers).toHaveBeenCalledWith('childgroupid', { - top: 999, - }); + expect(client.getGroupMembers).toHaveBeenCalledWith( + 'groupid', + { + top: 999, + }, + undefined, + undefined, + ); + expect(client.getGroupMembers).toHaveBeenCalledWith( + 'childgroupid', + { + top: 999, + }, + undefined, + undefined, + ); // TODO: Loading groups photos doesn't work right now as Microsoft Graph // doesn't allows this yet // expect(client.getGroupPhotoWithSizeLimit).toBeCalledTimes(1); @@ -1056,11 +1108,17 @@ describe('read microsoft graph', () => { }, undefined, '/groups/x/y', + undefined, ); expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { - top: 999, - }); + expect(client.getGroupMembers).toHaveBeenCalledWith( + 'groupid', + { + top: 999, + }, + undefined, + undefined, + ); }); }); @@ -1211,6 +1269,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroups).toHaveBeenCalledTimes(1); expect(client.getGroups).toHaveBeenCalledWith( @@ -1220,6 +1279,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); }); @@ -1253,6 +1313,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroups).toHaveBeenCalledTimes(1); expect(client.getGroups).toHaveBeenCalledWith( @@ -1262,6 +1323,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); }); @@ -1293,6 +1355,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroups).toHaveBeenCalledTimes(1); expect(client.getGroups).toHaveBeenCalledWith( @@ -1301,6 +1364,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); }); @@ -1331,6 +1395,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); }); @@ -1369,6 +1434,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroups).toHaveBeenNthCalledWith( 2, @@ -1378,6 +1444,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); }); @@ -1419,6 +1486,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroups).toHaveBeenCalledTimes(1); expect(client.getGroups).toHaveBeenCalledWith( @@ -1427,6 +1495,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); }); }); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts index 7202f70384..4a6c4163d1 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts @@ -53,6 +53,7 @@ export async function readMicrosoftGraphUsers( loadUserPhotos?: boolean; transformer?: UserTransformer; logger: LoggerService; + signal?: AbortSignal; }, ): Promise<{ users: UserEntity[]; // With all relations empty @@ -66,6 +67,7 @@ export async function readMicrosoftGraphUsers( }, options.queryMode, options.userPath, + options.signal, ); return { @@ -93,6 +95,7 @@ export async function readMicrosoftGraphUsersInGroups( groupExpand?: string; transformer?: UserTransformer; logger: LoggerService; + signal?: AbortSignal; }, ): Promise<{ users: UserEntity[]; // With all relations empty @@ -112,6 +115,7 @@ export async function readMicrosoftGraphUsersInGroups( }, options.queryMode, options.userGroupMemberPath, + options.signal, )) { // Process all groups in parallel, otherwise it can take quite some time userGroupMemberPromises.push( @@ -126,6 +130,7 @@ export async function readMicrosoftGraphUsersInGroups( top: PAGE_SIZE, }, options.queryMode, + options.signal, )) { userGroupMembers.set(user.id!, user); groupMemberCount++; @@ -161,12 +166,12 @@ export async function readMicrosoftGraphUsersInGroups( export async function readMicrosoftGraphOrganization( client: MicrosoftGraphClient, tenantId: string, - options?: { transformer?: OrganizationTransformer }, + options?: { transformer?: OrganizationTransformer; signal?: AbortSignal }, ): Promise<{ rootGroup?: GroupEntity; // With all relations empty }> { // For now we expect a single root organization - const organization = await client.getOrganization(tenantId); + const organization = await client.getOrganization(tenantId, options?.signal); const transformer = options?.transformer ?? defaultOrganizationTransformer; const rootGroup = await transformer(organization); @@ -186,6 +191,7 @@ export async function readMicrosoftGraphGroups( groupIncludeSubGroups?: boolean; groupTransformer?: GroupTransformer; organizationTransformer?: OrganizationTransformer; + signal?: AbortSignal; }, ): Promise<{ groups: GroupEntity[]; // With all relations empty @@ -200,6 +206,7 @@ export async function readMicrosoftGraphGroups( const { rootGroup } = await readMicrosoftGraphOrganization(client, tenantId, { transformer: options?.organizationTransformer, + signal: options?.signal, }); if (rootGroup) { groupMember.set(rootGroup.metadata.name, new Set()); @@ -219,6 +226,7 @@ export async function readMicrosoftGraphGroups( }, options?.queryMode, options?.groupPath, + options?.signal, )) { // Process all groups in parallel, otherwise it can take quite some time promises.push( @@ -238,9 +246,14 @@ export async function readMicrosoftGraphGroups( return; } - for await (const member of client.getGroupMembers(group.id!, { - top: PAGE_SIZE, - })) { + for await (const member of client.getGroupMembers( + group.id!, + { + top: PAGE_SIZE, + }, + undefined, + options?.signal, + )) { if (!member.id) { continue; } @@ -261,6 +274,8 @@ export async function readMicrosoftGraphGroups( for await (const subMember of client.getGroupMembers( member.id!, { top: PAGE_SIZE }, + undefined, + options?.signal, )) { if (!subMember.id) { continue; @@ -425,6 +440,7 @@ export async function readMicrosoftGraphOrg( groupTransformer?: GroupTransformer; organizationTransformer?: OrganizationTransformer; logger: LoggerService; + signal?: AbortSignal; }, ): Promise<{ users: UserEntity[]; groups: GroupEntity[] }> { let users: UserEntity[] = []; @@ -447,6 +463,7 @@ export async function readMicrosoftGraphOrg( loadUserPhotos: options.loadUserPhotos, transformer: options.userTransformer, logger: options.logger, + signal: options.signal, }, ); users = usersInGroups; @@ -460,6 +477,7 @@ export async function readMicrosoftGraphOrg( loadUserPhotos: options.loadUserPhotos, transformer: options.userTransformer, logger: options.logger, + signal: options.signal, }); users = usersWithFilter; } @@ -474,6 +492,7 @@ export async function readMicrosoftGraphOrg( groupIncludeSubGroups: options.groupIncludeSubGroups, groupTransformer: options.groupTransformer, organizationTransformer: options.organizationTransformer, + signal: options.signal, }); resolveRelations(rootGroup, groups, users, groupMember, groupMemberOf); diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts index 1d68f706bc..786790bb2f 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts @@ -255,6 +255,57 @@ describe('MicrosoftGraphOrgEntityProvider', () => { ); }); + it('should stop processing when AbortSignal is aborted', async () => { + jest.spyOn(logger, 'child').mockReturnValue(logger as any); + + const config = new ConfigReader({ + catalog: { + providers: { + microsoftGraphOrg: { + customProviderId: { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', + }, + }, + }, + }, + }); + const localTaskRunner = new PersistingTaskRunner(); + const provider = MicrosoftGraphOrgEntityProvider.fromConfig(config, { + logger, + schedule: localTaskRunner, + })[0]; + + await provider.connect(entityProviderConnection); + + const controller = new AbortController(); + controller.abort(); + + readMicrosoftGraphOrgMocked.mockImplementationOnce( + async (_client, _tenantId, options) => { + if (options.signal?.aborted) { + const error = new Error('The operation was aborted'); + error.name = 'AbortError'; + throw error; + } + return { users: [], groups: [] }; + }, + ); + + const taskDef = localTaskRunner.getTasks()[0]; + // Should resolve without throwing (abort is caught and logged at debug level) + await (taskDef.fn as (signal: AbortSignal) => Promise)( + controller.signal, + ); + + expect(entityProviderConnection.applyMutation).not.toHaveBeenCalled(); + expect(logger.debug).toHaveBeenCalledWith( + expect.stringContaining('refresh aborted due to shutdown'), + ); + }); + it('fail without schedule and scheduler', () => { const config = new ConfigReader({ catalog: { diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts index b223c18d6e..a6f2ee78d7 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts @@ -326,7 +326,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { * Runs one complete ingestion loop. Call this method regularly at some * appropriate cadence. */ - async read(options?: { logger?: LoggerService }) { + async read(options?: { logger?: LoggerService; signal?: AbortSignal }) { if (!this.connection) { throw new Error('Not initialized'); } @@ -361,6 +361,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { userTransformer: this.options.userTransformer, organizationTransformer: this.options.organizationTransformer, logger: logger, + signal: options?.signal, }, ); @@ -386,7 +387,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { const id = `${this.getProviderName()}:refresh`; await taskRunner.run({ id, - fn: async () => { + fn: async (abortSignal: AbortSignal) => { const logger = this.options.logger.child({ class: MicrosoftGraphOrgEntityProvider.prototype.constructor.name, taskId: id, @@ -394,8 +395,14 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { }); try { - await this.read({ logger }); + await this.read({ logger, signal: abortSignal }); } catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + logger.debug( + `${this.getProviderName()} refresh aborted due to shutdown`, + ); + return; + } logger.error( `${this.getProviderName()} refresh failed, ${error}`, error, diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index 48dae2d747..433d8c494c 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -191,6 +191,15 @@ export interface Config { stitchTimeout?: HumanDuration | string; }; + /** + * The strategy to use when there is a conflict with a location being registered. + * + * The default value is "reject". + * + * The "refresh" strategy will refresh the existing location instead of throwing a conflict error. + */ + defaultLocationConflictStrategy?: 'refresh' | 'reject'; + /** * The interval at which the catalog should process its entities. * @remarks diff --git a/plugins/catalog-backend/src/processing/TaskPipeline.test.ts b/plugins/catalog-backend/src/processing/TaskPipeline.test.ts index 43ec118bdb..02a7a2932c 100644 --- a/plugins/catalog-backend/src/processing/TaskPipeline.test.ts +++ b/plugins/catalog-backend/src/processing/TaskPipeline.test.ts @@ -121,8 +121,13 @@ describe('startTaskPipeline', () => { }); describe('createBarrier', () => { - const tick = (millis: number) => - new Promise(resolve => setTimeout(resolve, millis)); + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); it('abandons a wait after the timeout expires', async () => { const abortController = new AbortController(); @@ -132,10 +137,11 @@ describe('createBarrier', () => { const fn1 = jest.fn(); barrier.wait().then(fn1); - await tick(0); + await Promise.resolve(); expect(fn1).not.toHaveBeenCalled(); - await tick(50); + jest.advanceTimersByTime(50); + await Promise.resolve(); expect(fn1).not.toHaveBeenCalled(); // start a new wait mid-way through the timeout @@ -143,14 +149,16 @@ describe('createBarrier', () => { const fn2 = jest.fn(); barrier.wait().then(fn2); - await tick(0); + await Promise.resolve(); expect(fn2).not.toHaveBeenCalled(); - await tick(50); + jest.advanceTimersByTime(50); + await Promise.resolve(); expect(fn1).toHaveBeenCalledTimes(1); expect(fn2).not.toHaveBeenCalled(); - await tick(50); + jest.advanceTimersByTime(50); + await Promise.resolve(); expect(fn1).toHaveBeenCalledTimes(1); expect(fn2).toHaveBeenCalledTimes(1); }); @@ -164,16 +172,16 @@ describe('createBarrier', () => { barrier.wait().then(fn1); // should resolve immediately, not after timeout - await tick(0); + await Promise.resolve(); expect(fn1).not.toHaveBeenCalled(); abortController.abort(); - await tick(0); + await Promise.resolve(); expect(fn1).toHaveBeenCalledTimes(1); // subsequent waits should be immediate no matter what const fn2 = jest.fn(); barrier.wait().then(fn2); - await tick(0); + await Promise.resolve(); expect(fn2).toHaveBeenCalledTimes(1); }); @@ -185,7 +193,8 @@ describe('createBarrier', () => { const fn1 = jest.fn(); barrier.wait().then(fn1); - await tick(50); + jest.advanceTimersByTime(50); + await Promise.resolve(); expect(fn1).not.toHaveBeenCalled(); // start a new wait mid-way through the timeout @@ -193,13 +202,13 @@ describe('createBarrier', () => { const fn2 = jest.fn(); barrier.wait().then(fn2); - await tick(0); + await Promise.resolve(); expect(fn1).not.toHaveBeenCalled(); expect(fn2).not.toHaveBeenCalled(); barrier.release(); - await tick(0); + await Promise.resolve(); expect(fn1).toHaveBeenCalledTimes(1); expect(fn2).toHaveBeenCalledTimes(1); }); diff --git a/plugins/catalog-backend/src/providers/DefaultLocationStore.test.ts b/plugins/catalog-backend/src/providers/DefaultLocationStore.test.ts index e1bd8ec386..d1cd6e7c5e 100644 --- a/plugins/catalog-backend/src/providers/DefaultLocationStore.test.ts +++ b/plugins/catalog-backend/src/providers/DefaultLocationStore.test.ts @@ -15,7 +15,10 @@ */ import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils'; -import { ANNOTATION_ORIGIN_LOCATION } from '@backstage/catalog-model'; +import { + ANNOTATION_ORIGIN_LOCATION, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { v4 as uuid } from 'uuid'; import { applyDatabaseMigrations } from '../database/migrations'; import { @@ -25,6 +28,7 @@ import { DbSearchRow, } from '../database/tables'; import { DefaultLocationStore } from './DefaultLocationStore'; +import { locationSpecToLocationEntity } from '../util/conversion'; import { CatalogScmEventsServiceSubscriber } from '@backstage/plugin-catalog-node/alpha'; import waitFor from 'wait-for-expect'; @@ -154,6 +158,48 @@ describe('DefaultLocationStore', () => { }); }, ); + + it.each(databases.eachSupportedId())( + 'updates refresh_state when onConflict is refresh, %p', + async databaseId => { + const { store, knex } = await createLocationStore(databaseId); + const spec = { + type: 'url', + target: + 'https://github.com/backstage/demo/blob/master/catalog-info.yml', + }; + + // Create the location initially + await store.createLocation(spec); + + // Seed a refresh_state row for the corresponding Location entity + const entity = locationSpecToLocationEntity({ location: spec }); + const entityRef = stringifyEntityRef(entity); + const entityId = uuid(); + const oldDate = new Date('2020-01-01T00:00:00Z'); + await knex('refresh_state').insert({ + entity_id: entityId, + entity_ref: entityRef, + unprocessed_entity: '{}', + errors: '[]', + next_update_at: oldDate, + last_discovery_at: oldDate, + result_hash: 'old-hash', + }); + + // Re-register the same location with onConflict: 'refresh' + await store.createLocation(spec, { onConflict: 'refresh' }); + + // Verify that the refresh_state row was updated + const [row] = await knex('refresh_state').where({ + entity_ref: entityRef, + }); + expect(row.result_hash).toBe(''); + expect(new Date(row.next_update_at).getTime()).toBeGreaterThan( + oldDate.getTime(), + ); + }, + ); }); describe('deleteLocation', () => { diff --git a/plugins/catalog-backend/src/providers/DefaultLocationStore.ts b/plugins/catalog-backend/src/providers/DefaultLocationStore.ts index bfd4270696..8619d13459 100644 --- a/plugins/catalog-backend/src/providers/DefaultLocationStore.ts +++ b/plugins/catalog-backend/src/providers/DefaultLocationStore.ts @@ -68,16 +68,27 @@ export class DefaultLocationStore implements LocationStore, EntityProvider { return 'DefaultLocationStore'; } - async createLocation(input: LocationInput): Promise { + async createLocation( + input: LocationInput, + options?: { + onConflict?: 'refresh' | 'reject'; + }, + ): Promise { + let existed = false; + const location = await this.db.transaction(async tx => { // Attempt to find a previous location matching the input const previousLocations = await this.locations(tx); // TODO: when location id's are a compilation of input target we can remove this full // lookup of locations first and just grab the by that instead. - const previousLocation = previousLocations.some( + const previousLocation = previousLocations.find( l => input.type === l.type && input.target === l.target, ); if (previousLocation) { + if (options?.onConflict === 'refresh') { + existed = true; + return previousLocation; + } throw new ConflictError( `Location ${input.type}:${input.target} already exists`, ); @@ -93,6 +104,9 @@ export class DefaultLocationStore implements LocationStore, EntityProvider { return inner; }); + + // Always upsert the entity, even if the location already existed, to + // recover from cases where the entity was inadvertently deleted. const entity = locationSpecToLocationEntity({ location }); await this.connection.applyMutation({ type: 'delta', @@ -100,6 +114,18 @@ export class DefaultLocationStore implements LocationStore, EntityProvider { removed: [], }); + if (existed) { + // This is the "onConflict refresh" case, where a re-registration safely + // tries to recover from a bad state. + const entityRef = stringifyEntityRef(entity); + await this.db('refresh_state') + .where({ entity_ref: entityRef }) + .update({ + next_update_at: this.db.fn.now(), + result_hash: '', + }); + } + return location; } diff --git a/plugins/catalog-backend/src/schema/openapi.yaml b/plugins/catalog-backend/src/schema/openapi.yaml index 57d950b9f8..d0266e4d46 100644 --- a/plugins/catalog-backend/src/schema/openapi.yaml +++ b/plugins/catalog-backend/src/schema/openapi.yaml @@ -25,6 +25,9 @@ info: name: Apache-2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html contact: {} +tags: + - name: Entity + - name: Locations servers: - url: / components: @@ -1267,6 +1270,19 @@ paths: allowReserved: true schema: type: string + - in: query + name: onConflict + required: false + allowReserved: true + schema: + type: string + enum: + - refresh + - reject + description: >- + Behavior when the location already exists. 'reject' (default) + returns a 409 error, 'refresh' triggers a refresh of the + existing location entity and returns 201. requestBody: required: true content: diff --git a/plugins/catalog-backend/src/schema/openapi/generated/apis/Api.server.ts b/plugins/catalog-backend/src/schema/openapi/generated/apis/Api.server.ts index cbde6365ea..81a952a166 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/apis/Api.server.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/apis/Api.server.ts @@ -172,6 +172,7 @@ export type CreateLocation = { body: CreateLocationRequest; query: { dryRun?: string; + onConflict?: 'refresh' | 'reject'; }; response: CreateLocation201Response | Error | Error; }; diff --git a/plugins/catalog-backend/src/schema/openapi/generated/router.ts b/plugins/catalog-backend/src/schema/openapi/generated/router.ts index 56ee4cc036..6b92c7ae63 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/router.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/router.ts @@ -33,6 +33,14 @@ export const spec = { }, contact: {}, }, + tags: [ + { + name: 'Entity', + }, + { + name: 'Locations', + }, + ], servers: [ { url: '/', @@ -1490,6 +1498,18 @@ export const spec = { type: 'string', }, }, + { + in: 'query', + name: 'onConflict', + required: false, + allowReserved: true, + schema: { + type: 'string', + enum: ['refresh', 'reject'], + }, + description: + "Behavior when the location already exists. 'reject' (default) returns a 409 error, 'refresh' triggers a refresh of the existing location entity and returns 201.", + }, ], requestBody: { required: true, diff --git a/plugins/catalog-backend/src/service/AuthorizedLocationService.ts b/plugins/catalog-backend/src/service/AuthorizedLocationService.ts index 83f5118153..1b18d50324 100644 --- a/plugins/catalog-backend/src/service/AuthorizedLocationService.ts +++ b/plugins/catalog-backend/src/service/AuthorizedLocationService.ts @@ -46,6 +46,7 @@ export class AuthorizedLocationService implements LocationService { spec: LocationInput, dryRun: boolean, options: { + onConflict?: 'refresh' | 'reject'; credentials: BackstageCredentials; }, ): Promise<{ diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index d0f2442f0c..d3bf47874d 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -594,6 +594,10 @@ export class CatalogBuilder { const locationService = new AuthorizedLocationService( new DefaultLocationService(locationStore, orchestrator, { allowedLocationTypes: this.allowedLocationType, + defaultLocationConflictStrategy: + (config.getOptionalString( + 'catalog.defaultLocationConflictStrategy', + ) as 'refresh' | 'reject') || 'reject', }), permissionsService, ); diff --git a/plugins/catalog-backend/src/service/DefaultLocationService.test.ts b/plugins/catalog-backend/src/service/DefaultLocationService.test.ts index 822706ada4..3cce0efc3f 100644 --- a/plugins/catalog-backend/src/service/DefaultLocationService.test.ts +++ b/plugins/catalog-backend/src/service/DefaultLocationService.test.ts @@ -257,10 +257,13 @@ describe('DefaultLocationServiceTest', () => { type: 'url', }, }); - expect(store.createLocation).toHaveBeenCalledWith({ - target: 'https://backstage.io/catalog-info.yaml', - type: 'url', - }); + expect(store.createLocation).toHaveBeenCalledWith( + { + target: 'https://backstage.io/catalog-info.yaml', + type: 'url', + }, + expect.anything(), + ); }); it('should create location with unknown type if configuration allows it', async () => { @@ -279,6 +282,7 @@ describe('DefaultLocationServiceTest', () => { orchestrator, { allowedLocationTypes: ['url', 'unknown'], + defaultLocationConflictStrategy: 'reject', }, ); await expect( @@ -291,10 +295,13 @@ describe('DefaultLocationServiceTest', () => { type: 'unknown', }, }); - expect(store.createLocation).toHaveBeenCalledWith({ - target: 'https://backstage.io/catalog-info.yaml', - type: 'unknown', - }); + expect(store.createLocation).toHaveBeenCalledWith( + { + target: 'https://backstage.io/catalog-info.yaml', + type: 'unknown', + }, + expect.anything(), + ); }); it('should not allow locations of unknown types by default', async () => { @@ -309,6 +316,31 @@ describe('DefaultLocationServiceTest', () => { ).rejects.toThrow(InputError); }); + it('should pass onConflict through to store', async () => { + const locationSpec = { + type: 'url', + target: 'https://backstage.io/catalog-info.yaml', + }; + + store.createLocation.mockResolvedValueOnce({ + id: 'existing-id', + ...locationSpec, + }); + + const result = await locationService.createLocation(locationSpec, false, { + onConflict: 'refresh', + credentials: {} as any, + }); + + expect(result).toEqual({ + location: { id: 'existing-id', ...locationSpec }, + entities: [], + }); + expect(store.createLocation).toHaveBeenCalledWith(locationSpec, { + onConflict: 'refresh', + }); + }); + it('should return default InputError for failed processed entities in dryRun mode', async () => { store.listLocations.mockResolvedValueOnce([]); diff --git a/plugins/catalog-backend/src/service/DefaultLocationService.ts b/plugins/catalog-backend/src/service/DefaultLocationService.ts index ad3044ffd9..7effaa2e88 100644 --- a/plugins/catalog-backend/src/service/DefaultLocationService.ts +++ b/plugins/catalog-backend/src/service/DefaultLocationService.ts @@ -33,6 +33,7 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api'; export type DefaultLocationServiceOptions = { allowedLocationTypes: string[]; + defaultLocationConflictStrategy: 'refresh' | 'reject'; }; export class DefaultLocationService implements LocationService { @@ -45,6 +46,7 @@ export class DefaultLocationService implements LocationService { orchestrator: CatalogProcessingOrchestrator, options: DefaultLocationServiceOptions = { allowedLocationTypes: ['url'], + defaultLocationConflictStrategy: 'reject', }, ) { this.store = store; @@ -55,6 +57,10 @@ export class DefaultLocationService implements LocationService { async createLocation( input: LocationInput, dryRun: boolean, + options?: { + onConflict?: 'refresh' | 'reject'; + credentials?: BackstageCredentials; + }, ): Promise<{ location: Location; entities: Entity[]; exists?: boolean }> { if (!this.options.allowedLocationTypes.includes(input.type)) { throw new InputError( @@ -66,7 +72,10 @@ export class DefaultLocationService implements LocationService { if (dryRun) { return this.dryRunCreateLocation(input); } - const location = await this.store.createLocation(input); + const location = await this.store.createLocation(input, { + onConflict: + options?.onConflict ?? this.options.defaultLocationConflictStrategy, + }); return { location, entities: [] }; } diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 1984f2f04f..88cd5abde3 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -1685,7 +1685,10 @@ describe('POST /locations/by-query works end to end', () => { const locationService = new DefaultLocationService( store, { process: jest.fn() }, - { allowedLocationTypes: ['url'] }, + { + allowedLocationTypes: ['url'], + defaultLocationConflictStrategy: 'reject', + }, ); const router = await createRouter({ diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index 1a7f1bf6fc..89377c9e12 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -606,6 +606,7 @@ export async function createRouter( .post('/locations', async (req, res) => { const location = await validateRequestBody(req, locationInput); const dryRun = yn(req.query.dryRun, { default: false }); + const onConflict = req.query.onConflict; const auditorEvent = await auditor.createEvent({ eventId: 'location-mutate', @@ -629,6 +630,7 @@ export async function createRouter( location, dryRun, { + onConflict, credentials: await httpAuth.credentials(req), }, ); diff --git a/plugins/catalog-backend/src/service/types.ts b/plugins/catalog-backend/src/service/types.ts index f452484b72..c949da1eda 100644 --- a/plugins/catalog-backend/src/service/types.ts +++ b/plugins/catalog-backend/src/service/types.ts @@ -35,6 +35,7 @@ export interface LocationService { location: LocationInput, dryRun: boolean, options: { + onConflict?: 'refresh' | 'reject'; credentials: BackstageCredentials; }, ): Promise<{ location: Location; entities: Entity[]; exists?: boolean }>; @@ -84,7 +85,12 @@ export interface RefreshService { * Interacts with the database to manage locations. */ export interface LocationStore { - createLocation(location: LocationInput): Promise; + createLocation( + location: LocationInput, + options?: { + onConflict: 'refresh' | 'reject'; + }, + ): Promise; listLocations(): Promise; queryLocations(options: { limit: number; diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index dbb4c69899..c2c1b36885 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -56,9 +56,11 @@ "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/types": "workspace:^", + "@backstage/ui": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", + "@remixicon/react": "^4.6.0", "classnames": "^2.3.1", "lodash": "^4.17.15", "p-limit": "^3.1.0", diff --git a/plugins/catalog-graph/report-alpha.api.md b/plugins/catalog-graph/report-alpha.api.md index f11aa9a21b..f9d4542625 100644 --- a/plugins/catalog-graph/report-alpha.api.md +++ b/plugins/catalog-graph/report-alpha.api.md @@ -238,7 +238,6 @@ const _default: OverridableFrontendPlugin< kind: 'page'; name: undefined; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; diff --git a/plugins/catalog-graph/report.api.md b/plugins/catalog-graph/report.api.md index 8d3b8f2cc8..74b9713cf2 100644 --- a/plugins/catalog-graph/report.api.md +++ b/plugins/catalog-graph/report.api.md @@ -9,7 +9,6 @@ import { CompoundEntityRef } from '@backstage/catalog-model'; import { DependencyGraphTypes } from '@backstage/core-components'; import { Entity } from '@backstage/catalog-model'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; -import { InfoCardVariants } from '@backstage/core-components'; import { JsonObject } from '@backstage/types'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { MouseEvent as MouseEvent_2 } from 'react'; @@ -138,7 +137,6 @@ export namespace Direction { // @public export const EntityCatalogGraphCard: ( props: Partial & { - variant?: InfoCardVariants; height?: number; title?: string; action?: ReactNode; diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx index b08404f63b..20b4074f79 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx @@ -19,13 +19,15 @@ import { parseEntityRef, stringifyEntityRef, } from '@backstage/catalog-model'; -import { InfoCard, InfoCardVariants } from '@backstage/core-components'; import { useAnalytics, useRouteRef } from '@backstage/core-plugin-api'; import { + EntityInfoCard, humanizeEntityRef, useEntity, entityRouteRef, } from '@backstage/plugin-catalog-react'; +import { ButtonLink } from '@backstage/ui'; +import { RiArrowRightLine } from '@remixicon/react'; import { makeStyles, Theme } from '@material-ui/core/styles'; import qs from 'qs'; import { MouseEvent, ReactNode, useCallback, useMemo } from 'react'; @@ -41,23 +43,13 @@ import { Direction, EntityNode } from '../../lib/types'; import classNames from 'classnames'; /** @public */ -export type CatalogGraphCardClassKey = 'card' | 'graph'; +export type CatalogGraphCardClassKey = 'graph'; const useStyles = makeStyles( { - card: ({ height }) => ({ - display: 'flex', - flexDirection: 'column', - ...(height && { - height, - maxHeight: height, - minHeight: height, - }), - }), graph: ({ height }) => ({ - flex: height ? '0 0 auto' : 1, + height: height ?? '100%', minHeight: 0, - ...(height && { height }), }), }, { name: 'PluginCatalogGraphCatalogGraphCard' }, @@ -65,7 +57,6 @@ const useStyles = makeStyles( export const CatalogGraphCard = ( props: Partial & { - variant?: InfoCardVariants; height?: number; title?: string; action?: ReactNode; @@ -73,7 +64,6 @@ export const CatalogGraphCard = ( ) => { const { t } = useTranslationRef(catalogGraphTranslationRef); const { - variant = 'gridItem', relationPairs, maxDepth = 1, unidirectional = true, @@ -132,16 +122,18 @@ export const CatalogGraphCard = ( const catalogGraphUrl = `${catalogGraphRoute()}${catalogGraphParams}`; return ( - } + variant="tertiary" + href={catalogGraphUrl} + > + {t('catalogGraphCard.deepLinkTitle')} + + } > - + ); }; diff --git a/plugins/catalog-import/report-alpha.api.md b/plugins/catalog-import/report-alpha.api.md index ba841a7f70..b58b641050 100644 --- a/plugins/catalog-import/report-alpha.api.md +++ b/plugins/catalog-import/report-alpha.api.md @@ -181,7 +181,6 @@ const _default: OverridableFrontendPlugin< >; }; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; diff --git a/plugins/catalog-react/report.api.md b/plugins/catalog-react/report.api.md index 6a930671c2..3ae36ce941 100644 --- a/plugins/catalog-react/report.api.md +++ b/plugins/catalog-react/report.api.md @@ -837,6 +837,11 @@ export function useEntityPresentation( }, ): EntityRefPresentationSnapshot; +// @public +export function useEntityRefLink(): ( + entityRef: Entity | CompoundEntityRef | string, +) => string; + // @public export function useEntityTypeFilter(): { loading: boolean; diff --git a/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx index a0c7c20ffe..54db900fa4 100644 --- a/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx +++ b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx @@ -30,6 +30,10 @@ const useStyles = makeStyles({ root: { height: '100%', }, + footer: { + display: 'flex', + justifyContent: 'flex-end', + }, }); /** @public */ @@ -63,7 +67,9 @@ export function EntityInfoCard(props: EntityInfoCardProps) { )} {children} - {footerActions && {footerActions}} + {footerActions && ( + {footerActions} + )} ); } diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx index 40c738a66e..8af051db8c 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx @@ -17,7 +17,7 @@ import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; import { Link, LinkProps } from '@backstage/core-components'; import { useRouteRef } from '@backstage/core-plugin-api'; -import { ReactNode, forwardRef } from 'react'; +import { ReactNode, forwardRef, useCallback } from 'react'; import { entityRouteParams, entityRouteRef } from '../../routes'; import { EntityDisplayName } from '../EntityDisplayName'; @@ -54,7 +54,7 @@ export const EntityRefLink = forwardRef( disableTooltip, ...linkProps } = props; - const entityRoute = useEntityRoute(props.entityRef); + const entityLink = useEntityRefLink(); const content = children ?? title ?? ( ( ); return ( - + {content} ); }, ) as (props: EntityRefLinkProps) => JSX.Element; -// Hook that computes the route to a given entity / ref. This is a bit -// contrived, because it tries to retain the casing of the entity name if -// present, but not of other parts. This is in an attempt to make slightly more -// nice-looking URLs. -function useEntityRoute( +/** + * Returns a function that generates a route path to the given entity. + * + * @public + */ +export function useEntityRefLink(): ( entityRef: Entity | CompoundEntityRef | string, -): string { +) => string { const entityRoute = useRouteRef(entityRouteRef); - const routeParams = entityRouteParams(entityRef, { encodeParams: true }); - - return entityRoute(routeParams); + return useCallback( + (entityRef: Entity | CompoundEntityRef | string) => { + const routeParams = entityRouteParams(entityRef, { encodeParams: true }); + return entityRoute(routeParams); + }, + [entityRoute], + ); } diff --git a/plugins/catalog-react/src/components/EntityRefLink/index.ts b/plugins/catalog-react/src/components/EntityRefLink/index.ts index 50ac3d4534..82773da141 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/index.ts +++ b/plugins/catalog-react/src/components/EntityRefLink/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -export { EntityRefLink } from './EntityRefLink'; +export { EntityRefLink, useEntityRefLink } from './EntityRefLink'; export type { EntityRefLinkProps } from './EntityRefLink'; export { EntityRefLinks } from './EntityRefLinks'; export type { EntityRefLinksProps } from './EntityRefLinks'; diff --git a/plugins/catalog-unprocessed-entities/report-alpha.api.md b/plugins/catalog-unprocessed-entities/report-alpha.api.md index 30b115f07e..7408831f87 100644 --- a/plugins/catalog-unprocessed-entities/report-alpha.api.md +++ b/plugins/catalog-unprocessed-entities/report-alpha.api.md @@ -130,7 +130,6 @@ const _default: OverridableFrontendPlugin< >; }; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; diff --git a/plugins/catalog/report-alpha.api.md b/plugins/catalog/report-alpha.api.md index fbe90f461c..a0b092a453 100644 --- a/plugins/catalog/report-alpha.api.md +++ b/plugins/catalog/report-alpha.api.md @@ -1082,7 +1082,6 @@ const _default: OverridableFrontendPlugin< kind: 'page'; name: undefined; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; @@ -1270,7 +1269,6 @@ const _default: OverridableFrontendPlugin< kind: 'page'; name: 'entity'; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; diff --git a/plugins/devtools/report-alpha.api.md b/plugins/devtools/report-alpha.api.md index 6c3cfab581..753875eaaa 100644 --- a/plugins/devtools/report-alpha.api.md +++ b/plugins/devtools/report-alpha.api.md @@ -146,7 +146,6 @@ const _default: OverridableFrontendPlugin< kind: 'page'; name: undefined; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; diff --git a/plugins/home/report-alpha.api.md b/plugins/home/report-alpha.api.md index 48f7853cd7..0269f471fe 100644 --- a/plugins/home/report-alpha.api.md +++ b/plugins/home/report-alpha.api.md @@ -194,7 +194,6 @@ const _default: OverridableFrontendPlugin< kind: 'page'; name: undefined; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; diff --git a/plugins/kubernetes/report-alpha.api.md b/plugins/kubernetes/report-alpha.api.md index 86b824b2f4..7086976724 100644 --- a/plugins/kubernetes/report-alpha.api.md +++ b/plugins/kubernetes/report-alpha.api.md @@ -228,7 +228,6 @@ const _default: OverridableFrontendPlugin< >; }; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; diff --git a/plugins/mcp-actions-backend/README.md b/plugins/mcp-actions-backend/README.md index 095aa46122..7793a9f5f9 100644 --- a/plugins/mcp-actions-backend/README.md +++ b/plugins/mcp-actions-backend/README.md @@ -71,6 +71,64 @@ export const myPlugin = createBackendPlugin({ }); ``` +### Namespaced Tool Names + +By default, MCP tool names include the plugin ID prefix to avoid collisions across plugins. For example, an action registered as `greet-user` by `my-custom-plugin` is exposed as `my-custom-plugin:greet-user`. + +You can disable this if you need the short names for backward compatibility: + +```yaml +mcpActions: + namespacedToolNames: false +``` + +### Multiple MCP Servers + +By default, the plugin serves a single MCP server at `/api/mcp-actions/v1` that exposes all available actions. You can split actions into multiple focused servers by configuring `mcpActions.servers`, where each key becomes a separate MCP server endpoint. + +```yaml +mcpActions: + servers: + catalog: + name: 'Backstage Catalog' + description: 'Tools for interacting with the software catalog' + filter: + include: + - id: 'catalog:*' + scaffolder: + name: 'Backstage Scaffolder' + description: 'Tools for creating new software from templates' + filter: + include: + - id: 'scaffolder:*' +``` + +This creates two MCP server endpoints: + +- `http://localhost:7007/api/mcp-actions/v1/catalog` +- `http://localhost:7007/api/mcp-actions/v1/scaffolder` + +Each server uses include filter rules with glob patterns on action IDs to control which actions are exposed. For example, `id: 'catalog:*'` matches all actions registered by the catalog plugin. + +When `mcpActions.servers` is not configured, the plugin behaves exactly as before with a single server at `/api/mcp-actions/v1`. + +#### Filter Rules + +Include and exclude filter rules support glob patterns on action IDs and attribute matching. Exclude rules take precedence over include rules. When include rules are specified, actions must match at least one include rule to be exposed. + +```yaml +mcpActions: + servers: + catalog: + name: 'Backstage Catalog' + filter: + include: + - id: 'catalog:*' + exclude: + - attributes: + destructive: true +``` + ### Error Handling When errors are thrown from MCP actions, the backend will handle and surface error message for any error from `@backstage/errors`. Unknown errors will be handled by `@modelcontextprotocol/sdk`'s default error handling, which may result in a generic `500 Server Error` being returned. As a result, we recommend using errors from `@backstage/errors` when applicable. @@ -159,16 +217,15 @@ The MCP server supports both Server-Sent Events (SSE) and Streamable HTTP protoc The SSE protocol is deprecated, and should be avoided as it will be removed in a future release. +### Single Server (default) + - `Streamable HTTP`: `http://localhost:7007/api/mcp-actions/v1` - `SSE`: `http://localhost:7007/api/mcp-actions/v1/sse` -There's a few different ways to configure MCP tools, but here's a snippet of the most common. - ```json { "mcpServers": { "backstage-actions": { - // you can also replace this with the public / internal URL of the deployed backend. "url": "http://localhost:7007/api/mcp-actions/v1", "headers": { "Authorization": "Bearer ${MCP_TOKEN}" @@ -178,6 +235,32 @@ There's a few different ways to configure MCP tools, but here's a snippet of the } ``` +### Multiple Servers + +When `mcpActions.servers` is configured, each server key becomes part of the URL. For example, with servers named `catalog` and `scaffolder`: + +- `http://localhost:7007/api/mcp-actions/v1/catalog` +- `http://localhost:7007/api/mcp-actions/v1/scaffolder` + +```json +{ + "mcpServers": { + "backstage-catalog": { + "url": "http://localhost:7007/api/mcp-actions/v1/catalog", + "headers": { + "Authorization": "Bearer ${MCP_TOKEN}" + } + }, + "backstage-scaffolder": { + "url": "http://localhost:7007/api/mcp-actions/v1/scaffolder", + "headers": { + "Authorization": "Bearer ${MCP_TOKEN}" + } + } + } +} +``` + ## Metrics The MCP Actions Backend emits metrics for the following operations: diff --git a/plugins/mcp-actions-backend/config.d.ts b/plugins/mcp-actions-backend/config.d.ts new file mode 100644 index 0000000000..a535f4c511 --- /dev/null +++ b/plugins/mcp-actions-backend/config.d.ts @@ -0,0 +1,63 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + mcpActions?: { + /** + * When true, MCP tool names include the plugin ID prefix to avoid + * collisions across plugins. For example an action registered as + * "get-entity" by the catalog plugin becomes "catalog:get-entity". + * Defaults to true. + */ + namespacedToolNames?: boolean; + + /** + * Named MCP servers, each exposed at /api/mcp-actions/v1/{key}. + * When not configured, the plugin serves a single server at /api/mcp-actions/v1. + */ + servers?: { + [serverKey: string]: { + /** Display name for the MCP server. */ + name: string; + /** Description of the MCP server. */ + description?: string; + /** Filter rules to include or exclude specific actions. */ + filter?: { + include?: Array<{ + /** Glob pattern matched against action ID. */ + id?: string; + /** Match actions by their attribute flags. */ + attributes?: { + destructive?: boolean; + readOnly?: boolean; + idempotent?: boolean; + }; + }>; + exclude?: Array<{ + /** Glob pattern matched against action ID. */ + id?: string; + /** Match actions by their attribute flags. */ + attributes?: { + destructive?: boolean; + readOnly?: boolean; + idempotent?: boolean; + }; + }>; + }; + }; + }; + }; +} diff --git a/plugins/mcp-actions-backend/package.json b/plugins/mcp-actions-backend/package.json index dca19fd279..b858a76a9d 100644 --- a/plugins/mcp-actions-backend/package.json +++ b/plugins/mcp-actions-backend/package.json @@ -21,8 +21,10 @@ "license": "Apache-2.0", "main": "src/index.ts", "types": "src/index.ts", + "configSchema": "config.d.ts", "files": [ - "dist" + "dist", + "config.d.ts" ], "scripts": { "build": "backstage-cli package build", @@ -36,6 +38,7 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-client": "workspace:^", + "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", @@ -43,6 +46,7 @@ "@modelcontextprotocol/sdk": "^1.25.2", "express": "^4.22.0", "express-promise-router": "^4.1.0", + "minimatch": "^10.2.1", "zod": "^3.25.76" }, "devDependencies": { diff --git a/plugins/mcp-actions-backend/src/config.ts b/plugins/mcp-actions-backend/src/config.ts new file mode 100644 index 0000000000..5ff2a442bb --- /dev/null +++ b/plugins/mcp-actions-backend/src/config.ts @@ -0,0 +1,97 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; +import { Minimatch } from 'minimatch'; + +export type FilterRule = { + idMatcher?: Minimatch; + attributes?: Partial< + Record<'destructive' | 'readOnly' | 'idempotent', boolean> + >; +}; + +export type McpServerConfig = { + name: string; + description?: string; + includeRules: FilterRule[]; + excludeRules: FilterRule[]; +}; + +const SERVER_KEY_PATTERN = /^[a-z0-9][a-z0-9-]*$/; + +export function parseFilterRules(configArray: Config[]): FilterRule[] { + return configArray.map(ruleConfig => { + const idPattern = ruleConfig.getOptionalString('id'); + const attributesConfig = ruleConfig.getOptionalConfig('attributes'); + + const rule: FilterRule = {}; + + if (idPattern) { + rule.idMatcher = new Minimatch(idPattern); + } + + if (attributesConfig) { + rule.attributes = {}; + for (const key of ['destructive', 'readOnly', 'idempotent'] as const) { + const value = attributesConfig.getOptionalBoolean(key); + if (value !== undefined) { + rule.attributes[key] = value; + } + } + } + + return rule; + }); +} + +export function parseServerConfigs( + config: Config, +): Map | undefined { + const serversConfig = config.getOptionalConfig('mcpActions.servers'); + if (!serversConfig) { + return undefined; + } + + const servers = new Map(); + + for (const key of serversConfig.keys()) { + if (!SERVER_KEY_PATTERN.test(key)) { + throw new Error( + `Invalid MCP server key "${key}": must be lowercase alphanumeric with hyphens`, + ); + } + + const serverConfig = serversConfig.getConfig(key); + + const filterConfig = serverConfig.getOptionalConfig('filter'); + const includeRules = parseFilterRules( + filterConfig?.getOptionalConfigArray('include') ?? [], + ); + const excludeRules = parseFilterRules( + filterConfig?.getOptionalConfigArray('exclude') ?? [], + ); + + servers.set(key, { + name: serverConfig.getString('name'), + description: serverConfig.getOptionalString('description'), + includeRules, + excludeRules, + }); + } + + return servers; +} diff --git a/plugins/mcp-actions-backend/src/plugin.test.ts b/plugins/mcp-actions-backend/src/plugin.test.ts index 1e3297792a..ceae509f9f 100644 --- a/plugins/mcp-actions-backend/src/plugin.test.ts +++ b/plugins/mcp-actions-backend/src/plugin.test.ts @@ -118,7 +118,7 @@ describe('Mcp Backend', () => { required: ['name'], type: 'object', }, - name: 'make-greeting', + name: 'local:make-greeting', }, ]); }); @@ -161,11 +161,127 @@ describe('Mcp Backend', () => { required: ['name'], type: 'object', }, - name: 'make-greeting', + name: 'local:make-greeting', }, ]); }); + describe('multi-server routing', () => { + const mockCatalogPlugin = createBackendPlugin({ + pluginId: 'catalog-actions', + register({ registerInit }) { + registerInit({ + deps: { actionsRegistry: actionsRegistryServiceRef }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'get-entity', + title: 'Get Entity', + description: 'Fetch an entity', + schema: { + input: z => z.object({ name: z.string() }), + output: z => z.object({ entity: z.string() }), + }, + action: async ({ input }) => ({ + output: { entity: input.name }, + }), + }); + }, + }); + }, + }); + + const mockScaffolderPlugin = createBackendPlugin({ + pluginId: 'scaffolder-actions', + register({ registerInit }) { + registerInit({ + deps: { actionsRegistry: actionsRegistryServiceRef }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'create-app', + title: 'Create App', + description: 'Create an app from template', + schema: { + input: z => z.object({ template: z.string() }), + output: z => z.object({ name: z.string() }), + }, + action: async ({ input }) => ({ + output: { name: input.template }, + }), + }); + }, + }); + }, + }); + + it('should route to per-server endpoints when mcpActions.servers is configured', async () => { + const { server } = await startTestBackend({ + features: [ + mcpPlugin, + mockCatalogPlugin, + mockScaffolderPlugin, + metricsServiceMock.mock().factory, + mockServices.rootConfig.factory({ + data: { + backend: { + actions: { + pluginSources: ['catalog-actions', 'scaffolder-actions'], + }, + }, + mcpActions: { + servers: { + catalog: { + name: 'Catalog Server', + filter: { + include: [{ id: 'catalog-actions:*' }], + }, + }, + scaffolder: { + name: 'Scaffolder Server', + filter: { + include: [{ id: 'scaffolder-actions:*' }], + }, + }, + }, + }, + }, + }), + ], + }); + + const address = server.address(); + if (typeof address !== 'object' || !('port' in address!)) { + throw new Error('server broke'); + } + const serverAddress = `http://localhost:${address.port}`; + + const catalogClient = new Client({ name: 'test', version: '1.0' }); + const catalogTransport = new StreamableHTTPClientTransport( + new URL(`${serverAddress}/api/mcp-actions/v1/catalog`), + ); + await catalogClient.connect(catalogTransport); + const catalogResult = await catalogClient.request( + { method: 'tools/list' }, + ListToolsResultSchema, + ); + expect(catalogResult.tools).toHaveLength(1); + expect(catalogResult.tools[0].name).toBe('catalog-actions:get-entity'); + + const scaffolderClient = new Client({ name: 'test', version: '1.0' }); + const scaffolderTransport = new StreamableHTTPClientTransport( + new URL(`${serverAddress}/api/mcp-actions/v1/scaffolder`), + ); + await scaffolderClient.connect(scaffolderTransport); + const scaffolderResult = await scaffolderClient.request( + { method: 'tools/list' }, + ListToolsResultSchema, + ); + expect(scaffolderResult.tools).toHaveLength(1); + expect(scaffolderResult.tools[0].name).toBe( + 'scaffolder-actions:create-app', + ); + }); + }); + describe('OAuth well-known endpoints', () => { it('should not expose oauth endpoints when neither DCR nor CIMD is enabled', async () => { const { server } = await startTestBackend({ diff --git a/plugins/mcp-actions-backend/src/plugin.ts b/plugins/mcp-actions-backend/src/plugin.ts index 71a40db18d..08f9db350f 100644 --- a/plugins/mcp-actions-backend/src/plugin.ts +++ b/plugins/mcp-actions-backend/src/plugin.ts @@ -27,6 +27,7 @@ import { actionsServiceRef, metricsServiceRef, } from '@backstage/backend-plugin-api/alpha'; +import { parseServerConfigs } from './config'; /** * mcpPlugin backend plugin @@ -59,28 +60,48 @@ export const mcpPlugin = createBackendPlugin({ config, metrics, }) { + const serverConfigs = parseServerConfigs(config); + const namespacedToolNames = config.getOptionalBoolean( + 'mcpActions.namespacedToolNames', + ); + const mcpService = await McpService.create({ actions, metrics, - }); - - const sseRouter = createSseRouter({ - mcpService, - httpAuth, - }); - - const streamableRouter = createStreamableRouter({ - mcpService, - httpAuth, - logger, - metrics, + namespacedToolNames, }); const router = Router(); router.use(json()); - router.use('/v1/sse', sseRouter); - router.use('/v1', streamableRouter); + if (serverConfigs && serverConfigs.size > 0) { + for (const [key, serverConfig] of serverConfigs) { + const streamableRouter = createStreamableRouter({ + mcpService, + httpAuth, + logger, + metrics, + serverConfig, + }); + + router.use(`/v1/${key}`, streamableRouter); + } + } else { + const sseRouter = createSseRouter({ + mcpService, + httpAuth, + }); + + const streamableRouter = createStreamableRouter({ + mcpService, + httpAuth, + logger, + metrics, + }); + + router.use('/v1/sse', sseRouter); + router.use('/v1', streamableRouter); + } httpRouter.use(router); diff --git a/plugins/mcp-actions-backend/src/routers/createSseRouter.ts b/plugins/mcp-actions-backend/src/routers/createSseRouter.ts index 19bcf288e9..954d28af4e 100644 --- a/plugins/mcp-actions-backend/src/routers/createSseRouter.ts +++ b/plugins/mcp-actions-backend/src/routers/createSseRouter.ts @@ -18,6 +18,7 @@ import { Router } from 'express'; import { McpService } from '../services/McpService'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { HttpAuthService } from '@backstage/backend-plugin-api'; +import { McpServerConfig } from '../config'; /** * Legacy SSE endpoint for older clients, hopefully will not be needed for much longer. @@ -25,9 +26,11 @@ import { HttpAuthService } from '@backstage/backend-plugin-api'; export const createSseRouter = ({ mcpService, httpAuth, + serverConfig, }: { mcpService: McpService; httpAuth: HttpAuthService; + serverConfig?: McpServerConfig; }): Router => { const router = PromiseRouter(); const transportsToSessionId = new Map(); @@ -35,6 +38,7 @@ export const createSseRouter = ({ router.get('/', async (req, res) => { const server = mcpService.getServer({ credentials: await httpAuth.credentials(req), + serverConfig, }); const transport = new SSEServerTransport( diff --git a/plugins/mcp-actions-backend/src/routers/createStreamableRouter.ts b/plugins/mcp-actions-backend/src/routers/createStreamableRouter.ts index b08605337d..0225bbb98d 100644 --- a/plugins/mcp-actions-backend/src/routers/createStreamableRouter.ts +++ b/plugins/mcp-actions-backend/src/routers/createStreamableRouter.ts @@ -23,17 +23,20 @@ import { HttpAuthService, LoggerService } from '@backstage/backend-plugin-api'; import { isError } from '@backstage/errors'; import { MetricsService } from '@backstage/backend-plugin-api/alpha'; import { bucketBoundaries, McpServerSessionAttributes } from '../metrics'; +import { McpServerConfig } from '../config'; export const createStreamableRouter = ({ mcpService, httpAuth, logger, metrics, + serverConfig, }: { mcpService: McpService; logger: LoggerService; httpAuth: HttpAuthService; metrics: MetricsService; + serverConfig?: McpServerConfig; }): Router => { const router = PromiseRouter(); @@ -59,6 +62,7 @@ export const createStreamableRouter = ({ try { const server = mcpService.getServer({ credentials: await httpAuth.credentials(req), + serverConfig, }); const transport = new StreamableHTTPServerTransport({ diff --git a/plugins/mcp-actions-backend/src/services/McpService.test.ts b/plugins/mcp-actions-backend/src/services/McpService.test.ts index c75572bf20..8a6656b91c 100644 --- a/plugins/mcp-actions-backend/src/services/McpService.test.ts +++ b/plugins/mcp-actions-backend/src/services/McpService.test.ts @@ -27,6 +27,9 @@ import { ListToolsResultSchema, } from '@modelcontextprotocol/sdk/types.js'; import { InputError, NotFoundError } from '@backstage/errors'; +import { McpServerConfig, parseFilterRules } from '../config'; +import { ActionsService } from '@backstage/backend-plugin-api/alpha'; +import { ConfigReader } from '@backstage/config'; describe('McpService', () => { it('should list the available actions as tools in the mcp backend', async () => { @@ -93,7 +96,7 @@ describe('McpService', () => { required: ['input'], type: 'object', }, - name: 'mock-action', + name: 'test:mock-action', }, ]); @@ -193,7 +196,7 @@ describe('McpService', () => { const result = await client.request( { method: 'tools/call', - params: { name: 'mock-action', arguments: { input: 'test' } }, + params: { name: 'test:mock-action', arguments: { input: 'test' } }, }, CallToolResultSchema, ); @@ -223,7 +226,7 @@ describe('McpService', () => { expect.any(Number), expect.objectContaining({ 'mcp.method.name': 'tools/call', - 'gen_ai.tool.name': 'mock-action', + 'gen_ai.tool.name': 'test:mock-action', 'gen_ai.operation.name': 'execute_tool', }), ); @@ -257,14 +260,14 @@ describe('McpService', () => { const result = await client.request( { method: 'tools/call', - params: { name: 'mock-action', arguments: { input: 'test' } }, + params: { name: 'nonexistent-action', arguments: { input: 'test' } }, }, CallToolResultSchema, ); await expect(result).toEqual({ content: [ { - text: expect.stringMatching('Action "mock-action" not found'), + text: expect.stringMatching('Action "nonexistent-action" not found'), type: 'text', }, ], @@ -277,7 +280,7 @@ describe('McpService', () => { expect.any(Number), expect.objectContaining({ 'mcp.method.name': 'tools/call', - 'gen_ai.tool.name': 'mock-action', + 'gen_ai.tool.name': 'nonexistent-action', 'gen_ai.operation.name': 'execute_tool', 'error.type': 'tool_error', }), @@ -326,7 +329,7 @@ describe('McpService', () => { client.request( { method: 'tools/call', - params: { name: 'failing-action', arguments: {} }, + params: { name: 'test:failing-action', arguments: {} }, }, CallToolResultSchema, ), @@ -338,7 +341,7 @@ describe('McpService', () => { expect.any(Number), expect.objectContaining({ 'mcp.method.name': 'tools/call', - 'gen_ai.tool.name': 'failing-action', + 'gen_ai.tool.name': 'test:failing-action', 'gen_ai.operation.name': 'execute_tool', 'error.type': 'CustomError', }), @@ -385,7 +388,7 @@ describe('McpService', () => { const result = await client.request( { method: 'tools/call', - params: { name: 'failing-action', arguments: { value: 'test' } }, + params: { name: 'test:failing-action', arguments: { value: 'test' } }, }, CallToolResultSchema, ); @@ -441,7 +444,7 @@ describe('McpService', () => { const result = await client.request( { method: 'tools/call', - params: { name: 'not-found-action', arguments: { id: 'abc' } }, + params: { name: 'test:not-found-action', arguments: { id: 'abc' } }, }, CallToolResultSchema, ); @@ -456,4 +459,367 @@ describe('McpService', () => { isError: true, }); }); + + describe('per-server filtering', () => { + const fakeActions = [ + { + id: 'catalog:get-entity', + name: 'get-entity', + title: 'Get Entity', + description: 'Fetch an entity', + schema: { + input: { type: 'object' as const }, + output: { type: 'object' as const }, + }, + attributes: { destructive: false, readOnly: true, idempotent: true }, + }, + { + id: 'catalog:delete-entity', + name: 'delete-entity', + title: 'Delete Entity', + description: 'Delete an entity', + schema: { + input: { type: 'object' as const }, + output: { type: 'object' as const }, + }, + attributes: { destructive: true, readOnly: false, idempotent: false }, + }, + { + id: 'scaffolder:create-app', + name: 'create-app', + title: 'Create App', + description: 'Create an app', + schema: { + input: { type: 'object' as const }, + output: { type: 'object' as const }, + }, + attributes: { destructive: false, readOnly: false, idempotent: false }, + }, + ]; + + const fakeActionsService: ActionsService = { + list: jest.fn(async () => ({ actions: fakeActions })), + invoke: jest.fn(async () => ({ output: {} })), + }; + + it('should return all actions when no filter rules are set', async () => { + const mcpService = await McpService.create({ + actions: fakeActionsService, + metrics: metricsServiceMock.mock(), + }); + + const serverConfig: McpServerConfig = { + name: 'All Actions', + includeRules: [], + excludeRules: [], + }; + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + serverConfig, + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { method: 'tools/list' }, + ListToolsResultSchema, + ); + + expect(result.tools).toHaveLength(3); + }); + + it('should scope actions using include filter rules', async () => { + const mcpService = await McpService.create({ + actions: fakeActionsService, + metrics: metricsServiceMock.mock(), + }); + + const serverConfig: McpServerConfig = { + name: 'Catalog Only', + includeRules: parseFilterRules( + new ConfigReader({ + include: [{ id: 'catalog:*' }], + }).getConfigArray('include'), + ), + excludeRules: [], + }; + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + serverConfig, + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { method: 'tools/list' }, + ListToolsResultSchema, + ); + + expect(result.tools).toHaveLength(2); + expect(result.tools.map(t => t.name)).toEqual([ + 'catalog:get-entity', + 'catalog:delete-entity', + ]); + }); + + it('should apply exclude filter rules to remove destructive actions', async () => { + const mcpService = await McpService.create({ + actions: fakeActionsService, + metrics: metricsServiceMock.mock(), + }); + + const serverConfig: McpServerConfig = { + name: 'Catalog', + includeRules: parseFilterRules( + new ConfigReader({ + include: [{ id: 'catalog:*' }], + }).getConfigArray('include'), + ), + excludeRules: [{ attributes: { destructive: true } }], + }; + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + serverConfig, + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { method: 'tools/list' }, + ListToolsResultSchema, + ); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0].name).toBe('catalog:get-entity'); + }); + + it('should apply include filter rules with glob patterns', async () => { + const mcpService = await McpService.create({ + actions: fakeActionsService, + metrics: metricsServiceMock.mock(), + }); + + const serverConfig: McpServerConfig = { + name: 'Catalog', + includeRules: parseFilterRules( + new ConfigReader({ + include: [{ id: 'catalog:get-*' }], + }).getConfigArray('include'), + ), + excludeRules: [], + }; + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + serverConfig, + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { method: 'tools/list' }, + ListToolsResultSchema, + ); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0].name).toBe('catalog:get-entity'); + }); + + it('should reject tool calls for actions outside the filtered set', async () => { + const mcpService = await McpService.create({ + actions: fakeActionsService, + metrics: metricsServiceMock.mock(), + }); + + const serverConfig: McpServerConfig = { + name: 'Scaffolder', + includeRules: parseFilterRules( + new ConfigReader({ + include: [{ id: 'scaffolder:*' }], + }).getConfigArray('include'), + ), + excludeRules: [], + }; + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + serverConfig, + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { + method: 'tools/call', + params: { name: 'catalog:get-entity', arguments: {} }, + }, + CallToolResultSchema, + ); + + expect(result).toEqual({ + content: [ + { + type: 'text', + text: expect.stringContaining( + 'Action "catalog:get-entity" not found', + ), + }, + ], + isError: true, + }); + }); + }); + + describe('namespaced tool names', () => { + it('should use action ID as tool name by default', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockActionsRegistry.register({ + name: 'mock-action', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({}), + output: z => z.object({}), + }, + action: async () => ({ output: {} }), + }); + + const mcpService = await McpService.create({ + actions: mockActionsRegistry, + metrics: metricsServiceMock.mock(), + }); + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { method: 'tools/list' }, + ListToolsResultSchema, + ); + + expect(result.tools[0].name).toBe('test:mock-action'); + }); + + it('should use short action name when namespacing is disabled', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockActionsRegistry.register({ + name: 'mock-action', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({}), + output: z => z.object({}), + }, + action: async () => ({ output: {} }), + }); + + const mcpService = await McpService.create({ + actions: mockActionsRegistry, + metrics: metricsServiceMock.mock(), + namespacedToolNames: false, + }); + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { method: 'tools/list' }, + ListToolsResultSchema, + ); + + expect(result.tools[0].name).toBe('mock-action'); + }); + + it('should match tool calls using the namespaced name', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockActionsRegistry.register({ + name: 'mock-action', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({}), + output: z => z.object({}), + }, + action: async () => ({ output: {} }), + }); + + const mcpService = await McpService.create({ + actions: mockActionsRegistry, + metrics: metricsServiceMock.mock(), + }); + + const server = mcpService.getServer({ + credentials: mockCredentials.user(), + }); + + const client = new Client({ name: 'test', version: '1.0' }); + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const result = await client.request( + { + method: 'tools/call', + params: { name: 'test:mock-action', arguments: {} }, + }, + CallToolResultSchema, + ); + + expect(result.isError).toBeUndefined(); + }); + }); }); diff --git a/plugins/mcp-actions-backend/src/services/McpService.ts b/plugins/mcp-actions-backend/src/services/McpService.ts index b6338a0e1d..18a5e57908 100644 --- a/plugins/mcp-actions-backend/src/services/McpService.ts +++ b/plugins/mcp-actions-backend/src/services/McpService.ts @@ -22,6 +22,7 @@ import { import { JsonObject } from '@backstage/types'; import { ActionsService, + ActionsServiceAction, MetricsServiceHistogram, MetricsService, } from '@backstage/backend-plugin-api/alpha'; @@ -31,13 +32,20 @@ import { performance } from 'node:perf_hooks'; import { handleErrors } from './handleErrors'; import { bucketBoundaries, McpServerOperationAttributes } from '../metrics'; +import { FilterRule, McpServerConfig } from '../config'; export class McpService { private readonly actions: ActionsService; + private readonly namespacedToolNames: boolean; private readonly operationDuration: MetricsServiceHistogram; - constructor(actions: ActionsService, metrics: MetricsService) { + constructor( + actions: ActionsService, + metrics: MetricsService, + namespacedToolNames?: boolean, + ) { this.actions = actions; + this.namespacedToolNames = namespacedToolNames ?? true; this.operationDuration = metrics.createHistogram( 'mcp.server.operation.duration', @@ -52,17 +60,27 @@ export class McpService { static async create({ actions, metrics, + namespacedToolNames, }: { actions: ActionsService; metrics: MetricsService; + namespacedToolNames?: boolean; }) { - return new McpService(actions, metrics); + return new McpService(actions, metrics, namespacedToolNames); } - getServer({ credentials }: { credentials: BackstageCredentials }) { + getServer({ + credentials, + serverConfig, + }: { + credentials: BackstageCredentials; + serverConfig?: McpServerConfig; + }) { + const serverName = serverConfig?.name ?? 'backstage'; + const server = new McpServer( { - name: 'backstage', + name: serverName, // TODO: this version will most likely change in the future. version, }, @@ -74,8 +92,12 @@ export class McpService { let errorType: string | undefined; try { - // TODO: switch this to be configuration based later - const { actions } = await this.actions.list({ credentials }); + const { actions: allActions } = await this.actions.list({ + credentials, + }); + const actions = serverConfig + ? this.filterActions(allActions, serverConfig) + : allActions; return { tools: actions.map(action => ({ @@ -83,7 +105,7 @@ export class McpService { // todo(blam): this is unfortunately not supported by most clients yet. // When this is provided you need to provide structuredContent instead. // outputSchema: action.schema.output, - name: action.name, + name: this.getToolName(action), description: action.description, annotations: { title: action.title, @@ -114,8 +136,14 @@ export class McpService { try { const result = await handleErrors(async () => { - const { actions } = await this.actions.list({ credentials }); - const action = actions.find(a => a.name === params.name); + const { actions: allActions } = await this.actions.list({ + credentials, + }); + const actions = serverConfig + ? this.filterActions(allActions, serverConfig) + : allActions; + + const action = actions.find(a => this.getToolName(a) === params.name); if (!action) { throw new NotFoundError(`Action "${params.name}" not found`); @@ -169,4 +197,53 @@ export class McpService { return server; } + + private filterActions( + actions: ActionsServiceAction[], + serverConfig: McpServerConfig, + ): ActionsServiceAction[] { + const { includeRules, excludeRules } = serverConfig; + if (includeRules.length === 0 && excludeRules.length === 0) { + return actions; + } + + return actions.filter(action => { + if (excludeRules.some(rule => this.matchesRule(action, rule))) { + return false; + } + + if (includeRules.length === 0) { + return true; + } + + return includeRules.some(rule => this.matchesRule(action, rule)); + }); + } + + private getToolName(action: ActionsServiceAction): string { + if (this.namespacedToolNames) { + return action.id; + } + return action.name; + } + + private matchesRule(action: ActionsServiceAction, rule: FilterRule): boolean { + if (rule.idMatcher && !rule.idMatcher.match(action.id)) { + return false; + } + + if (rule.attributes) { + for (const [key, value] of Object.entries(rule.attributes)) { + if ( + action.attributes[ + key as 'destructive' | 'readOnly' | 'idempotent' + ] !== value + ) { + return false; + } + } + } + + return true; + } } diff --git a/plugins/mui-to-bui/report.api.md b/plugins/mui-to-bui/report.api.md index 302804d432..f4bf566c9e 100644 --- a/plugins/mui-to-bui/report.api.md +++ b/plugins/mui-to-bui/report.api.md @@ -102,7 +102,6 @@ const _default: OverridableFrontendPlugin< >; }; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; diff --git a/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemerPage.tsx b/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemerPage.tsx index 7f0e9f32fc..a9b2c07318 100644 --- a/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemerPage.tsx +++ b/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemerPage.tsx @@ -16,7 +16,7 @@ import { useApi } from '@backstage/core-plugin-api'; import { appThemeApiRef } from '@backstage/core-plugin-api'; -import { Box, Card, Container, Flex, HeaderPage, Text } from '@backstage/ui'; +import { Box, Card, Container, Flex, Header, Text } from '@backstage/ui'; import { ThemeContent } from './ThemeContent'; import { MuiThemeExtractor } from './MuiThemeExtractor'; @@ -26,7 +26,7 @@ export const BuiThemerPage = () => { return ( - +

Convert your MUI v5 theme into BUI CSS variables. Pick a theme to view diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts index d9aef27e46..ce7898566b 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts @@ -355,6 +355,51 @@ describe('SlackNotificationProcessor', () => { }); }); + describe('when recipients include both users and a group', () => { + it('should still DM explicit user recipients', async () => { + const slack = new WebClient(); + + const processor = SlackNotificationProcessor.fromConfig(config, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + })[0]; + + await processor.processOptions({ + recipients: { + type: 'entity', + entityRef: ['group:default/mock', 'user:default/mock'], + }, + payload: { title: 'notification' }, + }); + + await processor.postProcess( + { + origin: 'plugin', + id: 'explicit-user-1', + user: 'user:default/mock', + created: new Date(), + payload: { title: 'notification' }, + }, + { + recipients: { + type: 'entity', + entityRef: ['group:default/mock', 'user:default/mock'], + }, + payload: { title: 'notification' }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(2); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'U12345678' }), + ); + }); + }); + describe('when broadcast channels are not configured', () => { it('should not send broadcast messages', async () => { const slack = new WebClient(); diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts index 3bc833bcab..de084df464 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts @@ -19,6 +19,7 @@ import { Entity, isUserEntity, parseEntityRef, + stringifyEntityRef, UserEntity, } from '@backstage/catalog-model'; import { Config, readDurationFromConfig } from '@backstage/config'; @@ -280,8 +281,15 @@ export class SlackNotificationProcessor implements NotificationProcessor { } else if (options.recipients.type === 'entity') { // Handle user-specific notification const entityRefs = [options.recipients.entityRef].flat(); - if (entityRefs.some(e => parseEntityRef(e).kind === 'group')) { - // We've already dispatched a slack channel message, so let's not send a DM. + const explicitUserEntityRefs = entityRefs + .filter(entityRef => parseEntityRef(entityRef).kind === 'user') + .map(entityRef => stringifyEntityRef(parseEntityRef(entityRef))); + const normalizedUserRef = stringifyEntityRef( + parseEntityRef(notification.user), + ); + + if (!explicitUserEntityRefs.includes(normalizedUserRef)) { + // This user was resolved from a non-user entity. Skip sending a DM. return; } diff --git a/plugins/notifications/report-alpha.api.md b/plugins/notifications/report-alpha.api.md index 7ec24ef81e..e1feed0cde 100644 --- a/plugins/notifications/report-alpha.api.md +++ b/plugins/notifications/report-alpha.api.md @@ -108,7 +108,6 @@ const _default: OverridableFrontendPlugin< >; }; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; diff --git a/plugins/org/report-alpha.api.md b/plugins/org/report-alpha.api.md index 781cfe605a..44fd429bfa 100644 --- a/plugins/org/report-alpha.api.md +++ b/plugins/org/report-alpha.api.md @@ -214,17 +214,13 @@ export const orgTranslationRef: TranslationRef< 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.title': '{{groupName}} members'; + readonly 'membersListCard.cardLabel': 'User page for {{memberName}}'; 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 'membersListCard.noSearchResult': 'Found no members matching "{{searchTerm}}".'; + readonly 'membersListCard.aggregateMembersToggle.label': 'Include subgroups'; readonly 'ownershipCard.title': 'Ownership'; - readonly 'ownershipCard.aggregateRelationsToggle.ariaLabel': 'Ownership Type Switch'; - readonly 'ownershipCard.aggregateRelationsToggle.directRelations': 'Direct Relations'; - readonly 'ownershipCard.aggregateRelationsToggle.aggregatedRelations': 'Aggregated Relations'; + readonly 'ownershipCard.aggregateRelationsToggle.label': 'Include indirect ownership'; readonly 'userProfileCard.editIconButtonTitle': 'Edit Metadata'; readonly 'userProfileCard.listItemTitle.email': 'Email'; readonly 'userProfileCard.listItemTitle.memberOf': 'Member of'; diff --git a/plugins/org/report.api.md b/plugins/org/report.api.md index 0d6b15b308..d51a45e3f7 100644 --- a/plugins/org/report.api.md +++ b/plugins/org/report.api.md @@ -6,7 +6,6 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; -import { InfoCardVariants } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react/jsx-runtime'; // @public (undocumented) @@ -33,13 +32,11 @@ export const EntityMembersListCard: (props: { // @public (undocumented) export const EntityOwnershipCard: (props: { - variant?: InfoCardVariants; entityFilterKind?: string[]; hideRelationsToggle?: boolean; relationsType?: EntityRelationAggregation; relationAggregation?: EntityRelationAggregation; entityLimit?: number; - maxScrollHeight?: string; }) => JSX_2.Element; // @public (undocumented) @@ -57,9 +54,6 @@ export const GroupProfileCard: (props: { showLinks?: boolean; }) => JSX_2.Element; -// @public (undocumented) -export type MemberComponentClassKey = 'card' | 'avatar'; - // @public (undocumented) export const MembersListCard: (props: { memberDisplayTitle?: string; @@ -71,7 +65,7 @@ export const MembersListCard: (props: { }) => JSX_2.Element; // @public (undocumented) -export type MembersListCardClassKey = 'root' | 'cardContent' | 'memberList'; +export type MembersListCardClassKey = 'memberList'; // @public export const MyGroupsSidebarItem: (props: { @@ -93,23 +87,15 @@ export { orgPlugin as plugin }; // @public (undocumented) export const OwnershipCard: (props: { - variant?: InfoCardVariants; entityFilterKind?: string[]; hideRelationsToggle?: boolean; relationsType?: EntityRelationAggregation; relationAggregation?: EntityRelationAggregation; entityLimit?: number; - maxScrollHeight?: string; }) => JSX_2.Element; // @public (undocumented) -export type OwnershipCardClassKey = - | 'card' - | 'cardContent' - | 'list' - | 'listItemText' - | 'listItemSecondaryAction' - | 'grid'; +export type OwnershipCardClassKey = 'grid'; // @public (undocumented) export const UserProfileCard: (props: { diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx index 891c1ba3c7..5ae60fb9b2 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx @@ -124,19 +124,11 @@ describe('MemberTab Test', () => { }, }); - expect(screen.getByAltText('Tara MacGovern')).toHaveAttribute( - 'src', - 'https://example.com/staff/tara.jpeg', - ); + expect(screen.getByText('Tara MacGovern')).toBeInTheDocument(); expect(screen.getByText('tara-macgovern@example.com')).toBeInTheDocument(); - expect(screen.getByText('Tara MacGovern').closest('a')).toHaveAttribute( - 'href', - '/catalog/foo-bar/user/tara.macgovern', - ); - expect(screen.getByText('Super Awesome Developer')).toBeInTheDocument(); - expect(screen.getByText('Members (1 of 1)')).toBeInTheDocument(); + expect(screen.getByText('team-d members (1 of 1)')).toBeInTheDocument(); }); it('Can render different member display title', async () => { @@ -205,8 +197,7 @@ describe('MemberTab Test', () => { }, }, ); - const toggleSwitch = screen.queryByRole('checkbox'); - expect(toggleSwitch).toBeNull(); + expect(screen.queryByRole('switch')).not.toBeInTheDocument(); }); it('Shows the aggregate members toggle if the showAggregateMembersToggle prop is true', async () => { @@ -233,8 +224,9 @@ describe('MemberTab Test', () => { }, }, ); - expect(screen.queryByRole('checkbox')).toBeInTheDocument(); + expect(screen.getByRole('switch')).toBeInTheDocument(); }); + it('Shows only direct members if the showAggregateMembersToggle prop is undefined', async () => { await renderInTestApp( { }, }, ); - const displayedMemberNames = screen.queryAllByTestId('user-link'); const duplicatedUserText = screen.getByText('Duplicated User'); const groupAUserOneText = screen.getByText('Group A User One'); - expect(displayedMemberNames).toHaveLength(2); + expect(screen.getAllByRole('heading', { level: 4 })).toHaveLength(2); expect(duplicatedUserText).toBeInTheDocument(); expect(groupAUserOneText).toBeInTheDocument(); expect( @@ -294,10 +285,9 @@ describe('MemberTab Test', () => { }, }, ); - const displayedMemberNames = screen.queryAllByTestId('user-link'); const duplicatedUserText = screen.getByText('Duplicated User'); const groupAUserOneText = screen.getByText('Group A User One'); - expect(displayedMemberNames).toHaveLength(2); + expect(screen.getAllByRole('heading', { level: 4 })).toHaveLength(2); expect(duplicatedUserText).toBeInTheDocument(); expect(groupAUserOneText).toBeInTheDocument(); expect( @@ -331,18 +321,16 @@ describe('MemberTab Test', () => { ); // Should show only direct users on initial load - const displayedMemberNamesBefore = screen.queryAllByTestId('user-link'); - expect(displayedMemberNamesBefore).toHaveLength(2); + expect(screen.getAllByRole('heading', { level: 4 })).toHaveLength(2); // Click the toggle switch - await userEvent.click(screen.getByRole('checkbox')); - const displayedMemberNamesAfter = screen.queryAllByTestId('user-link'); + await userEvent.click(screen.getByRole('switch')); const duplicatedUserText = screen.getByText('Duplicated User'); const groupAUserOneText = screen.getByText('Group A User One'); const groupBUserOneText = screen.getByText('Group B User One'); const groupDUserOneText = screen.getByText('Group D User One'); const groupEUserOneText = screen.getByText('Group E User One'); - expect(displayedMemberNamesAfter).toHaveLength(5); + expect(screen.getAllByRole('heading', { level: 4 })).toHaveLength(5); expect(duplicatedUserText).toBeInTheDocument(); expect(groupAUserOneText).toBeInTheDocument(); expect(groupBUserOneText).toBeInTheDocument(); @@ -392,15 +380,13 @@ describe('MemberTab Test', () => { ); // Should show aggregated users on initial load - const displayedMemberNamesBefore = screen.queryAllByTestId('user-link'); - expect(displayedMemberNamesBefore).toHaveLength(5); + expect(screen.getAllByRole('heading', { level: 4 })).toHaveLength(5); // Click the toggle switch - await userEvent.click(screen.getByRole('checkbox')); + await userEvent.click(screen.getByRole('switch')); // Should now show only direct users - const displayedMemberNamesAfter = screen.queryAllByTestId('user-link'); - expect(displayedMemberNamesAfter).toHaveLength(2); + expect(screen.getAllByRole('heading', { level: 4 })).toHaveLength(2); }); it('Can show aggregated members without the aggregate members toggle', async () => { @@ -428,12 +414,11 @@ describe('MemberTab Test', () => { }, ); - // aggregated relations checkbox should not be rendered - expect(screen.queryByRole('checkbox')).toBeNull(); + // aggregated relations switch should not be rendered + expect(screen.queryByRole('switch')).not.toBeInTheDocument(); // Should show all descendant users on load - const displayedMemberNames = screen.queryAllByTestId('user-link'); - expect(displayedMemberNames).toHaveLength(5); + expect(screen.getAllByRole('heading', { level: 4 })).toHaveLength(5); }); describe('Search', () => { @@ -483,7 +468,7 @@ describe('MemberTab Test', () => { ); expect( - screen.getByText(/This group has no members./i), + screen.getByText(/Found no members matching/i), ).toBeInTheDocument(); }); }); diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx index ee2f37ea9c..feb8590826 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx @@ -23,25 +23,14 @@ import { import { catalogApiRef, useEntity, - EntityRefLink, + EntityInfoCard, + useEntityRefLink, } from '@backstage/plugin-catalog-react'; -import Box from '@material-ui/core/Box'; -import Grid from '@material-ui/core/Grid'; -import Switch from '@material-ui/core/Switch'; -import Typography from '@material-ui/core/Typography'; -import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; -import Pagination from '@material-ui/lab/Pagination'; -import { useState, useEffect, ChangeEvent } from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import { useState, useEffect } from 'react'; import useAsync from 'react-use/esm/useAsync'; -import { - Avatar, - InfoCard, - Progress, - ResponseErrorPanel, - Link, - OverflowTooltip, -} from '@backstage/core-components'; +import { Progress, ResponseErrorPanel } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { getAllDesendantMembersForGroupEntity, @@ -50,101 +39,107 @@ import { import { EntityRelationAggregation } from '../../types'; import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { orgTranslationRef } from '../../../../translation'; -import TextField from '@material-ui/core/TextField'; +import { + Avatar, + Box, + Card, + Flex, + Link, + SearchField, + Switch, + TablePagination, + Text, +} from '@backstage/ui'; -/** @public */ -export type MemberComponentClassKey = 'card' | 'avatar'; - -const useStyles = makeStyles( - (theme: Theme) => - createStyles({ - card: { - border: `1px solid ${theme.palette.divider}`, - boxShadow: theme.shadows[2], - borderRadius: '4px', - overflow: 'visible', - position: 'relative', - margin: theme.spacing(4, 1, 1), - flex: '1', - minWidth: '0px', - }, - avatar: { - position: 'absolute', - top: '-2rem', - }, - }), - { name: 'PluginOrgMemberComponent' }, -); +const useMemberStyles = makeStyles({ + card: { + display: 'flex', + gap: 'var(--bui-space-3)', + padding: 'var(--bui-space-3)', + alignItems: 'flex-start', + flexDirection: 'row', + height: 140, + overflow: 'hidden', + }, + avatar: { + flexShrink: 0, + }, + cardTextContainer: { + overflow: 'hidden', + }, + singlelineEllipsis: { + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }, + multilineEllipsis: { + display: '-webkit-box', + '-webkit-line-clamp': '3', + '-webkit-box-orient': 'vertical', + overflow: 'hidden', + }, +}); const MemberComponent = (props: { member: UserEntity }) => { - const classes = useStyles(); + const { t } = useTranslationRef(orgTranslationRef); + const classes = useMemberStyles(); const { metadata: { name: metaName, description }, spec: { profile }, } = props.member; const displayName = profile?.displayName ?? metaName; + const entityLink = useEntityRefLink(); return ( - - - - - - - - {profile?.email && ( - - - - )} - {description && ( - - - - )} - - - + + + + + {displayName} + + {profile?.email && ( + + {profile.email} + + )} + {description && ( + {description} + )} + + ); }; /** @public */ -export type MembersListCardClassKey = 'root' | 'cardContent' | 'memberList'; +export type MembersListCardClassKey = 'memberList'; const useListStyles = makeStyles( - theme => ({ - root: { - height: '100%', - }, - cardContent: { - overflow: 'auto', - }, + () => ({ memberList: { display: 'grid', - gap: theme.spacing(1.5), - gridTemplateColumns: `repeat(auto-fit, minmax(auto, ${theme.spacing( - 34, - )}px))`, + gap: 'var(--bui-space-3)', + gridTemplateColumns: `repeat(auto-fit, minmax(275px, 1fr))`, + gridAutoRows: '1fr', + margin: 0, + padding: 0, + paddingTop: 'var(--bui-space-3)', + listStyle: 'none', + }, + memberListItem: { + display: 'contents', }, }), { name: 'PluginOrgMembersListCardComponent' }, @@ -162,7 +157,7 @@ export const MembersListCard = (props: { }) => { const { t } = useTranslationRef(orgTranslationRef); const { - memberDisplayTitle = t('membersListCard.title'), + memberDisplayTitle, pageSize = 50, showAggregateMembersToggle, relationType = 'memberof', @@ -179,13 +174,13 @@ export const MembersListCard = (props: { const catalogApi = useApi(catalogApiRef); const displayName = profile?.displayName ?? groupName; + const cardTitle = + memberDisplayTitle ?? + t('membersListCard.title', { groupName: displayName }); const groupNamespace = grpNamespace || DEFAULT_NAMESPACE; - const [page, setPage] = useState(1); - const pageChange = (_: ChangeEvent, pageIndex: number) => { - setPage(pageIndex); - }; + const [offset, setOffset] = useState(0); const [showAggregateMembers, setShowAggregateMembers] = useState( relationAggregation === 'aggregated', @@ -194,8 +189,8 @@ export const MembersListCard = (props: { const [searchTerm, setSearchTerm] = useState(''); useEffect(() => { - setPage(1); - }, [searchTerm]); + setOffset(0); + }, [searchTerm, showAggregateMembers]); const { loading: loadingDescendantMembers, value: descendantMembers } = useAsync(async () => { @@ -245,25 +240,6 @@ export const MembersListCard = (props: { return ; } - const nbPages = Math.ceil((members?.length || 0) / pageSize); - const paginationLabel = - nbPages < 2 - ? '' - : t('membersListCard.paginationLabel', { - page: String(page), - nbPages: String(nbPages), - }); - - const pagination = ( - - ); - const filteredMembers = members.filter(member => { const fields = [ member.metadata.name, @@ -279,72 +255,79 @@ export const MembersListCard = (props: { }); const membersToRender = searchTerm ? filteredMembers : members; + const totalCount = membersToRender.length; + const hasNextPage = offset + pageSize < totalCount; + const hasPreviousPage = offset > 0; + + const pagination = + totalCount > pageSize ? ( + setOffset(prev => prev + pageSize)} + onPreviousPage={() => setOffset(prev => Math.max(0, prev - pageSize))} + /> + ) : undefined; let memberList: JSX.Element; - if (membersToRender && membersToRender.length > 0) { + if (membersToRender.length > 0) { memberList = ( - - {membersToRender - .slice(pageSize * (page - 1), pageSize * page) - .map(member => ( - - ))} - +
    + {membersToRender.slice(offset, offset + pageSize).map(member => ( +
  • + +
  • + ))} +
); } else { memberList = ( - - {t('membersListCard.noMembersDescription')} + + + {searchTerm + ? t('membersListCard.noSearchResult', { searchTerm }) + : t('membersListCard.noMembersDescription')} + ); } return ( - - - {showAggregateMembersToggle && ( - <> - {t('membersListCard.aggregateMembersToggle.directMembers')} - { - setShowAggregateMembers(!showAggregateMembers); - }} - inputProps={{ - 'aria-label': t( - 'membersListCard.aggregateMembersToggle.ariaLabel', - ), - }} - /> - {t('membersListCard.aggregateMembersToggle.aggregatedMembers')} - - )} - {showAggregateMembers && loadingDescendantMembers ? ( - - ) : ( - <> - ) => - setSearchTerm(e.target.value) - } - /> - {memberList} - - )} - - + + ) + } + footerActions={pagination} + > + {showAggregateMembers && loadingDescendantMembers ? ( + + ) : ( + <> + setSearchTerm('')} + /> + {memberList} + + )} + ); }; diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index 5bb058e1b2..780b9d4837 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -17,7 +17,7 @@ import { Entity, GroupEntity, UserEntity } from '@backstage/catalog-model'; import { catalogApiRef, EntityProvider } from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { queryByText } from '@testing-library/react'; +import { queryByText, screen } from '@testing-library/react'; import { catalogIndexRouteRef } from '../../../routes'; import { OwnershipCard } from './OwnershipCard'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; @@ -275,7 +275,7 @@ describe('OwnershipCard', () => { it('shows relations toggle', async () => { const catalogApi = catalogApiMock({ entities: items }); - const { getByTitle } = await renderInTestApp( + await renderInTestApp( @@ -288,13 +288,15 @@ describe('OwnershipCard', () => { }, ); - expect(getByTitle('Direct Relations')).toBeInTheDocument(); + expect( + screen.getByRole('switch', { name: 'Include indirect ownership' }), + ).toBeInTheDocument(); }); it('hides relations toggle', async () => { const catalogApi = catalogApiMock({ entities: items }); - const rendered = await renderInTestApp( + await renderInTestApp( @@ -307,13 +309,13 @@ describe('OwnershipCard', () => { }, ); - expect(rendered.queryByText('Direct Relations')).toBeNull(); + expect(screen.queryByRole('switch')).not.toBeInTheDocument(); }); it('overrides relation type', async () => { const catalogApi = catalogApiMock({ entities: items }); - const { getByTitle } = await renderInTestApp( + await renderInTestApp( @@ -326,13 +328,13 @@ describe('OwnershipCard', () => { }, ); - expect(getByTitle('Aggregated Relations')).toBeInTheDocument(); + await expect(screen.findByRole('switch')).resolves.toBeChecked(); }); it('defaults to aggregated for User entity kind', async () => { const catalogApi = catalogApiMock({ entities: items }); - const { getByLabelText } = await renderInTestApp( + await renderInTestApp( @@ -345,13 +347,13 @@ describe('OwnershipCard', () => { }, ); - expect(getByLabelText('Ownership Type Switch')).toBeChecked(); + await expect(screen.findByRole('switch')).resolves.toBeChecked(); }); it('defaults to direct for all entity kinds except User', async () => { const catalogApi = catalogApiMock({ entities: items }); - const { getByLabelText } = await renderInTestApp( + await renderInTestApp( @@ -364,13 +366,13 @@ describe('OwnershipCard', () => { }, ); - expect(getByLabelText('Ownership Type Switch')).not.toBeChecked(); + expect(screen.getByRole('switch')).not.toBeChecked(); }); it('defaults to provided relationsType', async () => { const catalogApi = catalogApiMock({ entities: items }); - const { getByLabelText } = await renderInTestApp( + await renderInTestApp( @@ -383,7 +385,7 @@ describe('OwnershipCard', () => { }, ); - expect(getByLabelText('Ownership Type Switch')).not.toBeChecked(); + expect(screen.getByRole('switch')).not.toBeChecked(); }); }); }); diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index 1df55c62c2..d756fdaca3 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -14,71 +14,25 @@ * limitations under the License. */ -import { InfoCard, InfoCardVariants } from '@backstage/core-components'; -import { useEntity } from '@backstage/plugin-catalog-react'; -import List from '@material-ui/core/List'; -import ListItem from '@material-ui/core/ListItem'; -import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; -import ListItemText from '@material-ui/core/ListItemText'; -import Switch from '@material-ui/core/Switch'; -import Tooltip from '@material-ui/core/Tooltip'; +import { EntityInfoCard, useEntity } from '@backstage/plugin-catalog-react'; +import { Switch } from '@backstage/ui'; import { createStyles, makeStyles } from '@material-ui/core/styles'; import { useEffect, useState } from 'react'; import { ComponentsGrid } from './ComponentsGrid'; import { EntityRelationAggregation } from '../types'; import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { orgTranslationRef } from '../../../translation'; -import Box from '@material-ui/core/Box'; /** @public */ -export type OwnershipCardClassKey = - | 'card' - | 'cardContent' - | 'list' - | 'listItemText' - | 'listItemSecondaryAction' - | 'grid'; +export type OwnershipCardClassKey = 'grid'; const useStyles = makeStyles( - theme => + () => createStyles({ - card: { - maxHeight: '100%', - }, - cardContent: { - display: 'flex', - flexDirection: 'column', - overflow: 'hidden', - }, - list: { - [theme.breakpoints.down('xs')]: { - padding: `0 0 12px`, - }, - }, - listItemText: { - [theme.breakpoints.down('xs')]: { - paddingRight: 0, - paddingLeft: 0, - }, - }, - listItemSecondaryAction: { - [theme.breakpoints.down('xs')]: { - width: '100%', - top: 'auto', - right: 'auto', - position: 'relative', - transform: 'unset', - }, - }, grid: { overflowY: 'auto', marginTop: 0, }, - box: { - overflowY: 'auto', - padding: theme.spacing(0, 1, 1), - margin: theme.spacing(0, -1), - }, }), { name: 'PluginOrgOwnershipCard', @@ -87,27 +41,17 @@ const useStyles = makeStyles( /** @public */ export const OwnershipCard = (props: { - variant?: InfoCardVariants; entityFilterKind?: string[]; hideRelationsToggle?: boolean; /** @deprecated Please use relationAggregation instead */ relationsType?: EntityRelationAggregation; relationAggregation?: EntityRelationAggregation; entityLimit?: number; - maxScrollHeight?: string; }) => { - const { - variant, - entityFilterKind, - hideRelationsToggle, - entityLimit = 6, - maxScrollHeight: propMaxScrollHeight, - } = props; + const { entityFilterKind, hideRelationsToggle, entityLimit = 6 } = props; const relationAggregation = props.relationAggregation ?? props.relationsType; const relationsToggle = hideRelationsToggle === undefined ? false : hideRelationsToggle; - const maxScrollHeight = - variant !== 'fullHeight' ? propMaxScrollHeight : undefined; const classes = useStyles(); const { entity } = useEntity(); const { t } = useTranslationRef(orgTranslationRef); @@ -125,65 +69,27 @@ export const OwnershipCard = (props: { }, [setRelationAggregation, defaultRelationAggregation, relationAggregation]); return ( - + setRelationAggregation(isSelected ? 'aggregated' : 'direct') + } + label={t('ownershipCard.aggregateRelationsToggle.label')} + /> + ) + } > - {!relationsToggle && ( - - - - - {t('ownershipCard.aggregateRelationsToggle.directRelations')} - - { - const updatedRelationAggregation = - getRelationAggregation === 'direct' - ? 'aggregated' - : 'direct'; - setRelationAggregation(updatedRelationAggregation); - }} - name="pin" - inputProps={{ - 'aria-label': t( - 'ownershipCard.aggregateRelationsToggle.ariaLabel', - ), - }} - /> - - {t('ownershipCard.aggregateRelationsToggle.aggregatedRelations')} - - - - )} - - - - + + ); }; diff --git a/plugins/org/src/overridableComponents.ts b/plugins/org/src/overridableComponents.ts index b4c5f31212..a5057e57ec 100644 --- a/plugins/org/src/overridableComponents.ts +++ b/plugins/org/src/overridableComponents.ts @@ -18,7 +18,6 @@ import { Overrides } from '@material-ui/core/styles/overrides'; import { StyleRules } from '@material-ui/core/styles/withStyles'; import { ComponentsGridClassKey, - MemberComponentClassKey, MembersListCardClassKey, OwnershipCardClassKey, UserProfileCardClassKey, @@ -26,7 +25,6 @@ import { /** @public */ export type CatalogReactComponentsNameToClassKey = { - PluginOrgMemberComponent: MemberComponentClassKey; PluginOrgMembersListCardComponent: MembersListCardClassKey; PluginOrgOwnershipCard: OwnershipCardClassKey; PluginOrgComponentsGrid: ComponentsGridClassKey; diff --git a/plugins/org/src/translation.ts b/plugins/org/src/translation.ts index b7dfda6937..b5309d14e7 100644 --- a/plugins/org/src/translation.ts +++ b/plugins/org/src/translation.ts @@ -34,22 +34,18 @@ export const orgTranslationRef = createTranslationRef({ }, }, membersListCard: { - title: 'Members', - subtitle: 'of {{groupName}}', - paginationLabel: ', page {{page}} of {{nbPages}}', + cardLabel: 'User page for {{memberName}}', + title: '{{groupName}} members', noMembersDescription: 'This group has no members.', + noSearchResult: 'Found no members matching "{{searchTerm}}".', aggregateMembersToggle: { - directMembers: 'Direct Members', - aggregatedMembers: 'Aggregated Members', - ariaLabel: 'Users Type Switch', + label: 'Include subgroups', }, }, ownershipCard: { title: 'Ownership', aggregateRelationsToggle: { - directRelations: 'Direct Relations', - aggregatedRelations: 'Aggregated Relations', - ariaLabel: 'Ownership Type Switch', + label: 'Include indirect ownership', }, }, userProfileCard: { diff --git a/plugins/scaffolder-backend/src/schema/openapi.yaml b/plugins/scaffolder-backend/src/schema/openapi.yaml index 72fa89532c..2baf9fbba3 100644 --- a/plugins/scaffolder-backend/src/schema/openapi.yaml +++ b/plugins/scaffolder-backend/src/schema/openapi.yaml @@ -686,6 +686,19 @@ paths: type: string required: - id + '400': + description: Validation errors. + content: + application/json: + schema: + type: object + properties: + errors: + type: array + items: + $ref: '#/components/schemas/ValidationError' + required: + - errors security: - {} - JWT: [] diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/apis/Api.server.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/apis/Api.server.ts index 6263921887..af824321b2 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/apis/Api.server.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/apis/Api.server.ts @@ -116,7 +116,7 @@ export type Retry = { taskId: string; }; body: RetryRequest; - response: Scaffold201Response; + response: Scaffold201Response | Scaffold400Response; }; /** * @public diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/router.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/router.ts index 1cb3ba755f..1f12056a69 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/router.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/router.ts @@ -1023,6 +1023,25 @@ export const spec = { }, }, }, + '400': { + description: 'Validation errors.', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + errors: { + type: 'array', + items: { + $ref: '#/components/schemas/ValidationError', + }, + }, + }, + required: ['errors'], + }, + }, + }, + }, }, security: [ {}, diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index ab1f5cd583..9da276c67b 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -20,11 +20,7 @@ import express from 'express'; import request from 'supertest'; import ObservableImpl from 'zen-observable'; -import { - parseEntityRef, - stringifyEntityRef, - UserEntity, -} from '@backstage/catalog-model'; +import { stringifyEntityRef, UserEntity } from '@backstage/catalog-model'; import { createTemplateAction, TaskBroker, @@ -83,9 +79,9 @@ function createDatabase(): DatabaseService { const config = new ConfigReader({}); -// todo: this needs to return a new object every time as there seems to -// be some mutation in the tests. -const generateMockTemplate = () => ({ +// Returns a new mock template object each time to avoid mutation issues. +// Accepts optional spec overrides that are merged with the base spec. +const generateMockTemplate = (specOverrides?: Record) => ({ apiVersion: 'scaffolder.backstage.io/v1beta3', kind: 'Template', metadata: { @@ -146,6 +142,7 @@ const generateMockTemplate = () => ({ }, }, ], + ...specOverrides, }, }); @@ -153,7 +150,7 @@ const mockUser: UserEntity = { apiVersion: 'backstage.io/v1alpha1', kind: 'User', metadata: { - name: 'guest', + name: 'mock', annotations: { 'google.com/email': 'bobby@tables.com', }, @@ -175,6 +172,7 @@ const createTestRouter = async ( | CreatedTemplateGlobal[]; autocompleteHandlers?: Record; actionsRegistry?: ActionsService; + entities?: any[]; } = {}, ) => { const logger = mockServices.logger.mock({ @@ -196,26 +194,13 @@ const createTestRouter = async ( jest.spyOn(taskBroker, 'vacuumTasks'); jest.spyOn(taskBroker, 'event$'); - const catalog = catalogServiceMock.mock(); + const entities = overrides.entities ?? [generateMockTemplate(), mockUser]; + const catalog = catalogServiceMock({ entities }); 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({}), @@ -651,6 +636,132 @@ describe('scaffolder router', () => { expect(response.status).toEqual(400); }); + it('rejects when required secrets are missing', async () => { + const templateWithSecrets = generateMockTemplate({ + secrets: { + schema: { + type: 'object', + required: ['NPM_TOKEN'], + properties: { + NPM_TOKEN: { type: 'string' }, + }, + }, + }, + }); + + const { router } = await createTestRouter({ + entities: [templateWithSecrets, mockUser], + }); + + 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', + }, + // No secrets provided + }); + + expect(response.status).toEqual(400); + expect(response.body.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + property: 'secrets', + message: 'secrets.NPM_TOKEN is required', + }), + ]), + ); + }); + + it('rejects when required secrets are missing without explicit type', async () => { + const templateWithSecrets = generateMockTemplate({ + secrets: { + schema: { + // No explicit type: 'object' - should still work + required: ['NPM_TOKEN'], + properties: { + NPM_TOKEN: { type: 'string' }, + }, + }, + }, + }); + + const { router } = await createTestRouter({ + entities: [templateWithSecrets, mockUser], + }); + + 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', + }, + // No secrets provided + }); + + expect(response.status).toEqual(400); + expect(response.body.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + property: 'secrets', + message: 'secrets.NPM_TOKEN is required', + }), + ]), + ); + }); + + it('accepts valid secrets matching the schema', async () => { + const templateWithSecrets = generateMockTemplate({ + secrets: { + schema: { + type: 'object', + required: ['NPM_TOKEN'], + properties: { + NPM_TOKEN: { type: 'string' }, + }, + }, + }, + }); + + const { router, taskBroker } = await createTestRouter({ + entities: [templateWithSecrets, mockUser], + }); + const broker = taskBroker.dispatch as jest.Mocked['dispatch']; + + broker.mockResolvedValue({ + taskId: 'a-random-id', + }); + + 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', + }, + secrets: { + NPM_TOKEN: 'my-secret-token', + }, + }); + + expect(response.status).toEqual(201); + expect(response.body.id).toBe('a-random-id'); + }); + it('return the template id', async () => { const { router, taskBroker } = await createTestRouter(); const broker = taskBroker.dispatch as jest.Mocked['dispatch']; @@ -1087,6 +1198,97 @@ describe('scaffolder router', () => { }); }); + describe('POST /v2/tasks/:taskId/retry', () => { + it('rejects when required secrets are missing', async () => { + const templateWithSecrets = generateMockTemplate({ + secrets: { + schema: { + type: 'object', + required: ['NPM_TOKEN'], + properties: { + NPM_TOKEN: { type: 'string' }, + }, + }, + }, + }); + + const { router, taskBroker } = await createTestRouter({ + entities: [templateWithSecrets, mockUser], + }); + + (taskBroker.get as jest.Mocked['get']).mockResolvedValue({ + id: 'a-random-id', + spec: { + templateInfo: { + entityRef: 'template:default/create-react-app-template', + baseUrl: 'https://example.com', + entity: { metadata: templateWithSecrets.metadata }, + }, + } as any, + status: 'failed', + createdAt: '', + createdBy: 'user:default/mock', + }); + + const response = await request(router) + .post('/v2/tasks/a-random-id/retry') + .send({}); + + expect(response.status).toEqual(400); + expect(response.body.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + property: 'secrets', + message: 'secrets.NPM_TOKEN is required', + }), + ]), + ); + }); + + it('accepts valid secrets on retry', async () => { + const templateWithSecrets = generateMockTemplate({ + secrets: { + schema: { + type: 'object', + required: ['NPM_TOKEN'], + properties: { + NPM_TOKEN: { type: 'string' }, + }, + }, + }, + }); + + const { router, taskBroker } = await createTestRouter({ + entities: [templateWithSecrets, mockUser], + }); + + (taskBroker.get as jest.Mocked['get']).mockResolvedValue({ + id: 'a-random-id', + spec: { + templateInfo: { + entityRef: 'template:default/create-react-app-template', + baseUrl: 'https://example.com', + entity: { metadata: templateWithSecrets.metadata }, + }, + } as any, + status: 'failed', + createdAt: '', + createdBy: 'user:default/mock', + }); + + const response = await request(router) + .post('/v2/tasks/a-random-id/retry') + .send({ + secrets: { + NPM_TOKEN: 'my-secret-token', + }, + }); + + expect(response.status).toEqual(201); + expect(taskBroker.retry).toHaveBeenCalled(); + }); + }); + describe('GET /v2/tasks/:taskId/eventstream', () => { it('should return log messages', async () => { const { unwrappedRouter: router, taskBroker } = await createTestRouter(); @@ -1407,6 +1609,9 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ const mockToken = mockCredentials.user.token(); const mockTemplate = generateMockTemplate(); + // Spy on the catalog method to verify it's called correctly + const getEntityByRefSpy = jest.spyOn(catalog, 'getEntityByRef'); + await request(router) .post('/v2/dry-run') .set('Authorization', `Bearer ${mockToken}`) @@ -1419,13 +1624,54 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ directoryContents: [], }); - expect(catalog.getEntityByRef).toHaveBeenCalledTimes(1); + expect(getEntityByRefSpy).toHaveBeenCalledTimes(1); - expect(catalog.getEntityByRef).toHaveBeenCalledWith( + expect(getEntityByRefSpy).toHaveBeenCalledWith( 'user:default/mock', expect.anything(), ); }); + + it('rejects when required secrets are missing', async () => { + const { router } = await createTestRouter(); + const mockToken = mockCredentials.user.token(); + + const templateWithSecrets = generateMockTemplate({ + secrets: { + schema: { + type: 'object', + required: ['NPM_TOKEN'], + properties: { + NPM_TOKEN: { type: 'string' }, + }, + }, + }, + }); + + const response = await request(router) + .post('/v2/dry-run') + .set('Authorization', `Bearer ${mockToken}`) + .send({ + template: templateWithSecrets, + values: { + requiredParameter1: 'required-value-1', + requiredParameter2: 'required-value-2', + }, + directoryContents: [], + // No secrets provided + }); + + expect(response.status).toEqual(400); + expect(response.body.errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + property: 'secrets', + message: 'secrets.NPM_TOKEN is required', + }), + ]), + ); + }); + it('allows payloads up to 10MB', async () => { const { unwrappedRouter } = await createTestRouter(); const mockToken = mockCredentials.user.token(); diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index ef2a39eb54..0c9daf53c2 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -16,6 +16,7 @@ import { AuditorService, + AuditorServiceEvent, AuthService, BackstageCredentials, DatabaseService, @@ -26,7 +27,7 @@ import { resolveSafeChildPath, SchedulerService, } from '@backstage/backend-plugin-api'; -import { validate } from 'jsonschema'; +import { validate, ValidatorResult } from 'jsonschema'; import { CompoundEntityRef, Entity, @@ -181,6 +182,49 @@ const readDuration = ( return defaultValue; }; +function formatSecretsValidationErrors(result: ValidatorResult) { + return result.errors.map(err => { + const property = err.property.replace(/^instance/, 'secrets'); + const secretName = err.argument; + const message = + err.name === 'required' + ? `secrets.${secretName} is required` + : `${property} ${err.message}`; + return { + ...err, + property, + message, + instance: {}, + }; + }); +} + +async function validateSecrets(options: { + template: TemplateEntityV1beta3; + secrets: Record; + res: express.Response; + auditorEvent?: AuditorServiceEvent; +}): Promise { + const { template, secrets, res, auditorEvent } = options; + if (!template.spec.secrets?.schema) { + return true; + } + + const result = validate(secrets, template.spec.secrets.schema); + if (result.valid) { + return true; + } + + await auditorEvent?.fail({ + error: new InputError('Secrets validation failed'), + }); + + res.status(400).json({ + errors: formatSecretsValidationErrors(result), + }); + return false; +} + /** * A method to create a router for the scaffolder backend plugin. */ @@ -527,6 +571,16 @@ export async function createRouter( } } + const secretsValid = await validateSecrets({ + template, + secrets: req.body.secrets ?? {}, + res, + auditorEvent, + }); + if (!secretsValid) { + return; + } + const baseUrl = getEntityBaseUrl(template); const taskSpec: TaskSpec = { @@ -747,6 +801,28 @@ export async function createRouter( isTaskAuthorized, }); + // Validate secrets against template schema if defined + if (task.spec.templateInfo?.entityRef) { + const templateEntityRef = parseEntityRef( + task.spec.templateInfo.entityRef, + { defaultKind: 'template' }, + ); + const template = await authorizeTemplate( + templateEntityRef, + credentials, + ); + + const secretsValid = await validateSecrets({ + template, + secrets: req.body.secrets ?? {}, + res, + auditorEvent, + }); + if (!secretsValid) { + return; + } + } + await auditorEvent?.success(); const { token } = await auth.getPluginRequestToken({ @@ -975,6 +1051,16 @@ export async function createRouter( } } + const secretsValid = await validateSecrets({ + template, + secrets: body.secrets ?? {}, + res, + auditorEvent, + }); + if (!secretsValid) { + return; + } + const steps = template.spec.steps.map((step, index) => ({ ...step, id: step.id ?? `step-${index + 1}`, diff --git a/plugins/scaffolder-common/report.api.md b/plugins/scaffolder-common/report.api.md index c4b17c1723..5b82272955 100644 --- a/plugins/scaffolder-common/report.api.md +++ b/plugins/scaffolder-common/report.api.md @@ -418,6 +418,9 @@ export interface TemplateEntityV1beta3 extends Entity { input?: JsonObject; }[]; parameters?: TemplateParametersV1beta3 | TemplateParametersV1beta3[]; + secrets?: { + schema?: JsonObject; + }; steps: Array; output?: { [name: string]: string; diff --git a/plugins/scaffolder-common/src/Template.v1beta3.schema.json b/plugins/scaffolder-common/src/Template.v1beta3.schema.json index a9f846068c..5eb61a0f6d 100644 --- a/plugins/scaffolder-common/src/Template.v1beta3.schema.json +++ b/plugins/scaffolder-common/src/Template.v1beta3.schema.json @@ -146,6 +146,16 @@ } ] }, + "secrets": { + "type": "object", + "description": "Configuration for secrets that are passed during task creation.", + "properties": { + "schema": { + "type": "object", + "description": "A JSONSchema for validating secrets passed during task creation." + } + } + }, "presentation": { "type": "object", "description": "A way to redefine the presentation of the scaffolder.", diff --git a/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts b/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts index 1851b7e70f..d48049946c 100644 --- a/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts +++ b/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts @@ -67,6 +67,13 @@ export interface TemplateEntityV1beta3 extends Entity { * variables passed from the user into each action in the template. */ parameters?: TemplateParametersV1beta3 | TemplateParametersV1beta3[]; + /** + * Configuration for secrets that are passed during task creation. + * The schema field contains a JSONSchema used to validate secrets. + */ + secrets?: { + schema?: JsonObject; + }; /** * A list of steps to be executed in sequence which are defined by the template. These steps are a list of the underlying * javascript action and some optional input parameters that may or may not have been collected from the end user. diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 20865ba208..206a477d55 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -85,7 +85,7 @@ "ajv": "^8.0.1", "ajv-errors": "^3.0.0", "classnames": "^2.2.6", - "flatted": "3.3.3", + "flatted": "^3.3.4", "humanize-duration": "^3.25.1", "immer": "^9.0.6", "json-schema": "^0.4.0", diff --git a/plugins/scaffolder/report-alpha.api.md b/plugins/scaffolder/report-alpha.api.md index 9baa74407f..7c96a74c8b 100644 --- a/plugins/scaffolder/report-alpha.api.md +++ b/plugins/scaffolder/report-alpha.api.md @@ -274,7 +274,6 @@ const _default: OverridableFrontendPlugin< kind: 'page'; name: undefined; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; @@ -618,6 +617,8 @@ export const scaffolderTranslationRef: TranslationRef< readonly 'renderSchema.tableCell.description': 'Description'; readonly 'templatingExtensions.content.values.title': 'Values'; readonly 'templatingExtensions.content.values.notAvailable': 'There are no global template values defined.'; + readonly 'templatingExtensions.content.emptyState.title': 'No information to display'; + readonly 'templatingExtensions.content.emptyState.description': 'There are no templating extensions available or there was an issue communicating with the backend.'; readonly 'templatingExtensions.content.filters.title': 'Filters'; readonly 'templatingExtensions.content.filters.schema.input': 'Input'; readonly 'templatingExtensions.content.filters.schema.output': 'Output'; @@ -625,8 +626,6 @@ export const scaffolderTranslationRef: TranslationRef< readonly 'templatingExtensions.content.filters.examples': 'Examples'; readonly 'templatingExtensions.content.filters.notAvailable': 'There are no template filters defined.'; readonly 'templatingExtensions.content.filters.metadataAbsent': 'Filter metadata unavailable'; - readonly 'templatingExtensions.content.emptyState.title': 'No information to display'; - readonly 'templatingExtensions.content.emptyState.description': 'There are no templating extensions available or there was an issue communicating with the backend.'; readonly 'templatingExtensions.content.functions.title': 'Functions'; readonly 'templatingExtensions.content.functions.schema.output': 'Output'; readonly 'templatingExtensions.content.functions.schema.arguments': 'Arguments'; diff --git a/plugins/search/report-alpha.api.md b/plugins/search/report-alpha.api.md index 98a47d6b1e..e3f2d6108a 100644 --- a/plugins/search/report-alpha.api.md +++ b/plugins/search/report-alpha.api.md @@ -181,7 +181,6 @@ const _default: OverridableFrontendPlugin< kind: 'page'; name: undefined; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; @@ -351,7 +350,6 @@ export const searchPage: OverridableExtensionDefinition<{ kind: 'page'; name: undefined; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 2d0a51563d..db45c0d4a0 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -80,7 +80,7 @@ "@material-ui/lab": "4.0.0-alpha.61", "@material-ui/styles": "^4.10.0", "@microsoft/fetch-event-source": "^2.0.1", - "dompurify": "^3.2.4", + "dompurify": "^3.3.2", "git-url-parse": "^15.0.0", "lodash": "^4.17.21", "react-helmet": "6.1.0", diff --git a/plugins/techdocs/report-alpha.api.md b/plugins/techdocs/report-alpha.api.md index 5cf7b4fd7a..aa14098288 100644 --- a/plugins/techdocs/report-alpha.api.md +++ b/plugins/techdocs/report-alpha.api.md @@ -348,7 +348,6 @@ const _default: OverridableFrontendPlugin< >; }; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; @@ -441,7 +440,6 @@ const _default: OverridableFrontendPlugin< kind: 'page'; name: 'reader'; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; diff --git a/plugins/techdocs/src/reader/components/useReaderState.test.tsx b/plugins/techdocs/src/reader/components/useReaderState.test.tsx index 8922a1855f..14946104c6 100644 --- a/plugins/techdocs/src/reader/components/useReaderState.test.tsx +++ b/plugins/techdocs/src/reader/components/useReaderState.test.tsx @@ -415,39 +415,44 @@ describe('useReaderState', () => { }); it('should handle stale content', async () => { - techdocsStorageApi.getEntityDocs - .mockResolvedValueOnce('my content') - .mockImplementationOnce(async () => { - await new Promise(resolve => setTimeout(resolve, 1100)); - return 'my new content'; + jest.useFakeTimers(); + + try { + techdocsStorageApi.getEntityDocs + .mockResolvedValueOnce('my content') + .mockImplementationOnce(async () => { + await new Promise(resolve => setTimeout(resolve, 1100)); + return 'my new content'; + }); + techdocsStorageApi.syncEntityDocs.mockImplementation( + async (_, logHandler) => { + await 'a tick'; + logHandler?.call(this, 'Line 1'); + logHandler?.call(this, 'Line 2'); + await new Promise(resolve => setTimeout(resolve, 1100)); + return 'updated'; + }, + ); + + const { result } = renderHook( + () => useReaderState('Component', 'default', 'backstage', '/example'), + { wrapper: Wrapper }, + ); + + expect(result.current).toEqual({ + state: 'CHECKING', + path: '/example', + content: undefined, + contentErrorMessage: undefined, + syncErrorMessage: undefined, + buildLog: [], + contentReload: expect.any(Function), }); - techdocsStorageApi.syncEntityDocs.mockImplementation( - async (_, logHandler) => { - await 'a tick'; - logHandler?.call(this, 'Line 1'); - logHandler?.call(this, 'Line 2'); - await new Promise(resolve => setTimeout(resolve, 1100)); - return 'updated'; - }, - ); - const { result } = renderHook( - () => useReaderState('Component', 'default', 'backstage', '/example'), - { wrapper: Wrapper }, - ); + // flush microtasks: content loads (resolved promise) and sync progresses past 'a tick' + await act(async () => {}); - expect(result.current).toEqual({ - state: 'CHECKING', - path: '/example', - content: undefined, - contentErrorMessage: undefined, - syncErrorMessage: undefined, - buildLog: [], - contentReload: expect.any(Function), - }); - - // the content is returned but the sync is in progress - await waitFor(() => { + // the content is returned but the sync is in progress expect(result.current).toEqual({ state: 'CONTENT_FRESH', path: '/example', @@ -457,10 +462,12 @@ describe('useReaderState', () => { buildLog: ['Line 1', 'Line 2'], contentReload: expect.any(Function), }); - }); - // the sync takes longer than 1 seconds so the refreshing state starts - await waitFor(() => { + // the sync takes longer than 1 second so the refreshing state starts + await act(async () => { + jest.advanceTimersByTime(1000); + }); + expect(result.current).toEqual({ state: 'CONTENT_STALE_REFRESHING', path: '/example', @@ -470,10 +477,12 @@ describe('useReaderState', () => { buildLog: ['Line 1', 'Line 2'], contentReload: expect.any(Function), }); - }); - // the content is updated but not yet displayed - await waitFor(() => { + // the sync completes — content is updated but not yet displayed + await act(async () => { + jest.advanceTimersByTime(100); + }); + expect(result.current).toEqual({ state: 'CONTENT_STALE_READY', path: '/example', @@ -483,15 +492,13 @@ describe('useReaderState', () => { buildLog: ['Line 1', 'Line 2'], contentReload: expect.any(Function), }); - }); - // reload the content - await act(async () => { - result.current.contentReload(); - }); + // reload the content + await act(async () => { + result.current.contentReload(); + }); - // the new content refresh is triggered - await waitFor(() => { + // the new content refresh is triggered expect(result.current).toEqual({ state: 'CHECKING', path: '/example', @@ -501,39 +508,38 @@ describe('useReaderState', () => { buildLog: [], contentReload: expect.any(Function), }); - }); - // the new content is loaded - await waitFor( - () => { - expect(result.current).toEqual({ - state: 'CONTENT_FRESH', - path: '/example', - content: 'my new content', - contentErrorMessage: undefined, - syncErrorMessage: undefined, - buildLog: [], - contentReload: expect.any(Function), - }); - }, - { - timeout: 2000, - }, - ); + // the new content is loaded + await act(async () => { + jest.advanceTimersByTime(1100); + }); - expect(techdocsStorageApi.getEntityDocs).toHaveBeenCalledTimes(2); - expect(techdocsStorageApi.getEntityDocs).toHaveBeenCalledWith( - { kind: 'Component', namespace: 'default', name: 'backstage' }, - '/example', - ); - expect(techdocsStorageApi.syncEntityDocs).toHaveBeenCalledWith( - { - kind: 'Component', - namespace: 'default', - name: 'backstage', - }, - expect.any(Function), - ); + expect(result.current).toEqual({ + state: 'CONTENT_FRESH', + path: '/example', + content: 'my new content', + contentErrorMessage: undefined, + syncErrorMessage: undefined, + buildLog: [], + contentReload: expect.any(Function), + }); + + expect(techdocsStorageApi.getEntityDocs).toHaveBeenCalledTimes(2); + expect(techdocsStorageApi.getEntityDocs).toHaveBeenCalledWith( + { kind: 'Component', namespace: 'default', name: 'backstage' }, + '/example', + ); + expect(techdocsStorageApi.syncEntityDocs).toHaveBeenCalledWith( + { + kind: 'Component', + namespace: 'default', + name: 'backstage', + }, + expect.any(Function), + ); + } finally { + jest.useRealTimers(); + } }); it('should handle navigation', async () => { diff --git a/plugins/user-settings/report-alpha.api.md b/plugins/user-settings/report-alpha.api.md index 11ffee9229..c99709d777 100644 --- a/plugins/user-settings/report-alpha.api.md +++ b/plugins/user-settings/report-alpha.api.md @@ -120,7 +120,6 @@ const _default: OverridableFrontendPlugin< kind: 'page'; name: undefined; params: { - defaultPath?: [Error: `Use the 'path' param instead`]; path: string; title?: string; icon?: IconElement; diff --git a/yarn.lock b/yarn.lock index 180d0d7521..b1de7684dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -261,15 +261,6 @@ __metadata: languageName: node linkType: hard -"@ardatan/sync-fetch@npm:^0.0.1": - version: 0.0.1 - resolution: "@ardatan/sync-fetch@npm:0.0.1" - dependencies: - node-fetch: "npm:^2.6.1" - checksum: 10/ee21741badecb18fb9a18a404275e25272f67ade914f98885de79ccecba3403b8a6357e6b033a028e24f0d902197dd541655309d7789ebacd7ad981bf1f12618 - languageName: node - linkType: hard - "@asamuzakjp/css-color@npm:^3.2.0": version: 3.2.0 resolution: "@asamuzakjp/css-color@npm:3.2.0" @@ -2866,7 +2857,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.28.4, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.28.4, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.28.4 resolution: "@babel/runtime@npm:7.28.4" checksum: 10/6c9a70452322ea80b3c9b2a412bcf60771819213a67576c8cec41e88a95bb7bf01fc983754cda35dc19603eef52df22203ccbf7777b9d6316932f9fb77c25163 @@ -3016,7 +3007,7 @@ __metadata: cron: "npm:^3.0.0" express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" - express-rate-limit: "npm:^7.5.0" + express-rate-limit: "npm:^8.2.2" fs-extra: "npm:^11.2.0" get-port: "npm:^5.1.1" git-url-parse: "npm:^15.0.0" @@ -3338,9 +3329,11 @@ __metadata: "@types/jest": "npm:^30.0.0" "@types/node": "npm:^22.13.14" "@types/npm-packlist": "npm:^3.0.0" + "@types/proper-lockfile": "npm:^4" "@types/recursive-readdir": "npm:^2.2.0" "@types/rollup-plugin-peer-deps-external": "npm:^2.2.0" "@types/rollup-plugin-postcss": "npm:^3.1.4" + "@types/shell-quote": "npm:^1.7.5" "@types/svgo": "npm:^2.6.2" "@types/tar": "npm:^6.1.1" "@types/terser-webpack-plugin": "npm:^5.0.4" @@ -3387,6 +3380,7 @@ __metadata: jest-css-modules: "npm:^2.1.0" jsdom: "npm:^27.1.0" json-schema: "npm:^0.4.0" + keytar: "npm:^7.9.0" lodash: "npm:^4.17.21" mini-css-extract-plugin: "npm:^2.4.2" minimatch: "npm:^10.2.1" @@ -3400,6 +3394,7 @@ __metadata: postcss: "npm:^8.1.0" postcss-import: "npm:^16.1.0" process: "npm:^0.11.10" + proper-lockfile: "npm:^4.1.2" raw-loader: "npm:^4.0.2" react-dev-utils: "npm:^12.0.0-next.60" react-refresh: "npm:^0.17.0" @@ -3411,6 +3406,7 @@ __metadata: rollup-plugin-postcss: "npm:^4.0.0" rollup-pluginutils: "npm:^2.8.2" semver: "npm:^7.5.3" + shell-quote: "npm:^1.8.1" style-loader: "npm:^3.3.1" sucrase: "npm:^3.20.2" swc-loader: "npm:^0.2.3" @@ -3442,6 +3438,9 @@ __metadata: terser-webpack-plugin: ^5.1.3 webpack: ~5.105.0 webpack-dev-server: ^5.0.0 + dependenciesMeta: + keytar: + optional: true peerDependenciesMeta: "@jest/environment-jsdom-abstract": optional: true @@ -3538,6 +3537,7 @@ __metadata: "@backstage/core-plugin-api": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/ui": "workspace:^" "@backstage/version-bridge": "workspace:^" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" @@ -3580,6 +3580,7 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" "@backstage/core-plugin-api": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/frontend-app-api": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" "@backstage/frontend-test-utils": "workspace:^" @@ -4155,7 +4156,7 @@ __metadata: "@backstage/plugin-catalog-react": "workspace:^" "@backstage/plugin-permission-react": "workspace:^" "@backstage/test-utils": "workspace:^" - "@graphiql/react": "npm:^0.23.0" + "@graphiql/react": "npm:0.29.0" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" @@ -4166,9 +4167,9 @@ __metadata: "@types/highlightjs": "npm:^10.1.0" "@types/react": "npm:^18.0.0" "@types/swagger-ui-react": "npm:^5.0.0" - graphiql: "npm:3.1.1" + graphiql: "npm:^3.9.0" graphql: "npm:^16.0.0" - graphql-config: "npm:^5.0.2" + graphql-config: "npm:^5.1.6" graphql-ws: "npm:^5.4.1" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" @@ -5369,9 +5370,11 @@ __metadata: "@backstage/plugin-catalog-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/ui": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" + "@remixicon/react": "npm:^4.6.0" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" @@ -6275,6 +6278,7 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-client": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" @@ -6284,6 +6288,7 @@ __metadata: "@types/supertest": "npm:^2.0.8" express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" + minimatch: "npm:^10.2.1" supertest: "npm:^7.0.0" zod: "npm:^3.25.76" languageName: unknown @@ -7141,7 +7146,7 @@ __metadata: ajv: "npm:^8.0.1" ajv-errors: "npm:^3.0.0" classnames: "npm:^2.2.6" - flatted: "npm:3.3.3" + flatted: "npm:^3.3.4" humanize-duration: "npm:^3.25.1" immer: "npm:^9.0.6" json-schema: "npm:^0.4.0" @@ -7835,7 +7840,7 @@ __metadata: "@testing-library/user-event": "npm:^14.0.0" "@types/dompurify": "npm:^3.0.0" "@types/react": "npm:^18.0.0" - dompurify: "npm:^3.2.4" + dompurify: "npm:^3.3.2" git-url-parse: "npm:^15.0.0" lodash: "npm:^4.17.21" react: "npm:^18.0.2" @@ -8457,8 +8462,8 @@ __metadata: linkType: hard "@codemirror/language@npm:^6.0.0": - version: 6.12.1 - resolution: "@codemirror/language@npm:6.12.1" + version: 6.12.2 + resolution: "@codemirror/language@npm:6.12.2" dependencies: "@codemirror/state": "npm:^6.0.0" "@codemirror/view": "npm:^6.23.0" @@ -8466,7 +8471,7 @@ __metadata: "@lezer/highlight": "npm:^1.0.0" "@lezer/lr": "npm:^1.0.0" style-mod: "npm:^4.0.0" - checksum: 10/a24c3512d38cbb2a20cc3128da0eea074b4a6102b6a5a041b3dfd5e67638fb61dcdf4743ed87708db882df5d72a84d9f891aac6fa68447830989c8e2d9ffa2ba + checksum: 10/9afc704e17cad4782b68ad02869503a27103c4c3ac6927d2ffa3c6decfad627ade40b0ed78d1d6e941b2e879a7a79bda0d573e58604f3923f9e1f3311108ec36 languageName: node linkType: hard @@ -8523,14 +8528,14 @@ __metadata: linkType: hard "@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.23.0": - version: 6.39.14 - resolution: "@codemirror/view@npm:6.39.14" + version: 6.39.16 + resolution: "@codemirror/view@npm:6.39.16" 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/956e79758d97e9fc6f6fd9a21575148cb0126712e0a3f78dc03031ebed1a5787eae581eda140f1af6686a5822b3c9c3a67547d2421a16003d45a2c0b6f83ca5e + checksum: 10/199576febda2a91fe7676b8708627ed2e38d7e964ec8258331422fe7c7f89003eee2de7dec828e09c046de005742fd476cae6ceebc7bd994744f771253bfcbf3 languageName: node linkType: hard @@ -8920,6 +8925,38 @@ __metadata: languageName: node linkType: hard +"@envelop/core@npm:^5.4.0": + version: 5.5.1 + resolution: "@envelop/core@npm:5.5.1" + dependencies: + "@envelop/instrumentation": "npm:^1.0.0" + "@envelop/types": "npm:^5.2.1" + "@whatwg-node/promise-helpers": "npm:^1.2.4" + tslib: "npm:^2.5.0" + checksum: 10/1dfac266702047d07533a893a33010fa31c5056c87196db2fcd5d44b0856d50d3410ac8aeb8f3c1b65f3a227f5fb7d1f1782051e71a9197978defe51ebcc7366 + languageName: node + linkType: hard + +"@envelop/instrumentation@npm:^1.0.0": + version: 1.0.0 + resolution: "@envelop/instrumentation@npm:1.0.0" + dependencies: + "@whatwg-node/promise-helpers": "npm:^1.2.1" + tslib: "npm:^2.5.0" + checksum: 10/4e3c9670c17e7fcf4a5654d145c64ba905abca7e71c8d8fae1ad88b1eb4a68f3a2a0aff4efd41e3d997015e5e763a7c1c335826c2b7b5f9dc691e59cd4ea472e + languageName: node + linkType: hard + +"@envelop/types@npm:^5.2.1": + version: 5.2.1 + resolution: "@envelop/types@npm:5.2.1" + dependencies: + "@whatwg-node/promise-helpers": "npm:^1.0.0" + tslib: "npm:^2.5.0" + checksum: 10/dc320a53dab896cef43de99ff972cf35da2671a248cd94fe6ab45f96954c9e505dd141cb8a3afb5fbab3d41bf4d22d30d823effb9a6fec0e7c3bb95d4c3726d1 + languageName: node + linkType: hard + "@epic-web/invariant@npm:^1.0.0": version: 1.0.0 resolution: "@epic-web/invariant@npm:1.0.0" @@ -9184,6 +9221,13 @@ __metadata: languageName: node linkType: hard +"@fastify/busboy@npm:^3.1.1": + version: 3.2.0 + resolution: "@fastify/busboy@npm:3.2.0" + checksum: 10/7d42b23eed18b1aaf2d2b1c77a5b76ec3606d59f5ddec31c04b144536be57478ed0c73e04768f11535b11a37b48aaaa0aed4904d5f18391ff90045c258e41acc + languageName: node + linkType: hard + "@floating-ui/core@npm:^1.6.0": version: 1.6.8 resolution: "@floating-ui/core@npm:1.6.8" @@ -9360,14 +9404,14 @@ __metadata: linkType: hard "@google-cloud/cloud-sql-connector@npm:^1.4.0": - version: 1.9.0 - resolution: "@google-cloud/cloud-sql-connector@npm:1.9.0" + version: 1.9.1 + resolution: "@google-cloud/cloud-sql-connector@npm:1.9.1" dependencies: - "@googleapis/sqladmin": "npm:^35.0.0" + "@googleapis/sqladmin": "npm:^35.2.0" gaxios: "npm:^7.1.3" google-auth-library: "npm:^10.5.0" p-throttle: "npm:^7.0.0" - checksum: 10/30a1539a2d59ae78ef2ce275e047981dc548359d153f29715741183c5df0411da882372a020dd97be2ceb7ad6153026a14e9751b3314582f57ff7d18987c6812 + checksum: 10/52fb46d3054896ede1ec83e5361062edb96005400b8544b95c2e18d3f769feea86a56ca621620fed73938bf0b50e96bd20bb1a7fd0f15772ef2a135a2d81b6d6 languageName: node linkType: hard @@ -9469,20 +9513,20 @@ __metadata: languageName: node linkType: hard -"@googleapis/sqladmin@npm:^35.0.0": - version: 35.0.0 - resolution: "@googleapis/sqladmin@npm:35.0.0" +"@googleapis/sqladmin@npm:^35.2.0": + version: 35.2.0 + resolution: "@googleapis/sqladmin@npm:35.2.0" dependencies: googleapis-common: "npm:^8.0.0" - checksum: 10/7ecae655921c3b1e71b0b9ff04271e6ba80a6c2d4a269770c0cff6b45397abdf6a3084daba70d2659a241bcb124d0701e66ecfab2f838933582b408000aedbb0 + checksum: 10/f5931b91626a29173732381ffa7ec3c674397d3192b30da8f4b3639fef3cd88bfa3b21f7e5aa084cab1d4f90bef6860617d2d1f68e1c3692fa1c5835fc89c25a languageName: node linkType: hard -"@graphiql/react@npm:^0.20.3": - version: 0.20.3 - resolution: "@graphiql/react@npm:0.20.3" +"@graphiql/react@npm:0.29.0, @graphiql/react@npm:^0.29.0": + version: 0.29.0 + resolution: "@graphiql/react@npm:0.29.0" dependencies: - "@graphiql/toolkit": "npm:^0.9.1" + "@graphiql/toolkit": "npm:^0.11.2" "@headlessui/react": "npm:^1.7.15" "@radix-ui/react-dialog": "npm:^1.0.4" "@radix-ui/react-dropdown-menu": "npm:^2.0.5" @@ -9491,209 +9535,210 @@ __metadata: "@types/codemirror": "npm:^5.60.8" clsx: "npm:^1.2.1" codemirror: "npm:^5.65.3" - codemirror-graphql: "npm:^2.0.10" + codemirror-graphql: "npm:^2.2.1" copy-to-clipboard: "npm:^3.2.0" framer-motion: "npm:^6.5.1" - graphql-language-service: "npm:^5.2.0" - markdown-it: "npm:^12.2.0" - set-value: "npm:^4.1.0" - peerDependencies: - graphql: ^15.5.0 || ^16.0.0 - react: ^16.8.0 || ^17 || ^18 - react-dom: ^16.8.0 || ^17 || ^18 - checksum: 10/cc9bda25eaf977544c2ab9db0616d80e1e448239063c4a84aab4234de192b5fe6630a0ac2614c9f1c79ae4f42612b52ba1523cd91c98f6f21e1e01936e1fb819 - languageName: node - linkType: hard - -"@graphiql/react@npm:^0.23.0": - version: 0.23.0 - resolution: "@graphiql/react@npm:0.23.0" - dependencies: - "@graphiql/toolkit": "npm:^0.9.2" - "@headlessui/react": "npm:^1.7.15" - "@radix-ui/react-dialog": "npm:^1.0.4" - "@radix-ui/react-dropdown-menu": "npm:^2.0.5" - "@radix-ui/react-tooltip": "npm:^1.0.6" - "@radix-ui/react-visually-hidden": "npm:^1.0.3" - "@types/codemirror": "npm:^5.60.8" - clsx: "npm:^1.2.1" - codemirror: "npm:^5.65.3" - codemirror-graphql: "npm:^2.0.13" - copy-to-clipboard: "npm:^3.2.0" - framer-motion: "npm:^6.5.1" - graphql-language-service: "npm:^5.2.2" + get-value: "npm:^3.0.1" + graphql-language-service: "npm:^5.3.1" markdown-it: "npm:^14.1.0" + react-compiler-runtime: "npm:19.1.0-rc.1" set-value: "npm:^4.1.0" peerDependencies: - graphql: ^15.5.0 || ^16.0.0 + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 react: ^16.8.0 || ^17 || ^18 react-dom: ^16.8.0 || ^17 || ^18 - checksum: 10/d3e10e5e61cdc766b4b2c3597c26595c234974383e0942112174d5782bd5da3ac1be8091eea5dc7a91f475ba84f7f559f6eb36ebca87abb2d134f2e8f06a0356 + checksum: 10/c906bf3175ae042aaa035872015ea5d60991dba633e2e44f707faee9dad9f3b2b6896ca6fcef7f54e65a1b707db9a44538137e5e7037e132d0f8a456df641c49 languageName: node linkType: hard -"@graphiql/toolkit@npm:^0.9.1, @graphiql/toolkit@npm:^0.9.2": - version: 0.9.2 - resolution: "@graphiql/toolkit@npm:0.9.2" +"@graphiql/toolkit@npm:^0.11.2": + version: 0.11.3 + resolution: "@graphiql/toolkit@npm:0.11.3" dependencies: "@n1ru4l/push-pull-async-iterable-iterator": "npm:^3.1.0" meros: "npm:^1.1.4" peerDependencies: - graphql: ^15.5.0 || ^16.0.0 + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 graphql-ws: ">= 4.5.0" peerDependenciesMeta: graphql-ws: optional: true - checksum: 10/cecf99652acbe3f263a08c2d895a6de99065308428458b051cb8d09e8303af24d5c62d9d6be3cc2ffa88b81f49a825696be8bd019eacb5693217c0018f812de1 + checksum: 10/a28d5261900bff00ca550963f9b0fba035e63b87dbed5c4c1fd1dd5a8963d2c614a9161af58fd4782027b86874be2403cb3e86514567e25dce171ba9823ddc62 languageName: node linkType: hard -"@graphql-tools/batch-execute@npm:^9.0.1": - version: 9.0.2 - resolution: "@graphql-tools/batch-execute@npm:9.0.2" +"@graphql-hive/signal@npm:^2.0.0": + version: 2.0.0 + resolution: "@graphql-hive/signal@npm:2.0.0" + checksum: 10/51b1b3527300e9d67366ead3c505a1abeabbf4cf764b125c21c11bb163201b2432e4c1af49ff94b13737c30e62c84a43558fd3c4f8b9b9d9e530be08a2a6f5fc + languageName: node + linkType: hard + +"@graphql-tools/batch-execute@npm:^10.0.5": + version: 10.0.5 + resolution: "@graphql-tools/batch-execute@npm:10.0.5" dependencies: - "@graphql-tools/utils": "npm:^10.0.5" - dataloader: "npm:^2.2.2" - tslib: "npm:^2.4.0" - value-or-promise: "npm:^1.0.12" + "@graphql-tools/utils": "npm:^11.0.0" + "@whatwg-node/promise-helpers": "npm:^1.3.2" + dataloader: "npm:^2.2.3" + tslib: "npm:^2.8.1" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/ec0be4f8790c6041b4d8e796b662ce3f6ffde304ee780bf91db1912da49bed096db0e7a3b62a797920997e38d4489cd7e2ed4ee26d605a3c343ee51309ebb736 + checksum: 10/4384b095b636c4ef0cb32f4145856aa1378e92e4b892fd4bbc9ce612626806b89112482126f0fe1d89175e9954d786ec3b446a023996c4fa0c0de732735eabe0 languageName: node linkType: hard -"@graphql-tools/delegate@npm:^10.0.0, @graphql-tools/delegate@npm:^10.0.3": - version: 10.0.3 - resolution: "@graphql-tools/delegate@npm:10.0.3" +"@graphql-tools/delegate@npm:^12.0.8": + version: 12.0.8 + resolution: "@graphql-tools/delegate@npm:12.0.8" dependencies: - "@graphql-tools/batch-execute": "npm:^9.0.1" - "@graphql-tools/executor": "npm:^1.0.0" - "@graphql-tools/schema": "npm:^10.0.0" - "@graphql-tools/utils": "npm:^10.0.5" - dataloader: "npm:^2.2.2" - tslib: "npm:^2.5.0" + "@graphql-tools/batch-execute": "npm:^10.0.5" + "@graphql-tools/executor": "npm:^1.4.13" + "@graphql-tools/schema": "npm:^10.0.29" + "@graphql-tools/utils": "npm:^11.0.0" + "@repeaterjs/repeater": "npm:^3.0.6" + "@whatwg-node/promise-helpers": "npm:^1.3.2" + dataloader: "npm:^2.2.3" + tslib: "npm:^2.8.1" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/d19e15d4bdfd1878c3f18ec6f325e62872765ef5bbbf59f074e898f8967600ae7a39b75284d69c67f1de73bcfe3a5fdb63c6a130cb1680b6277c8794c9e27ab7 + checksum: 10/c747ef3cc14141a67f275e21f8ddcbd924972670c2b50b16938ef3f3014f42aee1340973428515e62d063282083b88b8a5ff3ff9a9f459558f4f5184a5ad342c languageName: node linkType: hard -"@graphql-tools/executor-graphql-ws@npm:^1.0.0": - version: 1.1.1 - resolution: "@graphql-tools/executor-graphql-ws@npm:1.1.1" +"@graphql-tools/executor-common@npm:^1.0.6": + version: 1.0.6 + resolution: "@graphql-tools/executor-common@npm:1.0.6" dependencies: - "@graphql-tools/utils": "npm:^10.0.2" - "@types/ws": "npm:^8.0.0" - graphql-ws: "npm:^5.14.0" - isomorphic-ws: "npm:^5.0.0" - tslib: "npm:^2.4.0" - ws: "npm:^8.13.0" + "@envelop/core": "npm:^5.4.0" + "@graphql-tools/utils": "npm:^11.0.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/30d29e2ef8fbedf07d7c279218f31a7279e714328f6c24d28ea76536fb4c5ed857ab5e486922000fcf9f85b83a9f3e995b8fd066b01ea4ab31d35efaa770c133 + checksum: 10/9daf04a3d1d139d27ed8957a7166aba01a9df6c9d0a459229493c69415726afa1901e234b1bb946144a4794ba67fc4740dd791eddee1117a9c5af1d0c4423007 languageName: node linkType: hard -"@graphql-tools/executor-http@npm:^1.0.0": - version: 1.0.3 - resolution: "@graphql-tools/executor-http@npm:1.0.3" +"@graphql-tools/executor-graphql-ws@npm:^3.1.4": + version: 3.1.4 + resolution: "@graphql-tools/executor-graphql-ws@npm:3.1.4" dependencies: - "@graphql-tools/utils": "npm:^10.0.2" + "@graphql-tools/executor-common": "npm:^1.0.6" + "@graphql-tools/utils": "npm:^11.0.0" + "@whatwg-node/disposablestack": "npm:^0.0.6" + graphql-ws: "npm:^6.0.6" + isows: "npm:^1.0.7" + tslib: "npm:^2.8.1" + ws: "npm:^8.18.3" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10/c5b1576e4ceb928e8d47b2c9a25585fb575dcc76fbe1cabb0e743b89361367a10858e84daddb5f742c19ae44bd69a53c8d96809bc38a8147fab18bbc66b4a067 + languageName: node + linkType: hard + +"@graphql-tools/executor-http@npm:^3.1.0": + version: 3.1.0 + resolution: "@graphql-tools/executor-http@npm:3.1.0" + dependencies: + "@graphql-hive/signal": "npm:^2.0.0" + "@graphql-tools/executor-common": "npm:^1.0.6" + "@graphql-tools/utils": "npm:^11.0.0" "@repeaterjs/repeater": "npm:^3.0.4" - "@whatwg-node/fetch": "npm:^0.9.0" - extract-files: "npm:^11.0.0" - meros: "npm:^1.2.1" - tslib: "npm:^2.4.0" - value-or-promise: "npm:^1.0.12" + "@whatwg-node/disposablestack": "npm:^0.0.6" + "@whatwg-node/fetch": "npm:^0.10.13" + "@whatwg-node/promise-helpers": "npm:^1.3.2" + meros: "npm:^1.3.2" + tslib: "npm:^2.8.1" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/0fda267df3ebe3afd44cb86e779fcab17ab9d8045670b15ceb1b06ab10a576686beb0aa786781dae29e5bbe6ab04259e73e0fe5de2a45853fda6a19f122a12f5 + checksum: 10/6e288c0c0d57ebc217860a085e98361a527997b6a6f2d96d4d4881ae9951e94720c065d34c7ed10b6ec9ea5b632fcd32e015f1b12349e7720ae15874baf678e1 languageName: node linkType: hard -"@graphql-tools/executor-legacy-ws@npm:^1.0.0": - version: 1.1.0 - resolution: "@graphql-tools/executor-legacy-ws@npm:1.1.0" +"@graphql-tools/executor-legacy-ws@npm:^1.1.25": + version: 1.1.25 + resolution: "@graphql-tools/executor-legacy-ws@npm:1.1.25" dependencies: - "@graphql-tools/utils": "npm:^10.3.0" + "@graphql-tools/utils": "npm:^11.0.0" "@types/ws": "npm:^8.0.0" isomorphic-ws: "npm:^5.0.0" tslib: "npm:^2.4.0" - ws: "npm:^8.17.1" + ws: "npm:^8.19.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/b7bfe6c1e2229beef35fb175102d0a5ab45e679834c45d579c3d0efe8b7d1e22836d3773e03b0c920bbc94a58d490d54d62f1484e043eef6a54cc426b4b92f98 + checksum: 10/3ebcd004e6583c3f2e078e3caee7a51b707d125c0993f8a4529aa6e2a6b1aaad46b8eee16e14fde135dbaa1ec3783bc4257a1fd9466673a37e7e66a05a7fbf20 languageName: node linkType: hard -"@graphql-tools/executor@npm:^1.0.0": - version: 1.2.0 - resolution: "@graphql-tools/executor@npm:1.2.0" +"@graphql-tools/executor@npm:^1.4.13": + version: 1.5.1 + resolution: "@graphql-tools/executor@npm:1.5.1" dependencies: - "@graphql-tools/utils": "npm:^10.0.0" - "@graphql-typed-document-node/core": "npm:3.2.0" + "@graphql-tools/utils": "npm:^11.0.0" + "@graphql-typed-document-node/core": "npm:^3.2.0" "@repeaterjs/repeater": "npm:^3.0.4" + "@whatwg-node/disposablestack": "npm:^0.0.6" + "@whatwg-node/promise-helpers": "npm:^1.0.0" tslib: "npm:^2.4.0" - value-or-promise: "npm:^1.0.12" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/a13c9fb2b1e15661c469a3f048f19f96191e802842e48a1ba058a2422c30f3de64b5089f57f81ef38d49ed2261f7e72f5e34834cc322188c33306d2ae711a00d + checksum: 10/b73e4ba7b19c2ebf0d5943006626388575fa023400c2b17e96e5fe0b321dabbbc116fcf2e951a3b05398dcd078805e28d12821ddce89c088bb52c6a38c2dd2f7 languageName: node linkType: hard "@graphql-tools/graphql-file-loader@npm:^8.0.0": - version: 8.0.0 - resolution: "@graphql-tools/graphql-file-loader@npm:8.0.0" + version: 8.1.10 + resolution: "@graphql-tools/graphql-file-loader@npm:8.1.10" dependencies: - "@graphql-tools/import": "npm:7.0.0" - "@graphql-tools/utils": "npm:^10.0.0" + "@graphql-tools/import": "npm:7.1.10" + "@graphql-tools/utils": "npm:^11.0.0" globby: "npm:^11.0.3" tslib: "npm:^2.4.0" unixify: "npm:^1.0.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/bf1248593123f6aa740da8b58746e2a60f5a1f413da1dcff8890daae0f2eeeac1837a2d419bdbdfb6ccb2877e03103d335ae0d1696e392f6af247414b0ad8406 + checksum: 10/200d48bed7c3a3320f4193e3258e5d9bdacd578e0db87d914d0a9f16b7b8884e5089975382d6c1d6e6244ddef42c53528fee0325f865bcf63dbd5269b25e4cae languageName: node linkType: hard -"@graphql-tools/import@npm:7.0.0": - version: 7.0.0 - resolution: "@graphql-tools/import@npm:7.0.0" +"@graphql-tools/import@npm:7.1.10": + version: 7.1.10 + resolution: "@graphql-tools/import@npm:7.1.10" dependencies: - "@graphql-tools/utils": "npm:^10.0.0" + "@graphql-tools/utils": "npm:^11.0.0" + "@theguild/federation-composition": "npm:^0.21.3" resolve-from: "npm:5.0.0" tslib: "npm:^2.4.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/74741f670fb028526c363cd83871eeb9a1f51ecae27d1640914b0d5ddc482dc0a74d96b996244c726a12e80f63a4f8ec15fc71098e3b87ed3c463fa06ce8ac6c + checksum: 10/4a97c7740395ca398cf537d12b3f6a1b1784cda2d7dcc42f4f3c98a9ee6d965125cc2e500add2f9e25392444896719d16f9b05f0572f2e0c54ef8f218b86f038 languageName: node linkType: hard "@graphql-tools/json-file-loader@npm:^8.0.0": - version: 8.0.0 - resolution: "@graphql-tools/json-file-loader@npm:8.0.0" + version: 8.0.26 + resolution: "@graphql-tools/json-file-loader@npm:8.0.26" dependencies: - "@graphql-tools/utils": "npm:^10.0.0" + "@graphql-tools/utils": "npm:^11.0.0" globby: "npm:^11.0.3" tslib: "npm:^2.4.0" unixify: "npm:^1.0.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/a023466e261599803d1f8e1af3bb7b0007a5206c29df4fb14a448c1dacc04807482b97374c2bbb82bd286523f6a032c355d74f39bffb866325651f1a0f0412a2 + checksum: 10/6e4dc44a9aa3cdd3f7b958b50d98a26e56fb5cc9fab2667011ec12c82685016f269350d6ba2e45dcc7e6d71f8df7d1c4002edb55ce8896ba3e705cad14645ac1 languageName: node linkType: hard "@graphql-tools/load@npm:^8.1.0": - version: 8.1.0 - resolution: "@graphql-tools/load@npm:8.1.0" + version: 8.1.8 + resolution: "@graphql-tools/load@npm:8.1.8" dependencies: - "@graphql-tools/schema": "npm:^10.0.23" - "@graphql-tools/utils": "npm:^10.8.6" + "@graphql-tools/schema": "npm:^10.0.31" + "@graphql-tools/utils": "npm:^11.0.0" p-limit: "npm:3.1.0" tslib: "npm:^2.4.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/4601dda7eb32cb8afed2379102ad82f8a948e478f42c7b1f354a3468ca8dfcdcc2a89e6c6ebcbb574c77eaa80d47f20c27230bdcd6c2d0a3600fa1d6a450cc95 + checksum: 10/a9b24c8d9fc52ebf2b5e0d5dc99212e61704cfe0a07b17a5be3329c391ae01f710e0a2ca6b73b41379e9bee55210c0466d5dfb378a9e3cbe051062a69a07d616 languageName: node linkType: hard @@ -9709,28 +9754,28 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/merge@npm:^9.0.0, @graphql-tools/merge@npm:^9.0.24": - version: 9.0.24 - resolution: "@graphql-tools/merge@npm:9.0.24" +"@graphql-tools/merge@npm:^9.0.0, @graphql-tools/merge@npm:^9.1.7": + version: 9.1.7 + resolution: "@graphql-tools/merge@npm:9.1.7" dependencies: - "@graphql-tools/utils": "npm:^10.8.6" + "@graphql-tools/utils": "npm:^11.0.0" tslib: "npm:^2.4.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/95f77ff141f10d5d726cd8d1ae1ad84ed944c84346bf20461adca9b1543bb94cb524b0347885fe61d3158ccf5ffe1dddec361787ae40bfcc3449aad51528dd77 + checksum: 10/e0b77dfc16e91d7c2450df0b57a85a93e11f0f67e37e396bcf04275d1db8ed1b7257c763ebe6e7f122041d81f00d6aa954fbec531fa6c0b449d195a9aff199cc languageName: node linkType: hard -"@graphql-tools/schema@npm:^10.0.0, @graphql-tools/schema@npm:^10.0.23": - version: 10.0.23 - resolution: "@graphql-tools/schema@npm:10.0.23" +"@graphql-tools/schema@npm:^10.0.29, @graphql-tools/schema@npm:^10.0.31": + version: 10.0.31 + resolution: "@graphql-tools/schema@npm:10.0.31" dependencies: - "@graphql-tools/merge": "npm:^9.0.24" - "@graphql-tools/utils": "npm:^10.8.6" + "@graphql-tools/merge": "npm:^9.1.7" + "@graphql-tools/utils": "npm:^11.0.0" tslib: "npm:^2.4.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/f0960dae161a478941276df1802af09844825c8135e4695b36f5f7e7384f43ff8e1288a67546023fc861951d783327f239112ccf563cb4be1f22038fc78acf21 + checksum: 10/5b775736f8b8454319e07cadc7d41bc5c9cc804a393490aaffd1bb7f59afddb02b498837e870bf98db4a1a989a721d5d8e2fd2b97409d078bac14503c2d4f9cb languageName: node linkType: hard @@ -9748,26 +9793,25 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/url-loader@npm:^8.0.0": - version: 8.0.0 - resolution: "@graphql-tools/url-loader@npm:8.0.0" +"@graphql-tools/url-loader@npm:^9.0.0": + version: 9.0.7 + resolution: "@graphql-tools/url-loader@npm:9.0.7" dependencies: - "@ardatan/sync-fetch": "npm:^0.0.1" - "@graphql-tools/delegate": "npm:^10.0.0" - "@graphql-tools/executor-graphql-ws": "npm:^1.0.0" - "@graphql-tools/executor-http": "npm:^1.0.0" - "@graphql-tools/executor-legacy-ws": "npm:^1.0.0" - "@graphql-tools/utils": "npm:^10.0.0" - "@graphql-tools/wrap": "npm:^10.0.0" + "@graphql-tools/executor-graphql-ws": "npm:^3.1.4" + "@graphql-tools/executor-http": "npm:^3.1.0" + "@graphql-tools/executor-legacy-ws": "npm:^1.1.25" + "@graphql-tools/utils": "npm:^11.0.0" + "@graphql-tools/wrap": "npm:^11.1.1" "@types/ws": "npm:^8.0.0" - "@whatwg-node/fetch": "npm:^0.9.0" + "@whatwg-node/fetch": "npm:^0.10.13" + "@whatwg-node/promise-helpers": "npm:^1.0.0" isomorphic-ws: "npm:^5.0.0" + sync-fetch: "npm:0.6.0" tslib: "npm:^2.4.0" - value-or-promise: "npm:^1.0.11" - ws: "npm:^8.12.0" + ws: "npm:^8.19.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/206065c2490e0747f6f9d756171b151017f9e5ad2d5f4c82c1644af8da3bf03e0075e4c55e6317e1823e74e32d307af5dd102f58851c7c361022578aa52ca8c1 + checksum: 10/292a43974e471a556697aa3ac170d9da7e6e3cc16e9462933f634aa8983cf5752805ba0c580bff1dbd7891168fe5e238b22c02782f88bee5923fa640e451e92e languageName: node linkType: hard @@ -9782,18 +9826,17 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/utils@npm:^10.0.0, @graphql-tools/utils@npm:^10.0.2, @graphql-tools/utils@npm:^10.0.5, @graphql-tools/utils@npm:^10.3.0, @graphql-tools/utils@npm:^10.8.6": - version: 10.8.6 - resolution: "@graphql-tools/utils@npm:10.8.6" +"@graphql-tools/utils@npm:^11.0.0": + version: 11.0.0 + resolution: "@graphql-tools/utils@npm:11.0.0" dependencies: "@graphql-typed-document-node/core": "npm:^3.1.1" "@whatwg-node/promise-helpers": "npm:^1.0.0" cross-inspect: "npm:1.0.1" - dset: "npm:^3.1.4" tslib: "npm:^2.4.0" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/98329aef966b489d3674eb086b784f6fb4500afaf9bc46fbe6a14ca32e98fec480c7395d3488c5eb2f450b75a538e98edf0527ed4bf24af352230e850c914389 + checksum: 10/4cc7577ab85d60908a1d5d448071b318791b798f571cd4b8e4289e0e0eeae9d7183b661a1a7d5da3cedaf5f9b62b936031e3a90d2e17a1c50acbd95d9106ba3c languageName: node linkType: hard @@ -9808,22 +9851,22 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/wrap@npm:^10.0.0": - version: 10.0.1 - resolution: "@graphql-tools/wrap@npm:10.0.1" +"@graphql-tools/wrap@npm:^11.1.1": + version: 11.1.8 + resolution: "@graphql-tools/wrap@npm:11.1.8" dependencies: - "@graphql-tools/delegate": "npm:^10.0.3" - "@graphql-tools/schema": "npm:^10.0.0" - "@graphql-tools/utils": "npm:^10.0.0" - tslib: "npm:^2.4.0" - value-or-promise: "npm:^1.0.12" + "@graphql-tools/delegate": "npm:^12.0.8" + "@graphql-tools/schema": "npm:^10.0.29" + "@graphql-tools/utils": "npm:^11.0.0" + "@whatwg-node/promise-helpers": "npm:^1.3.2" + tslib: "npm:^2.8.1" peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: 10/0ccbb72a6d7bd67c10c86d4cc6da575b84ec2ba6488044dc7dfd6a046f09613d532f0af5549d481f2bfaf2f48c56cb087ea97021ebfe5e104531f80ec72e42a5 + checksum: 10/3ed15d0c92e5deed954a7e117b307af18ef8926132d356465938bbc66669da186aef1cd29cf72135d00ec86a3a149e216f83eaee83eb1fd2a0684a1ba1df473d languageName: node linkType: hard -"@graphql-typed-document-node/core@npm:3.2.0, @graphql-typed-document-node/core@npm:^3.1.1": +"@graphql-typed-document-node/core@npm:^3.1.1, @graphql-typed-document-node/core@npm:^3.2.0": version: 3.2.0 resolution: "@graphql-typed-document-node/core@npm:3.2.0" peerDependencies: @@ -9832,13 +9875,13 @@ __metadata: languageName: node linkType: hard -"@grpc/grpc-js@npm:^1.10.9, @grpc/grpc-js@npm:^1.11.1, @grpc/grpc-js@npm:^1.7.1": - version: 1.12.5 - resolution: "@grpc/grpc-js@npm:1.12.5" +"@grpc/grpc-js@npm:^1.10.9, @grpc/grpc-js@npm:^1.11.1, @grpc/grpc-js@npm:^1.14.3, @grpc/grpc-js@npm:^1.7.1": + version: 1.14.3 + resolution: "@grpc/grpc-js@npm:1.14.3" dependencies: - "@grpc/proto-loader": "npm:^0.7.13" + "@grpc/proto-loader": "npm:^0.8.0" "@js-sdsl/ordered-map": "npm:^4.4.2" - checksum: 10/4f8ead236dcab4d94e15e62d65ad2d93732d37f5cc52ffafe67ae00f69eae4a4c97d6d34a1b9eac9f30206468f2d15302ea6649afcba1d38929afa9d1e7c12d5 + checksum: 10/bb9bfe2f749179ae5ac7774d30486dfa2e0b004518c28de158b248e0f6f65f40138f01635c48266fa540670220f850216726e3724e1eb29d078817581c96e4db languageName: node linkType: hard @@ -9856,6 +9899,20 @@ __metadata: languageName: node linkType: hard +"@grpc/proto-loader@npm:^0.8.0": + version: 0.8.0 + resolution: "@grpc/proto-loader@npm:0.8.0" + dependencies: + lodash.camelcase: "npm:^4.3.0" + long: "npm:^5.0.0" + protobufjs: "npm:^7.5.3" + yargs: "npm:^17.7.2" + bin: + proto-loader-gen-types: build/bin/proto-loader-gen-types.js + checksum: 10/216813bdca52cd3a84ac355ad93c2c3f54252be47327692fe666fd85baa5b1d50aa681ebc5626ab08926564fb2deae3b2ea435aa5bd883197650bbe56f2ae108 + languageName: node + linkType: hard + "@headlessui/react@npm:^1.7.15": version: 1.7.17 resolution: "@headlessui/react@npm:1.7.17" @@ -9869,11 +9926,11 @@ __metadata: linkType: hard "@hono/node-server@npm:^1.19.9": - version: 1.19.9 - resolution: "@hono/node-server@npm:1.19.9" + version: 1.19.10 + resolution: "@hono/node-server@npm:1.19.10" peerDependencies: hono: ^4 - checksum: 10/d4915c2e736ee1e3934b5538cde92b19914dc71346340528a04e4c7219afc7367965080cd1a5291ac9cbda7b0780b89b6ca93472a9418aa105d6d1183033dc8a + checksum: 10/3f4a386bf51d2eb0224522e7485052f8ca6485e9f59fb46cef8fe9578c0d2f35ae26900ed69b6c368250e79944239c70934912f85b833045b14ef19be557aa13 languageName: node linkType: hard @@ -9993,6 +10050,28 @@ __metadata: languageName: node linkType: hard +"@inquirer/core@npm:^6.0.0": + version: 6.0.0 + resolution: "@inquirer/core@npm:6.0.0" + dependencies: + "@inquirer/type": "npm:^1.1.6" + "@types/mute-stream": "npm:^0.0.4" + "@types/node": "npm:^20.10.7" + "@types/wrap-ansi": "npm:^3.0.0" + ansi-escapes: "npm:^4.3.2" + chalk: "npm:^4.1.2" + cli-spinners: "npm:^2.9.2" + cli-width: "npm:^4.1.0" + figures: "npm:^3.2.0" + mute-stream: "npm:^1.0.0" + run-async: "npm:^3.0.0" + signal-exit: "npm:^4.1.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^6.2.0" + checksum: 10/a9f79fe538deab439afc845e16fa8832872b4562be6e39a5de8b50eca3e2b27be0e470fc4ee014f202a750213adc8a06068402d51d6d7b34b118b12b08200f85 + languageName: node + linkType: hard + "@inquirer/external-editor@npm:^1.0.0, @inquirer/external-editor@npm:^1.0.2": version: 1.0.3 resolution: "@inquirer/external-editor@npm:1.0.3" @@ -10015,6 +10094,28 @@ __metadata: languageName: node linkType: hard +"@inquirer/select@npm:1.3.3": + version: 1.3.3 + resolution: "@inquirer/select@npm:1.3.3" + dependencies: + "@inquirer/core": "npm:^6.0.0" + "@inquirer/type": "npm:^1.1.6" + ansi-escapes: "npm:^4.3.2" + chalk: "npm:^4.1.2" + figures: "npm:^3.2.0" + checksum: 10/0f33c51ab69c156b96092bfb107d08dd0f4227274917b9406aa23877e3ba94fd6738800fb973c44c051aaebdba72d07dc328df4b76e6e1285f68aa01a7ed0ed8 + languageName: node + linkType: hard + +"@inquirer/type@npm:^1.1.6": + version: 1.5.5 + resolution: "@inquirer/type@npm:1.5.5" + dependencies: + mute-stream: "npm:^1.0.0" + checksum: 10/bd3f3d7510785af4ad599e042e99e4be6380f52f79f3db140fe6fed0a605acf27b1a0a20fb5cc688eaf7b8aa0c36dacb1d89c7bba4586f38cbf58ba9f159e7b5 + languageName: node + linkType: hard + "@inquirer/type@npm:^3.0.0": version: 3.0.0 resolution: "@inquirer/type@npm:3.0.0" @@ -10116,12 +10217,12 @@ __metadata: languageName: unknown linkType: soft -"@internationalized/date@npm:^3.10.1": - version: 3.10.1 - resolution: "@internationalized/date@npm:3.10.1" +"@internationalized/date@npm:^3.12.0": + version: 3.12.0 + resolution: "@internationalized/date@npm:3.12.0" dependencies: "@swc/helpers": "npm:^0.5.0" - checksum: 10/f259285157e7b9930d7679cea06e09215c4f6773efd093804bd711cff22cd6f3e5afb1208237c177e752da196b10302bb8adf539e76301af49bfad0baa70565c + checksum: 10/0bc2e95385e156362a07a8788e93b4b380b43170cca75c19a9a72cb1840c1c701bb0d1fa67d9ba5e2fbc1e44b5bea29bca02b325d0daa2f960b92a71898ede92 languageName: node linkType: hard @@ -10597,11 +10698,11 @@ __metadata: languageName: node linkType: hard -"@joshwooding/vite-plugin-react-docgen-typescript@npm:^0.6.3": - version: 0.6.3 - resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.3" +"@joshwooding/vite-plugin-react-docgen-typescript@npm:^0.6.4": + version: 0.6.4 + resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.4" dependencies: - glob: "npm:^11.1.0" + glob: "npm:^13.0.1" react-docgen-typescript: "npm:^2.2.2" peerDependencies: typescript: ">= 4.3.x" @@ -10609,7 +10710,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/8a0f2c52b99e0dff4eeee574cf373b794e5398ee3c6b4d272378ccfe3b7dc2f3dc647d446716998ee2d88ee26df617e7b10da949702edcb625ee49e93b7daa95 + checksum: 10/9e1ead0120036e3acb2bfe4772c5c1f352ca43e19e3e59ef37aa7d08843302118a8d2313124c6058af60246058a6aa9c8895683e6d56d56b5c8cf20102078e04 languageName: node linkType: hard @@ -10718,6 +10819,15 @@ __metadata: languageName: node linkType: hard +"@jsonjoy.com/base64@npm:17.67.0": + version: 17.67.0 + resolution: "@jsonjoy.com/base64@npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10/ae44b0c4c83ecc5c0ee1911706a665e18e89d64a2b705cc458d7f6fc3c3c7db0e621261e978d02b74ded6a9fe1aafc8e708eb8a133e794a92bb033c50a0c4ccd + languageName: node + linkType: hard + "@jsonjoy.com/base64@npm:^1.1.2": version: 1.1.2 resolution: "@jsonjoy.com/base64@npm:1.1.2" @@ -10727,6 +10837,15 @@ __metadata: languageName: node linkType: hard +"@jsonjoy.com/buffers@npm:17.67.0, @jsonjoy.com/buffers@npm:^17.65.0": + version: 17.67.0 + resolution: "@jsonjoy.com/buffers@npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10/6c8f6c4c73ec4ddab538a88be0bf72d8a934752755d43b0289fbe19ce9fa6123f082d1cd5ae179495e121a2f50267d26d36641f6dadedd8d5d2a2f980426e8ff + languageName: node + linkType: hard + "@jsonjoy.com/buffers@npm:^1.0.0, @jsonjoy.com/buffers@npm:^1.2.0": version: 1.2.1 resolution: "@jsonjoy.com/buffers@npm:1.2.1" @@ -10736,6 +10855,15 @@ __metadata: languageName: node linkType: hard +"@jsonjoy.com/codegen@npm:17.67.0": + version: 17.67.0 + resolution: "@jsonjoy.com/codegen@npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10/e2462836c708999d045c4a15099f12e721089a3731f0ad33da210559a52ed763b8bddbec3c181857341984ef12ea355290609f37f0dc6f8de1545c028090adf5 + languageName: node + linkType: hard + "@jsonjoy.com/codegen@npm:^1.0.0": version: 1.0.0 resolution: "@jsonjoy.com/codegen@npm:1.0.0" @@ -10745,6 +10873,109 @@ __metadata: languageName: node linkType: hard +"@jsonjoy.com/fs-core@npm:4.56.11": + version: 4.56.11 + resolution: "@jsonjoy.com/fs-core@npm:4.56.11" + dependencies: + "@jsonjoy.com/fs-node-builtins": "npm:4.56.11" + "@jsonjoy.com/fs-node-utils": "npm:4.56.11" + thingies: "npm:^2.5.0" + peerDependencies: + tslib: 2 + checksum: 10/46adecf53d79246eed43503e999f3068b38d6c5b209e305c5b6ca55cfbb68fd2780a2fe3ddc2de3535c51a6db76e4acaca67dc127d1ce54399cf786a5ccbd4ca + languageName: node + linkType: hard + +"@jsonjoy.com/fs-fsa@npm:4.56.11": + version: 4.56.11 + resolution: "@jsonjoy.com/fs-fsa@npm:4.56.11" + dependencies: + "@jsonjoy.com/fs-core": "npm:4.56.11" + "@jsonjoy.com/fs-node-builtins": "npm:4.56.11" + "@jsonjoy.com/fs-node-utils": "npm:4.56.11" + thingies: "npm:^2.5.0" + peerDependencies: + tslib: 2 + checksum: 10/ed322ec4596f0b385d17dfeca6f2e1c2de51aa7c356f092700ad7b8930816f27f7f49234ba5eb544f03d173ea3cf5f9509597a5c64f0c03d2c4c456b751e2a16 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node-builtins@npm:4.56.11": + version: 4.56.11 + resolution: "@jsonjoy.com/fs-node-builtins@npm:4.56.11" + peerDependencies: + tslib: 2 + checksum: 10/04ca8c56cd3b883149b42cce5d9376d18445a071b6d88c5366f930c2ba93505a7d708ad8f5af1a15e691dd87ffb971048b906450287d5faf363472ced43ea884 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node-to-fsa@npm:4.56.11": + version: 4.56.11 + resolution: "@jsonjoy.com/fs-node-to-fsa@npm:4.56.11" + dependencies: + "@jsonjoy.com/fs-fsa": "npm:4.56.11" + "@jsonjoy.com/fs-node-builtins": "npm:4.56.11" + "@jsonjoy.com/fs-node-utils": "npm:4.56.11" + peerDependencies: + tslib: 2 + checksum: 10/e4e8b6ea18969665925b97055cb06636fbd74e5025cd126280ede2245ba2efe328146e0b7a5e45c45a64175dc74231caa982de4dda51bffb061c028c931f2fad + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node-utils@npm:4.56.11": + version: 4.56.11 + resolution: "@jsonjoy.com/fs-node-utils@npm:4.56.11" + dependencies: + "@jsonjoy.com/fs-node-builtins": "npm:4.56.11" + peerDependencies: + tslib: 2 + checksum: 10/72ca92de077c34d26c935b1ec6011de587bc2f382e822d9e57c76318d506f3c40f5075d04ede666b59d45e275e577ea77032a10b48161483040f3e3f7c381232 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-node@npm:4.56.11": + version: 4.56.11 + resolution: "@jsonjoy.com/fs-node@npm:4.56.11" + dependencies: + "@jsonjoy.com/fs-core": "npm:4.56.11" + "@jsonjoy.com/fs-node-builtins": "npm:4.56.11" + "@jsonjoy.com/fs-node-utils": "npm:4.56.11" + "@jsonjoy.com/fs-print": "npm:4.56.11" + "@jsonjoy.com/fs-snapshot": "npm:4.56.11" + glob-to-regex.js: "npm:^1.0.0" + thingies: "npm:^2.5.0" + peerDependencies: + tslib: 2 + checksum: 10/a6a7e84de467d644bd2b23f34afaf61e93eb05245f40325a067403d2af5b4b22adce806639b80ff0f8431049dda706a803e6f73f2bf944d73a1ad78ac04bbd15 + languageName: node + linkType: hard + +"@jsonjoy.com/fs-print@npm:4.56.11": + version: 4.56.11 + resolution: "@jsonjoy.com/fs-print@npm:4.56.11" + dependencies: + "@jsonjoy.com/fs-node-utils": "npm:4.56.11" + tree-dump: "npm:^1.1.0" + peerDependencies: + tslib: 2 + checksum: 10/352fb4932c0ed44b5fb148b253754748b6d3e9859452ef1b03cebb6b9601e9f84336ef0932ca4de9687b2f3ec703083d4572c7bf2036537f787673229c3ed68b + languageName: node + linkType: hard + +"@jsonjoy.com/fs-snapshot@npm:4.56.11": + version: 4.56.11 + resolution: "@jsonjoy.com/fs-snapshot@npm:4.56.11" + dependencies: + "@jsonjoy.com/buffers": "npm:^17.65.0" + "@jsonjoy.com/fs-node-utils": "npm:4.56.11" + "@jsonjoy.com/json-pack": "npm:^17.65.0" + "@jsonjoy.com/util": "npm:^17.65.0" + peerDependencies: + tslib: 2 + checksum: 10/97665962c5bf609720ac2913111da540235fa8aed52241e017da16ae2ac0228ae9e1938906d51e61f85fdaa0511c46db22b3c8212c9ba77a0ae4082cd20e4d57 + languageName: node + linkType: hard + "@jsonjoy.com/json-pack@npm:^1.11.0": version: 1.21.0 resolution: "@jsonjoy.com/json-pack@npm:1.21.0" @@ -10763,6 +10994,35 @@ __metadata: languageName: node linkType: hard +"@jsonjoy.com/json-pack@npm:^17.65.0": + version: 17.67.0 + resolution: "@jsonjoy.com/json-pack@npm:17.67.0" + dependencies: + "@jsonjoy.com/base64": "npm:17.67.0" + "@jsonjoy.com/buffers": "npm:17.67.0" + "@jsonjoy.com/codegen": "npm:17.67.0" + "@jsonjoy.com/json-pointer": "npm:17.67.0" + "@jsonjoy.com/util": "npm:17.67.0" + hyperdyperid: "npm:^1.2.0" + thingies: "npm:^2.5.0" + tree-dump: "npm:^1.1.0" + peerDependencies: + tslib: 2 + checksum: 10/9ff4403862e49433fe607175e90af749d64902640d63919ba559e5748d1a3db60d7366cc3b84dcc4a57ad478540e5eecb22fed80766e293482a0ab8e583b1b0b + languageName: node + linkType: hard + +"@jsonjoy.com/json-pointer@npm:17.67.0": + version: 17.67.0 + resolution: "@jsonjoy.com/json-pointer@npm:17.67.0" + dependencies: + "@jsonjoy.com/util": "npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10/5a27c6b5b1276d357cfc3e8a05112d6305ccd17bf672190f25dfac2f4108ced170e784451d64728f60f93305c0007e3f832ddd175b8a47f3eb652cbabcec31ad + languageName: node + linkType: hard + "@jsonjoy.com/json-pointer@npm:^1.0.2": version: 1.0.2 resolution: "@jsonjoy.com/json-pointer@npm:1.0.2" @@ -10775,6 +11035,18 @@ __metadata: languageName: node linkType: hard +"@jsonjoy.com/util@npm:17.67.0, @jsonjoy.com/util@npm:^17.65.0": + version: 17.67.0 + resolution: "@jsonjoy.com/util@npm:17.67.0" + dependencies: + "@jsonjoy.com/buffers": "npm:17.67.0" + "@jsonjoy.com/codegen": "npm:17.67.0" + peerDependencies: + tslib: 2 + checksum: 10/b0facf65c3190d6ed1ada7e5b7679d80fa5da73bfbd02f2bb2f3af1c28c0d854b6ee2350824313b7ba82c0e5191da94903b4af61255bc232dbb7feedd2f31e0c + languageName: node + linkType: hard + "@jsonjoy.com/util@npm:^1.9.0": version: 1.9.0 resolution: "@jsonjoy.com/util@npm:1.9.0" @@ -11305,8 +11577,8 @@ __metadata: linkType: hard "@modelcontextprotocol/sdk@npm:^1.25.2": - version: 1.26.0 - resolution: "@modelcontextprotocol/sdk@npm:1.26.0" + version: 1.27.1 + resolution: "@modelcontextprotocol/sdk@npm:1.27.1" dependencies: "@hono/node-server": "npm:^1.19.9" ajv: "npm:^8.17.1" @@ -11333,7 +11605,7 @@ __metadata: optional: true zod: optional: false - checksum: 10/a206b2a4d61a23be8b8f4c886528dd9348d11b17ce36013b350edf5c082b1c1f07941d52ea098f721daf3828085b6f6276bb844c484a0e9913edbc028517a3d5 + checksum: 10/3cb0d61cfb916e555c85b4a527e772f88fcf9c6abacbe5eb5e965aac7c898190c416341ab3b3cba8c2d5f5ce4d513279fba3ad7784a0903d7ccd335decc55395 languageName: node linkType: hard @@ -11947,9 +12219,9 @@ __metadata: languageName: node linkType: hard -"@nestjs/common@npm:11.1.13": - version: 11.1.13 - resolution: "@nestjs/common@npm:11.1.13" +"@nestjs/common@npm:11.1.16": + version: 11.1.16 + resolution: "@nestjs/common@npm:11.1.16" dependencies: file-type: "npm:21.3.0" iterare: "npm:1.2.1" @@ -11966,13 +12238,13 @@ __metadata: optional: true class-validator: optional: true - checksum: 10/85d865d45a36c3b7082c6167161022a51574e4a6b0e1a1d9160ebde0e26884597ec9f78343a9f944bb89c65b2f93a8102c00e168c55e2348610a506078560f5b + checksum: 10/23b0bd734333f17c97712d441d5ba0ff1981968f4fe1d3b3b8b0eafd3598c97f16b40e29d14f9cbf0dd3dfd98d665fe532f85b91dabc36c82f0783cb31160b30 languageName: node linkType: hard -"@nestjs/core@npm:11.1.13": - version: 11.1.13 - resolution: "@nestjs/core@npm:11.1.13" +"@nestjs/core@npm:11.1.16": + version: 11.1.16 + resolution: "@nestjs/core@npm:11.1.16" dependencies: "@nuxt/opencollective": "npm:0.4.1" fast-safe-stringify: "npm:2.1.1" @@ -11994,7 +12266,7 @@ __metadata: optional: true "@nestjs/websockets": optional: true - checksum: 10/63b5954510ca26b4ab908e54abaf76cb2a9fd121f78750cb4fe59f0abb93bc558c5b9e593f256355d3ac9045a7545d4f7c827749aea601a8b11f805a49668b98 + checksum: 10/a4e53b3877a503c78c763b2535307a5c43133e114ee826640b1e4d8feb5142a36e16f5db591e8d2077626a28e4e5cf2076fbacc217e0141557e21a3b85649326 languageName: node linkType: hard @@ -13002,29 +13274,29 @@ __metadata: linkType: hard "@openapitools/openapi-generator-cli@npm:^2.4.26, @openapitools/openapi-generator-cli@npm:^2.7.0": - version: 2.29.0 - resolution: "@openapitools/openapi-generator-cli@npm:2.29.0" + version: 2.30.2 + resolution: "@openapitools/openapi-generator-cli@npm:2.30.2" dependencies: + "@inquirer/select": "npm:1.3.3" "@nestjs/axios": "npm:4.0.1" - "@nestjs/common": "npm:11.1.13" - "@nestjs/core": "npm:11.1.13" + "@nestjs/common": "npm:11.1.16" + "@nestjs/core": "npm:11.1.16" "@nuxtjs/opencollective": "npm:0.3.2" - axios: "npm:1.13.5" + axios: "npm:^1.13.6" chalk: "npm:4.1.2" commander: "npm:8.3.0" compare-versions: "npm:6.1.1" concurrently: "npm:9.2.1" console.table: "npm:0.10.0" - fs-extra: "npm:11.3.3" - glob: "npm:13.0.3" - inquirer: "npm:8.2.7" + fs-extra: "npm:11.3.4" + glob: "npm:13.0.6" 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/84c399bb1fce724eb346a09ec85610215662171c5ffa45ab85cc4e31a82d21dbd28d06f1d19ae0992e0fd7b7fab82a238e1e1c56bb4916d89da1e0bd53a7aff4 + checksum: 10/247d3e3fce575440a6ce45c28b5851b4f07c6dd414a1f71526109e69f3768f1379871f92f822075625fd95c038cd5e8c11f486060a3f1c5a82ac3e5ec620acc7 languageName: node linkType: hard @@ -13042,15 +13314,6 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/api-logs@npm:0.208.0, @opentelemetry/api-logs@npm:^0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/api-logs@npm:0.208.0" - dependencies: - "@opentelemetry/api": "npm:^1.3.0" - checksum: 10/ae339416a244e90b1718af1ed5430348188be60871f3799c847bab409bba1513337cac7da40b4883bf7f280680754319b44a9dc95fa2879d15c0413c9955b145 - languageName: node - linkType: hard - "@opentelemetry/api-logs@npm:0.211.0": version: 0.211.0 resolution: "@opentelemetry/api-logs@npm:0.211.0" @@ -13060,6 +13323,15 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/api-logs@npm:0.213.0, @opentelemetry/api-logs@npm:^0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/api-logs@npm:0.213.0" + dependencies: + "@opentelemetry/api": "npm:^1.3.0" + checksum: 10/9b2d030d8534520f23e3903ccf64dc479fb4d37fcf5ca265ca7de439ec8dd5c849aaa62068278cd97879da7c9528e34c7162cc7bbd025dd8d74667843adc151f + languageName: node + linkType: hard + "@opentelemetry/api@npm:^1.3.0, @opentelemetry/api@npm:^1.4.0, @opentelemetry/api@npm:^1.9.0, @opentelemetry/api@npm:~1.9.0": version: 1.9.0 resolution: "@opentelemetry/api@npm:1.9.0" @@ -13067,63 +13339,63 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/auto-instrumentations-node@npm:^0.67.0": - version: 0.67.3 - resolution: "@opentelemetry/auto-instrumentations-node@npm:0.67.3" +"@opentelemetry/auto-instrumentations-node@npm:^0.71.0": + version: 0.71.0 + resolution: "@opentelemetry/auto-instrumentations-node@npm:0.71.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" - "@opentelemetry/instrumentation-amqplib": "npm:^0.56.0" - "@opentelemetry/instrumentation-aws-lambda": "npm:^0.61.1" - "@opentelemetry/instrumentation-aws-sdk": "npm:^0.64.1" - "@opentelemetry/instrumentation-bunyan": "npm:^0.54.0" - "@opentelemetry/instrumentation-cassandra-driver": "npm:^0.54.1" - "@opentelemetry/instrumentation-connect": "npm:^0.52.0" - "@opentelemetry/instrumentation-cucumber": "npm:^0.24.0" - "@opentelemetry/instrumentation-dataloader": "npm:^0.26.1" - "@opentelemetry/instrumentation-dns": "npm:^0.52.0" - "@opentelemetry/instrumentation-express": "npm:^0.57.1" - "@opentelemetry/instrumentation-fastify": "npm:^0.53.1" - "@opentelemetry/instrumentation-fs": "npm:^0.28.0" - "@opentelemetry/instrumentation-generic-pool": "npm:^0.52.0" - "@opentelemetry/instrumentation-graphql": "npm:^0.56.0" - "@opentelemetry/instrumentation-grpc": "npm:^0.208.0" - "@opentelemetry/instrumentation-hapi": "npm:^0.55.1" - "@opentelemetry/instrumentation-http": "npm:^0.208.0" - "@opentelemetry/instrumentation-ioredis": "npm:^0.57.0" - "@opentelemetry/instrumentation-kafkajs": "npm:^0.18.1" - "@opentelemetry/instrumentation-knex": "npm:^0.53.1" - "@opentelemetry/instrumentation-koa": "npm:^0.57.1" - "@opentelemetry/instrumentation-lru-memoizer": "npm:^0.53.1" - "@opentelemetry/instrumentation-memcached": "npm:^0.52.1" - "@opentelemetry/instrumentation-mongodb": "npm:^0.62.0" - "@opentelemetry/instrumentation-mongoose": "npm:^0.55.1" - "@opentelemetry/instrumentation-mysql": "npm:^0.55.0" - "@opentelemetry/instrumentation-mysql2": "npm:^0.55.1" - "@opentelemetry/instrumentation-nestjs-core": "npm:^0.55.0" - "@opentelemetry/instrumentation-net": "npm:^0.53.0" - "@opentelemetry/instrumentation-openai": "npm:^0.7.1" - "@opentelemetry/instrumentation-oracledb": "npm:^0.34.1" - "@opentelemetry/instrumentation-pg": "npm:^0.61.2" - "@opentelemetry/instrumentation-pino": "npm:^0.55.1" - "@opentelemetry/instrumentation-redis": "npm:^0.57.2" - "@opentelemetry/instrumentation-restify": "npm:^0.54.0" - "@opentelemetry/instrumentation-router": "npm:^0.53.0" - "@opentelemetry/instrumentation-runtime-node": "npm:^0.22.0" - "@opentelemetry/instrumentation-socket.io": "npm:^0.55.1" - "@opentelemetry/instrumentation-tedious": "npm:^0.28.0" - "@opentelemetry/instrumentation-undici": "npm:^0.19.0" - "@opentelemetry/instrumentation-winston": "npm:^0.53.0" - "@opentelemetry/resource-detector-alibaba-cloud": "npm:^0.32.0" - "@opentelemetry/resource-detector-aws": "npm:^2.9.0" - "@opentelemetry/resource-detector-azure": "npm:^0.17.0" - "@opentelemetry/resource-detector-container": "npm:^0.8.0" - "@opentelemetry/resource-detector-gcp": "npm:^0.44.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/instrumentation-amqplib": "npm:^0.60.0" + "@opentelemetry/instrumentation-aws-lambda": "npm:^0.65.0" + "@opentelemetry/instrumentation-aws-sdk": "npm:^0.68.0" + "@opentelemetry/instrumentation-bunyan": "npm:^0.58.0" + "@opentelemetry/instrumentation-cassandra-driver": "npm:^0.58.0" + "@opentelemetry/instrumentation-connect": "npm:^0.56.0" + "@opentelemetry/instrumentation-cucumber": "npm:^0.29.0" + "@opentelemetry/instrumentation-dataloader": "npm:^0.30.0" + "@opentelemetry/instrumentation-dns": "npm:^0.56.0" + "@opentelemetry/instrumentation-express": "npm:^0.61.0" + "@opentelemetry/instrumentation-fastify": "npm:^0.57.0" + "@opentelemetry/instrumentation-fs": "npm:^0.32.0" + "@opentelemetry/instrumentation-generic-pool": "npm:^0.56.0" + "@opentelemetry/instrumentation-graphql": "npm:^0.61.0" + "@opentelemetry/instrumentation-grpc": "npm:^0.213.0" + "@opentelemetry/instrumentation-hapi": "npm:^0.59.0" + "@opentelemetry/instrumentation-http": "npm:^0.213.0" + "@opentelemetry/instrumentation-ioredis": "npm:^0.61.0" + "@opentelemetry/instrumentation-kafkajs": "npm:^0.22.0" + "@opentelemetry/instrumentation-knex": "npm:^0.57.0" + "@opentelemetry/instrumentation-koa": "npm:^0.61.0" + "@opentelemetry/instrumentation-lru-memoizer": "npm:^0.57.0" + "@opentelemetry/instrumentation-memcached": "npm:^0.56.0" + "@opentelemetry/instrumentation-mongodb": "npm:^0.66.0" + "@opentelemetry/instrumentation-mongoose": "npm:^0.59.0" + "@opentelemetry/instrumentation-mysql": "npm:^0.59.0" + "@opentelemetry/instrumentation-mysql2": "npm:^0.59.0" + "@opentelemetry/instrumentation-nestjs-core": "npm:^0.59.0" + "@opentelemetry/instrumentation-net": "npm:^0.57.0" + "@opentelemetry/instrumentation-openai": "npm:^0.11.0" + "@opentelemetry/instrumentation-oracledb": "npm:^0.38.0" + "@opentelemetry/instrumentation-pg": "npm:^0.65.0" + "@opentelemetry/instrumentation-pino": "npm:^0.59.0" + "@opentelemetry/instrumentation-redis": "npm:^0.61.0" + "@opentelemetry/instrumentation-restify": "npm:^0.58.0" + "@opentelemetry/instrumentation-router": "npm:^0.57.0" + "@opentelemetry/instrumentation-runtime-node": "npm:^0.26.0" + "@opentelemetry/instrumentation-socket.io": "npm:^0.60.0" + "@opentelemetry/instrumentation-tedious": "npm:^0.32.0" + "@opentelemetry/instrumentation-undici": "npm:^0.23.0" + "@opentelemetry/instrumentation-winston": "npm:^0.57.0" + "@opentelemetry/resource-detector-alibaba-cloud": "npm:^0.33.3" + "@opentelemetry/resource-detector-aws": "npm:^2.13.0" + "@opentelemetry/resource-detector-azure": "npm:^0.21.0" + "@opentelemetry/resource-detector-container": "npm:^0.8.4" + "@opentelemetry/resource-detector-gcp": "npm:^0.48.0" "@opentelemetry/resources": "npm:^2.0.0" - "@opentelemetry/sdk-node": "npm:^0.208.0" + "@opentelemetry/sdk-node": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.4.1 "@opentelemetry/core": ^2.0.0 - checksum: 10/e14b9b8182ada889d4f0ddfdce46f0ce2d07295807549e6785da6f0292e75b2e36f9b248e01b45fd4057be8f8c813f9325d4936a8e0bb8bded0bf934dd2fd370 + checksum: 10/a31c3a9118b9cdf8e9a44f9a26c2c4d3bebc9f3c23f3f7bb37a6de0d9ab93bf3f1f46ddc4862998ac9cf11c6a4d169a05dfb1a5b02984335bfba5b6348122c3d languageName: node linkType: hard @@ -13139,12 +13411,15 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/context-async-hooks@npm:2.2.0": - version: 2.2.0 - resolution: "@opentelemetry/context-async-hooks@npm:2.2.0" +"@opentelemetry/configuration@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/configuration@npm:0.213.0" + dependencies: + "@opentelemetry/core": "npm:2.6.0" + yaml: "npm:^2.0.0" peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/00ee1a35ce9f96632955830d54672025db6d4dc77d440c183cd9751efd5bdfeb8a078094d3d1caaf8b3c250def098990a98bfd8254b1ec04b759f1e3f2fc224e + "@opentelemetry/api": ^1.9.0 + checksum: 10/2bf7ef3a73849d6c618cf04f469009cf46c004df43c014e2f2a60b17692b85eec261fb362f1d4d7276fd128681d35ac07493cb4ef743505fec5d6cbff1a446e2 languageName: node linkType: hard @@ -13157,18 +13432,16 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/core@npm:2.2.0": - version: 2.2.0 - resolution: "@opentelemetry/core@npm:2.2.0" - dependencies: - "@opentelemetry/semantic-conventions": "npm:^1.29.0" +"@opentelemetry/context-async-hooks@npm:2.6.0": + version: 2.6.0 + resolution: "@opentelemetry/context-async-hooks@npm:2.6.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/f25193ba8b1fadb7bd8ed0d86ac39dd0f3fd3eec47c2fb2745bd22442b2d5e3ca88e5cab6d97111349d3182bf8e4356f8b7c7213ebea8f7719de944ce13a19cb + checksum: 10/a1f746fb9bb25b4c40c0da4cc68a7412e82f120a6ddc80dcf0117432418e64c947527bca87895ebce4211a09992863a75a0f5b5f8e7185a573244cccb4809c42 languageName: node linkType: hard -"@opentelemetry/core@npm:2.5.0, @opentelemetry/core@npm:^2.0.0": +"@opentelemetry/core@npm:2.5.0": version: 2.5.0 resolution: "@opentelemetry/core@npm:2.5.0" dependencies: @@ -13179,6 +13452,17 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/core@npm:2.6.0, @opentelemetry/core@npm:^2.0.0": + version: 2.6.0 + resolution: "@opentelemetry/core@npm:2.6.0" + dependencies: + "@opentelemetry/semantic-conventions": "npm:^1.29.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/21c017cc68fe7836d06ecac31abeba6ad610dd42e9c1cb9562cd13eed3f644c48111a1fc7d00dfc2b7cc179d40f059482c69c978c24352ac0c605f30686a01a4 + languageName: node + linkType: hard + "@opentelemetry/core@npm:^1.29.0": version: 1.30.1 resolution: "@opentelemetry/core@npm:1.30.1" @@ -13190,22 +13474,6 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-grpc@npm:0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.208.0" - dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/otlp-exporter-base": "npm:0.208.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.208.0" - "@opentelemetry/otlp-transformer": "npm:0.208.0" - "@opentelemetry/sdk-logs": "npm:0.208.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/ed1d00184b8d03aed376a39363c1561eb4d26155147b53dda36f9c9c29e1e144a783c8d5dbeb1f91a157243777d7261b5e24c38202c5cec6d85dc6c45643d96c - languageName: node - linkType: hard - "@opentelemetry/exporter-logs-otlp-grpc@npm:0.211.0": version: 0.211.0 resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.211.0" @@ -13222,18 +13490,19 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-http@npm:0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/exporter-logs-otlp-http@npm:0.208.0" +"@opentelemetry/exporter-logs-otlp-grpc@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.213.0" dependencies: - "@opentelemetry/api-logs": "npm:0.208.0" - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/otlp-exporter-base": "npm:0.208.0" - "@opentelemetry/otlp-transformer": "npm:0.208.0" - "@opentelemetry/sdk-logs": "npm:0.208.0" + "@grpc/grpc-js": "npm:^1.14.3" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/sdk-logs": "npm:0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/b200e95bc71bc3c6591ef1f38a37ae27de5d2164a0e0df2b319369f026aaa6df080ed5d5fab02906b35f4c6e02a829ba0b09e041b506ade4b5c3240caafc420d + checksum: 10/5c852a8dbf9f1fc00b8af22689a1971af8db6a4d1c3ddcce516877a07b5b40c28dbb6c71549a73c8e8953db3602a8671666f22d1f0aca40fa1f0c1fbbbfbff33 languageName: node linkType: hard @@ -13252,20 +13521,18 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-proto@npm:0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/exporter-logs-otlp-proto@npm:0.208.0" +"@opentelemetry/exporter-logs-otlp-http@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-logs-otlp-http@npm:0.213.0" dependencies: - "@opentelemetry/api-logs": "npm:0.208.0" - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/otlp-exporter-base": "npm:0.208.0" - "@opentelemetry/otlp-transformer": "npm:0.208.0" - "@opentelemetry/resources": "npm:2.2.0" - "@opentelemetry/sdk-logs": "npm:0.208.0" - "@opentelemetry/sdk-trace-base": "npm:2.2.0" + "@opentelemetry/api-logs": "npm:0.213.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/sdk-logs": "npm:0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/71e11a3cb0fdca976ce644443846e4aaaad915848c99a212369e6b2b0cba664bf0cfbccc270f1d4210c6fc925b08a2dfb58f263d219fac39c5243993dcba7aa1 + checksum: 10/f9ee8f8d3d7113b96416c658ba17c9183e23365ba1ee1f3e3124a435f0d989367b6569cce201defcc295650af22ffbe766614704f19c5361caf4a058817184b6 languageName: node linkType: hard @@ -13286,21 +13553,20 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-metrics-otlp-grpc@npm:0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/exporter-metrics-otlp-grpc@npm:0.208.0" +"@opentelemetry/exporter-logs-otlp-proto@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-logs-otlp-proto@npm:0.213.0" dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/exporter-metrics-otlp-http": "npm:0.208.0" - "@opentelemetry/otlp-exporter-base": "npm:0.208.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.208.0" - "@opentelemetry/otlp-transformer": "npm:0.208.0" - "@opentelemetry/resources": "npm:2.2.0" - "@opentelemetry/sdk-metrics": "npm:2.2.0" + "@opentelemetry/api-logs": "npm:0.213.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-logs": "npm:0.213.0" + "@opentelemetry/sdk-trace-base": "npm:2.6.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/495ac28c27781750ae19d1e6405e5dd9ff277547e4ab1015e4b3fd2a1d8c26b0977a62dd20ac4e263a980bbace6a2fbe8fed91ae289dddfed6a5a55db0df861e + checksum: 10/4229f62eb802da49e75820188f77b12c3fdde4f0522e1df01cc097307baba8b141587d78695edd71343f40e811e946909f5c648436edfeef6b03e367e340b7e6 languageName: node linkType: hard @@ -13322,18 +13588,21 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-metrics-otlp-http@npm:0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/exporter-metrics-otlp-http@npm:0.208.0" +"@opentelemetry/exporter-metrics-otlp-grpc@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-metrics-otlp-grpc@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/otlp-exporter-base": "npm:0.208.0" - "@opentelemetry/otlp-transformer": "npm:0.208.0" - "@opentelemetry/resources": "npm:2.2.0" - "@opentelemetry/sdk-metrics": "npm:2.2.0" + "@grpc/grpc-js": "npm:^1.14.3" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.213.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-metrics": "npm:2.6.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/ea4e14481e3a7cd08574f811bf62527305a735972818cc4bb4b2232d19c65732200266de16ae0a59dafd8aa27b7acff77af6e3e300df457841b7e2f5f97ee43b + checksum: 10/b1914ec0a2017312b69320214f09c3c2319baf4da5da3377c2d9baf6ec85ebaf7c4cb6ea1d129224bf40f8fb8225072b8fbfa089524ab7383eda0251d4f4c724 languageName: node linkType: hard @@ -13352,19 +13621,18 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-metrics-otlp-proto@npm:0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/exporter-metrics-otlp-proto@npm:0.208.0" +"@opentelemetry/exporter-metrics-otlp-http@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-metrics-otlp-http@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/exporter-metrics-otlp-http": "npm:0.208.0" - "@opentelemetry/otlp-exporter-base": "npm:0.208.0" - "@opentelemetry/otlp-transformer": "npm:0.208.0" - "@opentelemetry/resources": "npm:2.2.0" - "@opentelemetry/sdk-metrics": "npm:2.2.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-metrics": "npm:2.6.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/06d96d582294e3f792508f49a2138b926bde85545057260a97bb7aaed7b935f424e41c25c9b94f5781a1902bc77bcf9c0308d4a8d9e9e1539eb89567adb8b28f + checksum: 10/1cbb059b2b3d7b1a183c5ce1b4aba1a7a7e43dd6983af30191be19f406930914fc666dc20e0f8db4422cf2a83b0c6416612663e74c5cfedd8e2487fbd20257ea languageName: node linkType: hard @@ -13384,16 +13652,19 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-prometheus@npm:0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/exporter-prometheus@npm:0.208.0" +"@opentelemetry/exporter-metrics-otlp-proto@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-metrics-otlp-proto@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/resources": "npm:2.2.0" - "@opentelemetry/sdk-metrics": "npm:2.2.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.213.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-metrics": "npm:2.6.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/3aae6be7f694e8e7add17c76f7c37660003026fc1feb3f5e2d4b6ff171426a96fd595384b7ecca19a5b2a58d887a3fc87906d4524dd8fb8424a1022d595dabb6 + checksum: 10/c1d9d94cf2ccf4daf495046f2d00675985fcfe02f1ed5db538c9ea040123cae767c7ae29f470dca138bcbe7dbea2c92f9accd8e4e27f540792c6f411169c7bb1 languageName: node linkType: hard @@ -13410,20 +13681,17 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-trace-otlp-grpc@npm:0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.208.0" +"@opentelemetry/exporter-prometheus@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-prometheus@npm:0.213.0" dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/otlp-exporter-base": "npm:0.208.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.208.0" - "@opentelemetry/otlp-transformer": "npm:0.208.0" - "@opentelemetry/resources": "npm:2.2.0" - "@opentelemetry/sdk-trace-base": "npm:2.2.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-metrics": "npm:2.6.0" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/5405fb3ecf2ab9fcb8279e13ed89eb490ea645cfd9662a064d9bbd7e97c8ff838cd52908d14e27509aa9d00dbded22a9bc707436c54220c114d29c51abddb31f + checksum: 10/037631feebd40b1866f1eafd0e16fa09b11da5ab869a744768aad4defba26db1c03e1d1e84c806c478123719ec2e7390b41e31e3049a3a4ad8d5dafbc51e34af languageName: node linkType: hard @@ -13444,18 +13712,20 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-trace-otlp-http@npm:0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.208.0" +"@opentelemetry/exporter-trace-otlp-grpc@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/otlp-exporter-base": "npm:0.208.0" - "@opentelemetry/otlp-transformer": "npm:0.208.0" - "@opentelemetry/resources": "npm:2.2.0" - "@opentelemetry/sdk-trace-base": "npm:2.2.0" + "@grpc/grpc-js": "npm:^1.14.3" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-trace-base": "npm:2.6.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/69cbd8a223ddd774db97fe3b8e55063ac02e78502a7378596c14101dc98256cef3a5ef0092808ab73d42d09393e000a9eb2041c0d3c087efeb3864bb57f49907 + checksum: 10/f3b7e98f28427f2459c635581f659bc9b1494620001a23d2ef5ee1b48e4f6730a5fae7d958692aa24445ea084b9938e9bc6ea2ca24bde6ae75419005e5d4d49a languageName: node linkType: hard @@ -13474,18 +13744,18 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-trace-otlp-proto@npm:0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/exporter-trace-otlp-proto@npm:0.208.0" +"@opentelemetry/exporter-trace-otlp-http@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/otlp-exporter-base": "npm:0.208.0" - "@opentelemetry/otlp-transformer": "npm:0.208.0" - "@opentelemetry/resources": "npm:2.2.0" - "@opentelemetry/sdk-trace-base": "npm:2.2.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-trace-base": "npm:2.6.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/af948730fd917471c220fcb9ad5c05bb3ab365e1cd8429fb69f50e855aa76d516a64307c52bb7e756fdd768c02806940d5fe060506191d231903e7db158dfb12 + checksum: 10/e8f6ad2eee843625e9e1efd9e47b1325dde1e3e9e7a4e2cfe4f5a6bece4e2e422431270b4530a87383c36d365f65a2ebee6ca5bab797d86ec585407dd3cd675f languageName: node linkType: hard @@ -13504,17 +13774,18 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-zipkin@npm:2.2.0": - version: 2.2.0 - resolution: "@opentelemetry/exporter-zipkin@npm:2.2.0" +"@opentelemetry/exporter-trace-otlp-proto@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/exporter-trace-otlp-proto@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/resources": "npm:2.2.0" - "@opentelemetry/sdk-trace-base": "npm:2.2.0" - "@opentelemetry/semantic-conventions": "npm:^1.29.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-trace-base": "npm:2.6.0" peerDependencies: - "@opentelemetry/api": ^1.0.0 - checksum: 10/5e7b1464810ab2de77cee437dfc4436ade5c5391a9b80cdd5c1495f3aa32cf8e4cf458cae327dc477260876a8a9ef01bdb5bb3a41d37aaaaf392f2a00185d238 + "@opentelemetry/api": ^1.3.0 + checksum: 10/839d7670e1598a25e5a76bf066cdd0951b5eff794f3f1393b1eb32a7d4b9e3b7a53cdadfd5279a0631dacff7a2a9c034de21562a65a4520b7493e5f07409891f languageName: node linkType: hard @@ -13532,526 +13803,530 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/instrumentation-amqplib@npm:^0.56.0": - version: 0.56.0 - resolution: "@opentelemetry/instrumentation-amqplib@npm:0.56.0" +"@opentelemetry/exporter-zipkin@npm:2.6.0": + version: 2.6.0 + resolution: "@opentelemetry/exporter-zipkin@npm:2.6.0" dependencies: - "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.208.0" - "@opentelemetry/semantic-conventions": "npm:^1.33.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-trace-base": "npm:2.6.0" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/433377a73ba83f9b1008ab9868a686109244bb51ea87b6ffa54939b23fc4ac63ac1dafdb2d9b9720382d194ba638135cd0762277ae0a2e86f6a5b02aa4513ca8 + "@opentelemetry/api": ^1.0.0 + checksum: 10/dcae93429cf76880e422634a9da41b6c026d968e1a18413d5318bf55540539dc3ec949976b391f8254e92567dbfa144bc3db208dfd79489c318d24d10062c234 languageName: node linkType: hard -"@opentelemetry/instrumentation-aws-lambda@npm:^0.61.1": - version: 0.61.1 - resolution: "@opentelemetry/instrumentation-aws-lambda@npm:0.61.1" +"@opentelemetry/instrumentation-amqplib@npm:^0.60.0": + version: 0.60.0 + resolution: "@opentelemetry/instrumentation-amqplib@npm:0.60.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.33.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/fcf55c6489dd59575bd33de44ff2c6dd1ce2af06d71188df7d81791bd1c6b54414c63518a8c645f56cf08e535cfca211afa023df48b8ce168d3069190e769666 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-aws-lambda@npm:^0.65.0": + version: 0.65.0 + resolution: "@opentelemetry/instrumentation-aws-lambda@npm:0.65.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" "@types/aws-lambda": "npm:^8.10.155" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/04167ed3ec275c045f0fc1f82dfdb09d4f54744bb87929e26ac4f51de8fb57139c57cb0570ad8e81f382a45f8c8ca1c241636249651a0a7961cbe89368856a23 + checksum: 10/08eff3816df64f6dfb189070abf10163f98f837751e2311720d422a0f5b56fc26100eacccc607a639912ac2e58d0e4e9babb486b33629c6971cede4190c73d0e languageName: node linkType: hard -"@opentelemetry/instrumentation-aws-sdk@npm:^0.64.1": - version: 0.64.1 - resolution: "@opentelemetry/instrumentation-aws-sdk@npm:0.64.1" +"@opentelemetry/instrumentation-aws-sdk@npm:^0.68.0": + version: 0.68.0 + resolution: "@opentelemetry/instrumentation-aws-sdk@npm:0.68.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.34.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/37cbdace1541980e95c2fb4f43859c7ccca1f85dce009e69bf2e957278190fa627be93fe3a2bcd112709c1028213d5ef897450c1f247181e0ab4d4e372c368df + checksum: 10/bb5851b446067c05aef200cc28ca9b4ca941ee7308ab82039c915aec8fc97bafd5ea8ed1b6f01053a1e7b31caa21ad8a013779f7db77018115b2be630ddfb623 languageName: node linkType: hard -"@opentelemetry/instrumentation-bunyan@npm:^0.54.0": - version: 0.54.0 - resolution: "@opentelemetry/instrumentation-bunyan@npm:0.54.0" +"@opentelemetry/instrumentation-bunyan@npm:^0.58.0": + version: 0.58.0 + resolution: "@opentelemetry/instrumentation-bunyan@npm:0.58.0" dependencies: - "@opentelemetry/api-logs": "npm:^0.208.0" - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/api-logs": "npm:^0.213.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@types/bunyan": "npm:1.8.11" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/563eeb0788c551e03947730586ee75a32f9fac28512fb97f109eccd582fe3c049d8642c9f1a638ca028215899d83c8cc6f0b3f0c36467c5e69e7160dd81c0bc4 + checksum: 10/5ba6b704793c02bc51c3fa4507c7ac943e4adf69e0e62f6d63bab13c3cbadb724b79b8d9d385518efd6e8165ad723e7d2d14631d3d6a1630ec92b8da4d82fe5c languageName: node linkType: hard -"@opentelemetry/instrumentation-cassandra-driver@npm:^0.54.1": - version: 0.54.1 - resolution: "@opentelemetry/instrumentation-cassandra-driver@npm:0.54.1" +"@opentelemetry/instrumentation-cassandra-driver@npm:^0.58.0": + version: 0.58.0 + resolution: "@opentelemetry/instrumentation-cassandra-driver@npm:0.58.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.37.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/93089f2d9d657ce69f3ae6f0b86ce467b899744defd94b98351a1081ae470ef01f82496ca623edf089aca7391d8911f11fd94499306ea935c61e2e342638d440 + checksum: 10/c6bdc86e7c24737a0d08c031b88f5e863482d3da126bce7711f5b92fd840f49f31834a38a6f530100042f5d0cea6da549b813489665331d01a15ce4e034242e3 languageName: node linkType: hard -"@opentelemetry/instrumentation-connect@npm:^0.52.0": - version: 0.52.0 - resolution: "@opentelemetry/instrumentation-connect@npm:0.52.0" +"@opentelemetry/instrumentation-connect@npm:^0.56.0": + version: 0.56.0 + resolution: "@opentelemetry/instrumentation-connect@npm:0.56.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" "@types/connect": "npm:3.4.38" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/c526768a10a4f6c76b791e382edeae2217a7b3c448b1a3458ef34ca46a645b6cf67eeec79bae119e2005f5d766c8eabc03b16058da6e94300110d029157e7e95 + checksum: 10/7c3c71e205aa748f3f3b24ca3a9bd9e07ee965175a0cdd672fca9604a97a668962190f9db80518ae94c6ce21f7b13b9dcadc37dea0d749b12988bb273c3f400a languageName: node linkType: hard -"@opentelemetry/instrumentation-cucumber@npm:^0.24.0": - version: 0.24.0 - resolution: "@opentelemetry/instrumentation-cucumber@npm:0.24.0" +"@opentelemetry/instrumentation-cucumber@npm:^0.29.0": + version: 0.29.0 + resolution: "@opentelemetry/instrumentation-cucumber@npm:0.29.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/fc9542beb91284eb42cd72fb5d18b8a183ccdc7b40ca1c5f04013aded6517a336544d6e83fadfff51dfffb2abf748f0dc546eddb67330bed773d7275038697df + checksum: 10/03498a50448ea0ce3a7e96db4903697791b13b0bb42a933a92dffb4f499acaceb7cc899365c8208d12107d1fc34b65b19be3b1d03184c312b1358ad629ec8b55 languageName: node linkType: hard -"@opentelemetry/instrumentation-dataloader@npm:^0.26.1": - version: 0.26.1 - resolution: "@opentelemetry/instrumentation-dataloader@npm:0.26.1" +"@opentelemetry/instrumentation-dataloader@npm:^0.30.0": + version: 0.30.0 + resolution: "@opentelemetry/instrumentation-dataloader@npm:0.30.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/b2ca71ec08651a193c3291cf80b502c8a8043790fba787f13bd7f920fdb1ba976ec39fd23035ae833bdb877b97da7194bf7ae98fa07c30c1f1006da5e1bca111 + checksum: 10/9c45ba18866980e898fd8a7060d655e5c37dfa7dd5878a56e6ba5de22e88273215fc4fa02f8971b403abfeb79fa28dc938d5ca071ef812526dedd3774caf8587 languageName: node linkType: hard -"@opentelemetry/instrumentation-dns@npm:^0.52.0": - version: 0.52.0 - resolution: "@opentelemetry/instrumentation-dns@npm:0.52.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/a6ff78cacbcc50ff3d4f805144223a0fc2fa7df2bea5c198511c2e1a546550dddef75f8b85b5ed78bf5ffa50d006e166ae1f19ab6a4125edbdab7cc92cb49bad - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-express@npm:^0.57.1": - version: 0.57.1 - resolution: "@opentelemetry/instrumentation-express@npm:0.57.1" - dependencies: - "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.208.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/0d70672aa6c26b46fbc219c9b0dbc08e5b1f5614d7d25590c1ba8149054552a2226edbf3c7287a0ccba49d6a638379048709ecfbb175327a7bec72b4113d5d5e - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-fastify@npm:^0.53.1": - version: 0.53.1 - resolution: "@opentelemetry/instrumentation-fastify@npm:0.53.1" - dependencies: - "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.208.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/e72e4e8adef7687519688bd2e2bd9ee49586cf1839aeabb1d916fe5308085cf7681536a93938d96a6f79b24d18e82c47faa4205ab2bdc7f435e708d42d626dbf - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-fs@npm:^0.28.0": - version: 0.28.0 - resolution: "@opentelemetry/instrumentation-fs@npm:0.28.0" - dependencies: - "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.208.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/f62c1256e3c47d958b296b57f8ab1d0b8b8851e9c3d6ad8691af514a33a2341ec1a9345d57a705a420a7a5757a297e03cc04c497a5701ac027a98b63b1a90ab1 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-generic-pool@npm:^0.52.0": - version: 0.52.0 - resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.52.0" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/f94b27d84a4172f95f7a33fa967f2ad562d4dba3182b1e8b6658b4eceb8a26a678b407a232858378a034dd8ffdb4e66bd98b4a8861fe042be59af3e4b400244f - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-graphql@npm:^0.56.0": +"@opentelemetry/instrumentation-dns@npm:^0.56.0": version: 0.56.0 - resolution: "@opentelemetry/instrumentation-graphql@npm:0.56.0" + resolution: "@opentelemetry/instrumentation-dns@npm:0.56.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/7895e702484367ef9c26f7c79f0039ac9677133342253f3935e7203156cbc396dfd799ef84e4758e83c008ed8c6b6893a05fc1df2566cfaae65e3770485ad8c9 + checksum: 10/9cc729e47dd6e22e8c363eeb335fe9d90f98be4c81307bbbd1b504062a9b1a8e53189ae210490f222d10da15486e287796e8f69c40108914a746a7200fe90456 languageName: node linkType: hard -"@opentelemetry/instrumentation-grpc@npm:^0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/instrumentation-grpc@npm:0.208.0" +"@opentelemetry/instrumentation-express@npm:^0.61.0": + version: 0.61.0 + resolution: "@opentelemetry/instrumentation-express@npm:0.61.0" dependencies: - "@opentelemetry/instrumentation": "npm:0.208.0" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/039273c9985238ac74a3576c7a8e8be6b63a962f89e289eed2ee6eeca14821c82d384c6015b536bc42a082a39e5d4db10abcdaf47235e2ca0f2cc0ff47525b14 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-fastify@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/instrumentation-fastify@npm:0.57.0" + dependencies: + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/74fb7b4d01bbabe82da3a81c11bdbbb86a043f2eb47bbfac62791b0ee029d2d7613ec9d1e34aa0db884711f6d3b92391ad4eabffabaa7fa7c8ab042f0169a13f + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-fs@npm:^0.32.0": + version: 0.32.0 + resolution: "@opentelemetry/instrumentation-fs@npm:0.32.0" + dependencies: + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/6a72169c4410700add4d2f7ff3c6e31e5a03040771932eb5392c0ab31c13b660a498727c876b95f0f8b88caa2d3af2b4c0d785bfc709c607b775f8797d76de85 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-generic-pool@npm:^0.56.0": + version: 0.56.0 + resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.56.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.213.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/fae98b198728f06a323b552070c8f6b41a184e1e86c8f3a1868e3f100ac4e80d8985faa89a1ef42549d3e29d6ee9ec59781cfe49ddfddfa94de0e397da0cc46c + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-graphql@npm:^0.61.0": + version: 0.61.0 + resolution: "@opentelemetry/instrumentation-graphql@npm:0.61.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.213.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/ba5270bc5b98523d3865f277d5f45b077ab02b81e84f1d8ee34994ff5ac4d28b6810c87dd1f52daf57a4983929a09d6b02419a26b068a50990a34fa864e31aa5 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-grpc@npm:^0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/instrumentation-grpc@npm:0.213.0" + dependencies: + "@opentelemetry/instrumentation": "npm:0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/8f5a75c1cac44c2e15963b0c14259de7a3f0320391d24f4bbda2830e9471481af84be7f8b978c679d6dd94d5dfbb96bbe94c4dd601f70fb7ed2df10de89c195a + checksum: 10/6ac17dbed347571e954846c30b97d649800dab79718c04846813fc2358f6679e3050575801c0c5cbfda632220dfec673e4c695ca104d0e72637ce8604528959d languageName: node linkType: hard -"@opentelemetry/instrumentation-hapi@npm:^0.55.1": - version: 0.55.1 - resolution: "@opentelemetry/instrumentation-hapi@npm:0.55.1" +"@opentelemetry/instrumentation-hapi@npm:^0.59.0": + version: 0.59.0 + resolution: "@opentelemetry/instrumentation-hapi@npm:0.59.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/af5e52ebcf56141c8973c334a1a5c4cfb01bf64974e6dd36502f93af4115500f70159492d19723201d30ad599885e87223f518d4598640a9f58d564ffb688d3b + checksum: 10/bcca71c35d525fe54abb3315cfad19dff635cdb1e72b7e15d083a468e05ae90eb966bbf1538bc4fdb0f16fb46057dd61780057c43fdcdb1a1ee5d94aa200cc27 languageName: node linkType: hard -"@opentelemetry/instrumentation-http@npm:^0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/instrumentation-http@npm:0.208.0" +"@opentelemetry/instrumentation-http@npm:^0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/instrumentation-http@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/instrumentation": "npm:0.208.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/instrumentation": "npm:0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.29.0" forwarded-parse: "npm:2.1.2" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/f5e11eb7054d6701ea44ecb90321558e524b253a7945d1539f9971f83c5dfc1d294a6ac632eb35d2f19309837f16f6c405958c0bd9c50a8751dd07ee0704ca4e + checksum: 10/cbb8c2d639ddeff70f13a4781b2bb39067ce34ad9fd02a5f20f32865fb3b2e028c46dd29296a9190f42e1e7965877d9b73941f969de093c0d5e92ad0f4ae3aec languageName: node linkType: hard -"@opentelemetry/instrumentation-ioredis@npm:^0.57.0": - version: 0.57.0 - resolution: "@opentelemetry/instrumentation-ioredis@npm:0.57.0" +"@opentelemetry/instrumentation-ioredis@npm:^0.61.0": + version: 0.61.0 + resolution: "@opentelemetry/instrumentation-ioredis@npm:0.61.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/redis-common": "npm:^0.38.2" "@opentelemetry/semantic-conventions": "npm:^1.33.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/01ae642771e62b7e460fdad4756ff1af503617b18af34bc16468b53d9afa674cdaa3a19c76b51ec1a7f05a7e838c8943a40ab13b09ee1f32c0cd02b5e0fb8f0d + checksum: 10/598d2a6763019a5b7b1c1139e12bb8199e7fc29aea54af3e78f77da9ea3ea45b98165581c74613923590ee242f6a90a30f5849b208f9e9268efdcc30d9a7d7cb languageName: node linkType: hard -"@opentelemetry/instrumentation-kafkajs@npm:^0.18.1": - version: 0.18.1 - resolution: "@opentelemetry/instrumentation-kafkajs@npm:0.18.1" +"@opentelemetry/instrumentation-kafkajs@npm:^0.22.0": + version: 0.22.0 + resolution: "@opentelemetry/instrumentation-kafkajs@npm:0.22.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.30.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/5aeb751e2a7f5a6a602c4d92cebb9215b574ef50684b268183a7dc4e60d71f2568e4ac3fa9a258577799a8433306abcee3db25aaf9ee918202d6828b3aea8d37 + checksum: 10/ac0012cd95a29b7c29de27de57b1c3981de72a95e549fba99dc7ff3e1b1168a668b07f43cde32438890a150ad058af82063df233939338ecce05d60c1281bccf languageName: node linkType: hard -"@opentelemetry/instrumentation-knex@npm:^0.53.1": - version: 0.53.1 - resolution: "@opentelemetry/instrumentation-knex@npm:0.53.1" +"@opentelemetry/instrumentation-knex@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/instrumentation-knex@npm:0.57.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.33.1" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/a9acaaefcf1f4523b47b33f8e5bd99dfc9f803aa7c8c3fc79e3cc497a022edeefa890905708eccbecd0dff37b8cce5543a6d17c61e565d87bfa9bd1de43e1526 + checksum: 10/bf8a5b9030d51e50387c92ac91dcb1d8be6b829236a245ee834a9bd6c9206bf8f9ddf267e7f2ccb1095adc08ec28624144aae2e5a010523e7ef5479b978e3c49 languageName: node linkType: hard -"@opentelemetry/instrumentation-koa@npm:^0.57.1": - version: 0.57.1 - resolution: "@opentelemetry/instrumentation-koa@npm:0.57.1" +"@opentelemetry/instrumentation-koa@npm:^0.61.0": + version: 0.61.0 + resolution: "@opentelemetry/instrumentation-koa@npm:0.61.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.36.0" peerDependencies: "@opentelemetry/api": ^1.9.0 - checksum: 10/8235d78b8c845ab86a264cd0882e6a95dc4ff890b13b02a81d0e3ccc9a73e055d732b14043cf2366940e1e808912520cf0ca96bc893a6aa89468a1e3a9846031 + checksum: 10/5426d58dd0c0498a5cdea2996a8840308ce9d7c48e8894f8df2abe9aa7fbd8b130a89d32c2465e660d19a9dc9772d4f74517da3d3702869c45ac0105d4877e65 languageName: node linkType: hard -"@opentelemetry/instrumentation-lru-memoizer@npm:^0.53.1": - version: 0.53.1 - resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.53.1" +"@opentelemetry/instrumentation-lru-memoizer@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.57.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/10a5840848ec59b658150af94dbb6297f5b8f9202b9bfe02a5f860540de92f1d38ec79a320a334a827cf06384f5f1bb19bb19c5d24bc3998808f933f05b41920 + checksum: 10/f4b41d072251be5d9a296a179b8ce09a734204bacacbb1f8e6f5ebc2e798a999ead9806a3944b203d35f50421e86f4f19069e0f2d211f37ac8c372cd89defbb3 languageName: node linkType: hard -"@opentelemetry/instrumentation-memcached@npm:^0.52.1": - version: 0.52.1 - resolution: "@opentelemetry/instrumentation-memcached@npm:0.52.1" +"@opentelemetry/instrumentation-memcached@npm:^0.56.0": + version: 0.56.0 + resolution: "@opentelemetry/instrumentation-memcached@npm:0.56.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.33.0" "@types/memcached": "npm:^2.2.6" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/b61f3a67b86e2e37b565c85c23c9161bce8ed634bb463e9dc58b409c3b512e8da04b896ca62caebca918d81e8496fc29e7b98a5e1a839f1f992b623b069e045e + checksum: 10/9c3cf65410c232d61edc1e8ea95c83927d77554b269e3bdbeef31bad6ed03387f7ce838ad5968af20c8abca140730005ad68505f6a36a6d976dae354c88c07d9 languageName: node linkType: hard -"@opentelemetry/instrumentation-mongodb@npm:^0.62.0": - version: 0.62.0 - resolution: "@opentelemetry/instrumentation-mongodb@npm:0.62.0" +"@opentelemetry/instrumentation-mongodb@npm:^0.66.0": + version: 0.66.0 + resolution: "@opentelemetry/instrumentation-mongodb@npm:0.66.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.33.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/994a4a020f1876b3d4692e60c80c1dcb28e5cc5bfe91d270f5b4267afc03d3e5e43a8a05c69bc568b3ec91fa6a35fbbac52cb238fbd5f1a86ab7c07782d79dd3 + checksum: 10/50baa175386bc71daf9098ddb39460bde17fac1a7c5bc347f07fd3e0a752dc1c56cc1cd5a09048b51892b1cb29b244d4865da8deec34f3ea6c645e6e97d93b9c languageName: node linkType: hard -"@opentelemetry/instrumentation-mongoose@npm:^0.55.1": - version: 0.55.1 - resolution: "@opentelemetry/instrumentation-mongoose@npm:0.55.1" +"@opentelemetry/instrumentation-mongoose@npm:^0.59.0": + version: 0.59.0 + resolution: "@opentelemetry/instrumentation-mongoose@npm:0.59.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" + "@opentelemetry/semantic-conventions": "npm:^1.33.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/6442ce148c930f7eb4b982cf2085e2f84f79a9d429d5c2162bd95d05716abe5b24414a923a6c69eaca5faf83b7cf97480ae92523e8d0149430299dee6fd98be3 + checksum: 10/121506d6e4941145c7066b2b723eaa2e0182a5d0b565d61cbf6269454d86c180ec417c66cf4320ee82d173099d59b098c924bb17e72a873722aa54f14aaa5e7e languageName: node linkType: hard -"@opentelemetry/instrumentation-mysql2@npm:^0.55.1": - version: 0.55.1 - resolution: "@opentelemetry/instrumentation-mysql2@npm:0.55.1" +"@opentelemetry/instrumentation-mysql2@npm:^0.59.0": + version: 0.59.0 + resolution: "@opentelemetry/instrumentation-mysql2@npm:0.59.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.33.0" "@opentelemetry/sql-common": "npm:^0.41.2" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/1bbe617aff0bba4807ab52d8dd5af0c031412f1e8ef3d7180dd9eb3645451ee4bf587c7a0140a1c22c48777c394778e7a387992b391eb938a7b8bbb818527c64 + checksum: 10/7fa1941bdad8cbfcf6084ba2a291c68d101050c738bad06915f2cce4d475548353fdd99ba8f41bcc26267cd092eaa485849343256bd48043bc2dfa0412570f6b languageName: node linkType: hard -"@opentelemetry/instrumentation-mysql@npm:^0.55.0": - version: 0.55.0 - resolution: "@opentelemetry/instrumentation-mysql@npm:0.55.0" +"@opentelemetry/instrumentation-mysql@npm:^0.59.0": + version: 0.59.0 + resolution: "@opentelemetry/instrumentation-mysql@npm:0.59.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.33.0" "@types/mysql": "npm:2.15.27" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/492de2b5d4759119935a167cbc549f5d4ba1373b43bb39cbabadee896fc3b5256cd2bd9b08abe0704f5bbaa29afeea77a16186533e06449878fefdaa05aa8e98 + checksum: 10/87833280fa53a691d95263066b04458db33b6bb49a15fb97add7505fcae0cb574da5ac9e53c7788d2044ae108cbc42308eb2684c2fa05751098cb54d6bd0d315 languageName: node linkType: hard -"@opentelemetry/instrumentation-nestjs-core@npm:^0.55.0": - version: 0.55.0 - resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.55.0" +"@opentelemetry/instrumentation-nestjs-core@npm:^0.59.0": + version: 0.59.0 + resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.59.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.30.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/6c930d4b670f3a1764d11539867656594fa1a74d47597ca0b4fdf0425403db14552cf41a28ae4169d2034680f9d968a1c90cb5bfe2a6504bf101eaef026cc2fe + checksum: 10/4a86f6658e45993381051352c61ef904899675c641210bd30210298c86585881212b4db3fe769ef7bbe05dc20c4a2019a6ecddd6d7ddfb10b01cb330bc727b43 languageName: node linkType: hard -"@opentelemetry/instrumentation-net@npm:^0.53.0": - version: 0.53.0 - resolution: "@opentelemetry/instrumentation-net@npm:0.53.0" +"@opentelemetry/instrumentation-net@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/instrumentation-net@npm:0.57.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.33.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/3fb1868fe15916a8806279ae8da6e57a4e8aff2637bdca608a0e11000eaf93ee9af092f88eef20bdc76e77cdf713ce767ba11b2691146a9375fb1fa8b735fd08 + checksum: 10/7aff49402d16ce3d501060759d1c729a5e2d514f5ebb8a39a0190f828a3b402886aa2cefa4012bc93f3a1c002b67b4729ed193905c67556fbebb2863ae18370a languageName: node linkType: hard -"@opentelemetry/instrumentation-openai@npm:^0.7.1": - version: 0.7.1 - resolution: "@opentelemetry/instrumentation-openai@npm:0.7.1" +"@opentelemetry/instrumentation-openai@npm:^0.11.0": + version: 0.11.0 + resolution: "@opentelemetry/instrumentation-openai@npm:0.11.0" dependencies: - "@opentelemetry/api-logs": "npm:^0.208.0" - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/api-logs": "npm:^0.213.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.36.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/f7c5fcf0467e17aebe87c2cc0fb46b8d7a7601cfb31ae0378a30bdafa1c9cf7e8f203d8d8e5f78e41705a9fdca245fa3ba401585a9ce21f1f04f041a0616087c + checksum: 10/8cfa799a96b9d815f7a2c1ab9c68251826077ee9fa77f974bcf031490acb432095f280e17745e14eefae60782a12d3dbebbb8798889873ed6c4c9e7eae72f650 languageName: node linkType: hard -"@opentelemetry/instrumentation-oracledb@npm:^0.34.1": - version: 0.34.1 - resolution: "@opentelemetry/instrumentation-oracledb@npm:0.34.1" +"@opentelemetry/instrumentation-oracledb@npm:^0.38.0": + version: 0.38.0 + resolution: "@opentelemetry/instrumentation-oracledb@npm:0.38.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.34.0" "@types/oracledb": "npm:6.5.2" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/c00983a5d03b1611fa8e7c17d82479c1c43e295559f2c616435c569059d23ece026e0c83dd9f130aff7d9436a66ec32766bf04d106d98c6f0439cf2c9ee2df6b + checksum: 10/5ca691e164a4fc8ef686553e062e67342d7e929484bdb6f2adef219e1e50d55a6dd5570381a4347345e1fd514d5667cbe29a2b7bb9801eda77bd532b04341307 languageName: node linkType: hard -"@opentelemetry/instrumentation-pg@npm:^0.61.2": - version: 0.61.2 - resolution: "@opentelemetry/instrumentation-pg@npm:0.61.2" +"@opentelemetry/instrumentation-pg@npm:^0.65.0": + version: 0.65.0 + resolution: "@opentelemetry/instrumentation-pg@npm:0.65.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.34.0" "@opentelemetry/sql-common": "npm:^0.41.2" "@types/pg": "npm:8.15.6" - "@types/pg-pool": "npm:2.0.6" + "@types/pg-pool": "npm:2.0.7" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/74359ac9a5130096f5b43040f4dc10d6b2d6f104099b5244106fee6c3ffd3a2dc729ad65e9c0447c8637607de156de28f12dbf9431226dc50e2ba3b9ed7d7e29 + checksum: 10/835953ae633fe0efa3ad0a39579f7860c59ec066330a592682ceea0eb14eb770d94c309a4fd64ff2162e804fe53cfcb38f1fd3d19116786473cfd8e6a1eaee7f languageName: node linkType: hard -"@opentelemetry/instrumentation-pino@npm:^0.55.1": - version: 0.55.1 - resolution: "@opentelemetry/instrumentation-pino@npm:0.55.1" +"@opentelemetry/instrumentation-pino@npm:^0.59.0": + version: 0.59.0 + resolution: "@opentelemetry/instrumentation-pino@npm:0.59.0" dependencies: - "@opentelemetry/api-logs": "npm:^0.208.0" + "@opentelemetry/api-logs": "npm:^0.213.0" "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/7da10316dfe60622e6080a54112ebeea7873b43d48c32910108512db07dbc292cd785fa2be2d53a9b05d31f0649f62f2f01aba9f2795949fd6582b37b5236cf4 + checksum: 10/9198c2bc9ab17fabef251d69feb9f3199b39e878792a9a9fb5b7e0936faf860d4706170db8578399bcb7e8826d7d544199dfeabec16cfb4b55df02a537f8c1e9 languageName: node linkType: hard -"@opentelemetry/instrumentation-redis@npm:^0.57.2": - version: 0.57.2 - resolution: "@opentelemetry/instrumentation-redis@npm:0.57.2" +"@opentelemetry/instrumentation-redis@npm:^0.61.0": + version: 0.61.0 + resolution: "@opentelemetry/instrumentation-redis@npm:0.61.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/redis-common": "npm:^0.38.2" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/8e1e7840f1b524d0d69a0783216173f8dfc794133385f7449b5215e5a04775d469a009ecef02d99fe05fa0382859a25e65653eadbde1fece3514ba119d1db1a1 + checksum: 10/667eacf85fcf3de51e5052f66826aed90b62e565099c7cea6b465830ee0fd9f149a7ddf208a00e34f3bbb65213ed9d24b1601c242234e78db21447f9f9fd56fe languageName: node linkType: hard -"@opentelemetry/instrumentation-restify@npm:^0.54.0": - version: 0.54.0 - resolution: "@opentelemetry/instrumentation-restify@npm:0.54.0" +"@opentelemetry/instrumentation-restify@npm:^0.58.0": + version: 0.58.0 + resolution: "@opentelemetry/instrumentation-restify@npm:0.58.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/5488aaf97dcc5e1ec8f9c055258c587553f20f825b8d7c5dcd2a7398adf85554f1d8b5d1c35235c98280ab6662917b12d148215c4d3882e205314883c9bed0b8 + checksum: 10/c6b1aaa19e9d4f2520a4d2ac61b6125b2abafa5bba81331fc263b59e5256d2ce8b016dc8cd12e2ac1fcebd6068844d894450f45aa2ed7dc4bb051916d055954d languageName: node linkType: hard -"@opentelemetry/instrumentation-router@npm:^0.53.0": - version: 0.53.0 - resolution: "@opentelemetry/instrumentation-router@npm:0.53.0" +"@opentelemetry/instrumentation-router@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/instrumentation-router@npm:0.57.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/3a24e2cd25b0bff9e96c1956fe9bb2fbda8aa807125cb58535fb16e4e10a65ac391a2db748bc50a1ca7e35fcfbc26e078a3fc96e0f105f813b06f57079f84062 + checksum: 10/71b0c108708ac214377e96769c7863f3172e17a8a24afd2dc82ac48aebd4f0bf9ed73266b7549ac9d05221df66af35e9bd6a493f8fd546cd59f52b24f8bfae5b languageName: node linkType: hard -"@opentelemetry/instrumentation-runtime-node@npm:^0.22.0": - version: 0.22.0 - resolution: "@opentelemetry/instrumentation-runtime-node@npm:0.22.0" +"@opentelemetry/instrumentation-runtime-node@npm:^0.26.0": + version: 0.26.0 + resolution: "@opentelemetry/instrumentation-runtime-node@npm:0.26.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/20a45189e0bffb08e9ef111fc4be33e98309948aaa10b448979bd900b0c53a752606366b44fa2351ba2fdc2cf775ba4eaf03cb038ba31798c679180ea987dba8 + checksum: 10/b3ea3c4dece7dc3d35c2dc7de38cccc3b73c34175c4fb28a8442f72b312b7f83417d37c9f1864feb02ed2c8d8940f0525fe0e2e0c7d0615bcfe77825b8125928 languageName: node linkType: hard -"@opentelemetry/instrumentation-socket.io@npm:^0.55.1": - version: 0.55.1 - resolution: "@opentelemetry/instrumentation-socket.io@npm:0.55.1" +"@opentelemetry/instrumentation-socket.io@npm:^0.60.0": + version: 0.60.0 + resolution: "@opentelemetry/instrumentation-socket.io@npm:0.60.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/6df6e677ee49a65622a659217d117a81a8dae89e7b5cb283e34da112697179f892842f17ceb00a86c62b9d07ec5d8b49bd334062f582084d779c8243fc904fa0 + checksum: 10/7bc82b036ad37fc1361e7f44d97036e8e456f27e133139d21bbdd1194d32ff9ff3a8aa7101874d966e44ac39472e5ef339145a372e8338690ba65a18aaa4b606 languageName: node linkType: hard -"@opentelemetry/instrumentation-tedious@npm:^0.28.0": - version: 0.28.0 - resolution: "@opentelemetry/instrumentation-tedious@npm:0.28.0" +"@opentelemetry/instrumentation-tedious@npm:^0.32.0": + version: 0.32.0 + resolution: "@opentelemetry/instrumentation-tedious@npm:0.32.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.33.0" "@types/tedious": "npm:^4.0.14" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/43b1bd400aa393ea2000ea2c6f40fee40a06184739d014175f2713eb8d284a5b4eb7c047801084506fa1fed7d7a55d25dfb009592d2ebc193370c10fb3d3df13 + checksum: 10/25b7c42e75ee393990ff1502ec7e4dea33e9615b35ff32bded2634824c0278dc55cccd6b0d6dbecdfff91d3f998be5d9e89526469287ea5df4637810dfbfc1c6 languageName: node linkType: hard -"@opentelemetry/instrumentation-undici@npm:^0.19.0": - version: 0.19.0 - resolution: "@opentelemetry/instrumentation-undici@npm:0.19.0" +"@opentelemetry/instrumentation-undici@npm:^0.23.0": + version: 0.23.0 + resolution: "@opentelemetry/instrumentation-undici@npm:0.23.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" "@opentelemetry/semantic-conventions": "npm:^1.24.0" peerDependencies: "@opentelemetry/api": ^1.7.0 - checksum: 10/862ea5e49c2cf38c7a135f5ea6a95f1b8ca009620f9b0e4d6c7ffb69e8e1d4e7d4169e4cf0cd2af4ce574864b37ea3f7c039b0e6600d3e490f3c263a8647325e + checksum: 10/bfb6ed86e8098827725f5cb2f21e21d5c0b13254ab6da84418de68b3c1cadc2f503e4fc213cd6f2a661819e8327c9af9b87c32468444a772e8b82b966cb616a6 languageName: node linkType: hard -"@opentelemetry/instrumentation-winston@npm:^0.53.0": - version: 0.53.0 - resolution: "@opentelemetry/instrumentation-winston@npm:0.53.0" +"@opentelemetry/instrumentation-winston@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/instrumentation-winston@npm:0.57.0" dependencies: - "@opentelemetry/api-logs": "npm:^0.208.0" - "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/api-logs": "npm:^0.213.0" + "@opentelemetry/instrumentation": "npm:^0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/5c3efd7c9cb074718cf4de99c27acf69bbb35bdf16d74f92dbdd8fd4efba2c637e3b234155e335b473d5caa060a6f40a49136d808017547d3a4d5eab66eed9b7 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation@npm:0.208.0, @opentelemetry/instrumentation@npm:^0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/instrumentation@npm:0.208.0" - dependencies: - "@opentelemetry/api-logs": "npm:0.208.0" - import-in-the-middle: "npm:^2.0.0" - require-in-the-middle: "npm:^8.0.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/0591121c1bab29b8246ba879b1ed91f2db17680cfce56a635bf2e81390a9140f029b094ff4498ff154132379192bf424d7d234d2114883735603e4a6581a4a79 + checksum: 10/0972e3ac889d5408687ddb139c49f69de78b6eebba30bdade3b4b605a240248523971ffd987b9bec976c7dba4dc1f67dfef63fe145937ee9061e4c0b15daa7f6 languageName: node linkType: hard @@ -14068,15 +14343,16 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/otlp-exporter-base@npm:0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/otlp-exporter-base@npm:0.208.0" +"@opentelemetry/instrumentation@npm:0.213.0, @opentelemetry/instrumentation@npm:^0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/instrumentation@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/otlp-transformer": "npm:0.208.0" + "@opentelemetry/api-logs": "npm:0.213.0" + import-in-the-middle: "npm:^3.0.0" + require-in-the-middle: "npm:^8.0.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/d24e1e766a8059861232fd338be7d65bded5167176b4c7c1be9a1833167f73fd352392a1df28ca002734bdbe00a70d06d1e7daeb97d9d7f71f85dc310a831a42 + checksum: 10/69baeaae0c5836ede140485530954d32c8d20d864340f7d57b43a6e3ef9c10394d29a1884dd2b076512aec896039a1ea02f698282649b5aa3fa59b13bca00f97 languageName: node linkType: hard @@ -14092,17 +14368,15 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/otlp-grpc-exporter-base@npm:0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/otlp-grpc-exporter-base@npm:0.208.0" +"@opentelemetry/otlp-exporter-base@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/otlp-exporter-base@npm:0.213.0" dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/otlp-exporter-base": "npm:0.208.0" - "@opentelemetry/otlp-transformer": "npm:0.208.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/f88ee3d2377a27ee41077b5cb79b122ee8f8fa95cab9457ca53f8ef9b92b688b3c455b05c80b55ff3bf23d254eb596c361089fdf4a84b4f0317fc7e059fb4790 + checksum: 10/ff36dbdae9ae3b4b7e25fb650e9b351851ae9d50cd09b90d2be709a10dd62a8fef522adad9b27010825e9d22d96238ab37f38530cf92eaf3340d2730d38ad301 languageName: node linkType: hard @@ -14120,20 +14394,17 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/otlp-transformer@npm:0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/otlp-transformer@npm:0.208.0" +"@opentelemetry/otlp-grpc-exporter-base@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/otlp-grpc-exporter-base@npm:0.213.0" dependencies: - "@opentelemetry/api-logs": "npm:0.208.0" - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/resources": "npm:2.2.0" - "@opentelemetry/sdk-logs": "npm:0.208.0" - "@opentelemetry/sdk-metrics": "npm:2.2.0" - "@opentelemetry/sdk-trace-base": "npm:2.2.0" - protobufjs: "npm:^7.3.0" + "@grpc/grpc-js": "npm:^1.14.3" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/otlp-exporter-base": "npm:0.213.0" + "@opentelemetry/otlp-transformer": "npm:0.213.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/867a16a7a723a3df7a7ea8fa9f3c976139e32954efd1146812486cbee20c5eea73ba03841c64312b049e998a7e678589ddef80f7a04aaf95f649753eebe79e3d + checksum: 10/dc6aa2414b4f24349a749423504dc0aba00164241b30bb468cfab39b7936408fb00ede7fa675ea95ac131249764a1320a8657765c4509d0d93245879a49905f9 languageName: node linkType: hard @@ -14154,14 +14425,20 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/propagator-b3@npm:2.2.0": - version: 2.2.0 - resolution: "@opentelemetry/propagator-b3@npm:2.2.0" +"@opentelemetry/otlp-transformer@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/otlp-transformer@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/api-logs": "npm:0.213.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-logs": "npm:0.213.0" + "@opentelemetry/sdk-metrics": "npm:2.6.0" + "@opentelemetry/sdk-trace-base": "npm:2.6.0" + protobufjs: "npm:^7.0.0" peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/567a1bdd74cd81008fc5e4289c5a870daf5c46326a7c21e9dfd7e462e42c033dd51e7acd8c7b206926b5e78d1a4e3d72d3b13c1ce290ecec984730e847d4e122 + "@opentelemetry/api": ^1.3.0 + checksum: 10/3f4caa24f9b4d57d9290f9090701031641e8aff33bca13711e2a2766c1acd8ee3c348342f9d81fcb73bbd48fe910444c10990b148d70437f0508bb8ad3751b1b languageName: node linkType: hard @@ -14176,14 +14453,14 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/propagator-jaeger@npm:2.2.0": - version: 2.2.0 - resolution: "@opentelemetry/propagator-jaeger@npm:2.2.0" +"@opentelemetry/propagator-b3@npm:2.6.0": + version: 2.6.0 + resolution: "@opentelemetry/propagator-b3@npm:2.6.0" dependencies: - "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/core": "npm:2.6.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/7da76cab4387cd52723865ccee055dafeed1763c784ea6e13152f1264ad3ccd5b0cace442dac00755eeb17d9af216d0e235aeb6c739a9703e1b1d14b3bf1f5f8 + checksum: 10/8cc50658cc5a31a06ae97b15cda6f0d27fe8e19a06b841ff07b84e458f2e9d81347bbff823819eb8fccd387335475091d0834b9bbe1295b77a1d6a253fcd522d languageName: node linkType: hard @@ -14198,6 +14475,17 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/propagator-jaeger@npm:2.6.0": + version: 2.6.0 + resolution: "@opentelemetry/propagator-jaeger@npm:2.6.0" + dependencies: + "@opentelemetry/core": "npm:2.6.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/d9f6f971994aa4cbf5495368e17e248631fbf3c3eeb2405f0c7ce987c830388e6d6987a5f85ba24cfbf1cacdff204cf1b8338703c4845158f602407ec99b64f0 + languageName: node + linkType: hard + "@opentelemetry/redis-common@npm:^0.38.2": version: 0.38.2 resolution: "@opentelemetry/redis-common@npm:0.38.2" @@ -14205,82 +14493,70 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/resource-detector-alibaba-cloud@npm:^0.32.0": - version: 0.32.0 - resolution: "@opentelemetry/resource-detector-alibaba-cloud@npm:0.32.0" +"@opentelemetry/resource-detector-alibaba-cloud@npm:^0.33.3": + version: 0.33.3 + resolution: "@opentelemetry/resource-detector-alibaba-cloud@npm:0.33.3" dependencies: "@opentelemetry/core": "npm:^2.0.0" "@opentelemetry/resources": "npm:^2.0.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/2c3825e5bd87f763a17b660e6acc4b6aeee3e3ebe9c90a3f0d09010153814724aa46f3af3e57e340eb971bdda5503a15cd823f87311c911f671acdbbf14ccbb2 + checksum: 10/600d67e1a910d8278a97171a1a5bf403da34ca070348cb8f7af09b19bedfd48464dc8c794775723058b60137c9a48cb3b7894e6afb81ae62e99be9b4b24f4f62 languageName: node linkType: hard -"@opentelemetry/resource-detector-aws@npm:^2.9.0": - version: 2.9.0 - resolution: "@opentelemetry/resource-detector-aws@npm:2.9.0" +"@opentelemetry/resource-detector-aws@npm:^2.13.0": + version: 2.13.0 + resolution: "@opentelemetry/resource-detector-aws@npm:2.13.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" "@opentelemetry/resources": "npm:^2.0.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/925be76939c0b8b786e80e17d9be62b42c7cdcf672d7f10f21c88786510fba114ff53fc904c2c9be2a429704d8bcb656898f7a2f267d96f5bc849c5b92584517 + checksum: 10/b0d057684f83ca666b3ac61d04a53e0129c3a434b38e3870db071133cc9d3552c8ff9f9b50e9f326f1a450ffd22af292ba4fc594cf429207966ce00a89286ba4 languageName: node linkType: hard -"@opentelemetry/resource-detector-azure@npm:^0.17.0": - version: 0.17.0 - resolution: "@opentelemetry/resource-detector-azure@npm:0.17.0" +"@opentelemetry/resource-detector-azure@npm:^0.21.0": + version: 0.21.0 + resolution: "@opentelemetry/resource-detector-azure@npm:0.21.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" "@opentelemetry/resources": "npm:^2.0.0" "@opentelemetry/semantic-conventions": "npm:^1.37.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/2da1ed2f0dd4e1f8fc2eaeb90956cb97055b30ecf6b42cac418f014b1b1388b80ba9933663ba8f85ae2a2bc7406b241d9e5f430cf1ddbd351e0f70496b8ada60 + checksum: 10/cfc4f8e419ec7b077c87599d0d842aeb741413f33e66e97ab093f79b292eaa9eb8dc54ea4e644550f31363ebf4efc8182c03df302143d6e89552427f575e2177 languageName: node linkType: hard -"@opentelemetry/resource-detector-container@npm:^0.8.0": - version: 0.8.0 - resolution: "@opentelemetry/resource-detector-container@npm:0.8.0" +"@opentelemetry/resource-detector-container@npm:^0.8.4": + version: 0.8.4 + resolution: "@opentelemetry/resource-detector-container@npm:0.8.4" dependencies: "@opentelemetry/core": "npm:^2.0.0" "@opentelemetry/resources": "npm:^2.0.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/aa621d823c70ed09a9601b7167897f40f94ad2514512208db2ded9dd5f307e8dfe06e9a51347d912bd4367031d0c09cab2642157ebe266b45432a6038d964fe9 + checksum: 10/65fb62e9e414ac941e94f5befeaca5fd2d2e475e414c9e4521b66729c6a2d9fc3b6e32bb316f95459c6c12910feaa124498827faa466017bd0d3004a26a6630a languageName: node linkType: hard -"@opentelemetry/resource-detector-gcp@npm:^0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/resource-detector-gcp@npm:0.44.0" +"@opentelemetry/resource-detector-gcp@npm:^0.48.0": + version: 0.48.0 + resolution: "@opentelemetry/resource-detector-gcp@npm:0.48.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" "@opentelemetry/resources": "npm:^2.0.0" - gcp-metadata: "npm:^6.0.0" + gcp-metadata: "npm:^8.0.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/cf7b401a9dc77a87793db04e9b92e0822562d703b270957a482c6cb41fbbcf3b7cb35089f7273c6143cd9c78ea45d849fc7c54ed9b29435e4b384ad3b5b05cb9 + checksum: 10/2c11431c7be28620868d091c4bf9d646ce524587056e94c04b8d9b31bdec77f54ca132a1ac8ff1e6f894009d8cb776527eb3639d3f31b11eeee576b736b3d56a languageName: node linkType: hard -"@opentelemetry/resources@npm:2.2.0": - version: 2.2.0 - resolution: "@opentelemetry/resources@npm:2.2.0" - dependencies: - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/semantic-conventions": "npm:^1.29.0" - peerDependencies: - "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/65ccdb1de957dc89aef252cf84b73cd0257ec44feec2b513fcf08e8c4d03e97275661d3f60c4b6134cee33ca4359a5ab6ef5d3a97339a3585aa997a381ef9098 - languageName: node - linkType: hard - -"@opentelemetry/resources@npm:2.5.0, @opentelemetry/resources@npm:^2.0.0": +"@opentelemetry/resources@npm:2.5.0": version: 2.5.0 resolution: "@opentelemetry/resources@npm:2.5.0" dependencies: @@ -14292,16 +14568,15 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/sdk-logs@npm:0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/sdk-logs@npm:0.208.0" +"@opentelemetry/resources@npm:2.6.0, @opentelemetry/resources@npm:^2.0.0": + version: 2.6.0 + resolution: "@opentelemetry/resources@npm:2.6.0" dependencies: - "@opentelemetry/api-logs": "npm:0.208.0" - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/resources": "npm:2.2.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: - "@opentelemetry/api": ">=1.4.0 <1.10.0" - checksum: 10/8413cdbf3a072d79a569ca7bcf3c8b333dfb1cb11a7a8841a9bb5482d6ee58d5a3a26efa0b9f02bf410b3f9fa99995ee5aa58aab505de560a2b2ac0b114ce70d + "@opentelemetry/api": ">=1.3.0 <1.10.0" + checksum: 10/837e76911d013e52c1c0cd8da6f2912818fcc107fd1c6fcb2ec8faa6e617b802e0eef1aa53bd4417c7a77c96ea02b6f5bbdccb9ff411ef8efeff49c2e02d9443 languageName: node linkType: hard @@ -14318,15 +14593,17 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/sdk-metrics@npm:2.2.0": - version: 2.2.0 - resolution: "@opentelemetry/sdk-metrics@npm:2.2.0" +"@opentelemetry/sdk-logs@npm:0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/sdk-logs@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/resources": "npm:2.2.0" + "@opentelemetry/api-logs": "npm:0.213.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: - "@opentelemetry/api": ">=1.9.0 <1.10.0" - checksum: 10/d6dacce73319e038d55a67f5b1a7a153531a703ef881b03df52f2d76685a4d53d0d840e02a0e0b24eddae4bd7d11c694e3c146f8db78e19d353316372d04c065 + "@opentelemetry/api": ">=1.4.0 <1.10.0" + checksum: 10/c443b0f2c88582713c291ca86106679ffdadee96ea428b5058e0cb7470b387828e78d6614e7e01e5ad82f4c44571c1080aa2cf69e621bd2e07089105794cacdf languageName: node linkType: hard @@ -14342,35 +14619,15 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/sdk-node@npm:^0.208.0": - version: 0.208.0 - resolution: "@opentelemetry/sdk-node@npm:0.208.0" +"@opentelemetry/sdk-metrics@npm:2.6.0": + version: 2.6.0 + resolution: "@opentelemetry/sdk-metrics@npm:2.6.0" dependencies: - "@opentelemetry/api-logs": "npm:0.208.0" - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/exporter-logs-otlp-grpc": "npm:0.208.0" - "@opentelemetry/exporter-logs-otlp-http": "npm:0.208.0" - "@opentelemetry/exporter-logs-otlp-proto": "npm:0.208.0" - "@opentelemetry/exporter-metrics-otlp-grpc": "npm:0.208.0" - "@opentelemetry/exporter-metrics-otlp-http": "npm:0.208.0" - "@opentelemetry/exporter-metrics-otlp-proto": "npm:0.208.0" - "@opentelemetry/exporter-prometheus": "npm:0.208.0" - "@opentelemetry/exporter-trace-otlp-grpc": "npm:0.208.0" - "@opentelemetry/exporter-trace-otlp-http": "npm:0.208.0" - "@opentelemetry/exporter-trace-otlp-proto": "npm:0.208.0" - "@opentelemetry/exporter-zipkin": "npm:2.2.0" - "@opentelemetry/instrumentation": "npm:0.208.0" - "@opentelemetry/propagator-b3": "npm:2.2.0" - "@opentelemetry/propagator-jaeger": "npm:2.2.0" - "@opentelemetry/resources": "npm:2.2.0" - "@opentelemetry/sdk-logs": "npm:0.208.0" - "@opentelemetry/sdk-metrics": "npm:2.2.0" - "@opentelemetry/sdk-trace-base": "npm:2.2.0" - "@opentelemetry/sdk-trace-node": "npm:2.2.0" - "@opentelemetry/semantic-conventions": "npm:^1.29.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/resources": "npm:2.6.0" peerDependencies: - "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/929be561500feb30329314ee0f42adc064f0d9def9bf112b11c0f27ed3848366f172a13b178adf56fe96037f9e886c80a46122f105c2756a1fd1f4fdb0f831c2 + "@opentelemetry/api": ">=1.9.0 <1.10.0" + checksum: 10/5fd1254ab86cdb6573999f3c5d60b8332fb3a2b7d50d1befcfd9d8ef021d5e9e405e31394f7aa4a106a4546bd6308a652a78cd9a35d6fbe56e44984d605cb5d5 languageName: node linkType: hard @@ -14408,16 +14665,37 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/sdk-trace-base@npm:2.2.0": - version: 2.2.0 - resolution: "@opentelemetry/sdk-trace-base@npm:2.2.0" +"@opentelemetry/sdk-node@npm:^0.213.0": + version: 0.213.0 + resolution: "@opentelemetry/sdk-node@npm:0.213.0" dependencies: - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/resources": "npm:2.2.0" + "@opentelemetry/api-logs": "npm:0.213.0" + "@opentelemetry/configuration": "npm:0.213.0" + "@opentelemetry/context-async-hooks": "npm:2.6.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/exporter-logs-otlp-grpc": "npm:0.213.0" + "@opentelemetry/exporter-logs-otlp-http": "npm:0.213.0" + "@opentelemetry/exporter-logs-otlp-proto": "npm:0.213.0" + "@opentelemetry/exporter-metrics-otlp-grpc": "npm:0.213.0" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.213.0" + "@opentelemetry/exporter-metrics-otlp-proto": "npm:0.213.0" + "@opentelemetry/exporter-prometheus": "npm:0.213.0" + "@opentelemetry/exporter-trace-otlp-grpc": "npm:0.213.0" + "@opentelemetry/exporter-trace-otlp-http": "npm:0.213.0" + "@opentelemetry/exporter-trace-otlp-proto": "npm:0.213.0" + "@opentelemetry/exporter-zipkin": "npm:2.6.0" + "@opentelemetry/instrumentation": "npm:0.213.0" + "@opentelemetry/propagator-b3": "npm:2.6.0" + "@opentelemetry/propagator-jaeger": "npm:2.6.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/sdk-logs": "npm:0.213.0" + "@opentelemetry/sdk-metrics": "npm:2.6.0" + "@opentelemetry/sdk-trace-base": "npm:2.6.0" + "@opentelemetry/sdk-trace-node": "npm:2.6.0" "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/0838128f965055b5f8d37026a2f4736ebb77a772a94b9f5b7accb0447a44cfa279da4da959a82565e958e1676ad2a02c17f6fd0e688b205bdde0c846e310c643 + checksum: 10/2175a8ba71d3657b2a19972a2f45cca34c908e710b16b27aec1beb918c39b64170ade4c2c2059b49aeb3726044e176ce2caa4f4c6349af19ba8c74401601c843 languageName: node linkType: hard @@ -14434,16 +14712,16 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/sdk-trace-node@npm:2.2.0": - version: 2.2.0 - resolution: "@opentelemetry/sdk-trace-node@npm:2.2.0" +"@opentelemetry/sdk-trace-base@npm:2.6.0": + version: 2.6.0 + resolution: "@opentelemetry/sdk-trace-base@npm:2.6.0" dependencies: - "@opentelemetry/context-async-hooks": "npm:2.2.0" - "@opentelemetry/core": "npm:2.2.0" - "@opentelemetry/sdk-trace-base": "npm:2.2.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/resources": "npm:2.6.0" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/4918bc6a66649203d7165a8beca5a3ebaf05d91a2b4910aa195f582a6b50658e86303e00a4fc53172e48cac3cc79b50906d70a3b989895b423dc90dad5e9b5da + "@opentelemetry/api": ">=1.3.0 <1.10.0" + checksum: 10/8ca3c1c4d7a95ec8a28ab5237162e31334216a59408e9d9d10ad51f5709911a405699ad69f445c212aad55fb6cc2c70f473b835e9e52bf4ed63f237c4d1813af languageName: node linkType: hard @@ -14460,6 +14738,19 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/sdk-trace-node@npm:2.6.0": + version: 2.6.0 + resolution: "@opentelemetry/sdk-trace-node@npm:2.6.0" + dependencies: + "@opentelemetry/context-async-hooks": "npm:2.6.0" + "@opentelemetry/core": "npm:2.6.0" + "@opentelemetry/sdk-trace-base": "npm:2.6.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/b1576f44198ae18ab36dea92e5c254eb6a96ca5793a5adbb96a01c586dd992e3532eb291c9ea225e2cd6979f7f6dd3fca2fad56ab360ba573f8b3bfb462dd2aa + languageName: node + linkType: hard + "@opentelemetry/semantic-conventions@npm:1.28.0": version: 1.28.0 resolution: "@opentelemetry/semantic-conventions@npm:1.28.0" @@ -14956,993 +15247,1028 @@ __metadata: languageName: node linkType: hard -"@radix-ui/primitive@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/primitive@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.13.10" - checksum: 10/2b93e161d3fdabe9a64919def7fa3ceaecf2848341e9211520c401181c9eaebb8451c630b066fad2256e5c639c95edc41de0ba59c40eff37e799918d019822d1 +"@radix-ui/primitive@npm:1.1.3": + version: 1.1.3 + resolution: "@radix-ui/primitive@npm:1.1.3" + checksum: 10/ee27abbff0d6d305816e9314655eb35e72478ba47416bc9d5cb0581728be35e3408cfc0748313837561d635f0cb7dfaae26e61831f0e16c0fd7d669a612f2cb0 languageName: node linkType: hard -"@radix-ui/react-arrow@npm:1.0.3": - version: 1.0.3 - resolution: "@radix-ui/react-arrow@npm:1.0.3" +"@radix-ui/react-arrow@npm:1.1.7": + version: 1.1.7 + resolution: "@radix-ui/react-arrow@npm:1.1.7" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-primitive": "npm:1.0.3" + "@radix-ui/react-primitive": "npm:2.1.3" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/8cca086f0dbb33360e3c0142adf72f99fc96352d7086d6c2356dbb2ea5944cfb720a87d526fc48087741c602cd8162ca02b0af5e6fdf5f56d20fddb44db8b4c3 + checksum: 10/6cdf74f06090f8994cdf6d3935a44ea3ac309163a4f59c476482c4907e8e0775f224045030abf10fa4f9e1cb7743db034429249b9e59354988e247eeb0f4fdcf languageName: node linkType: hard -"@radix-ui/react-collection@npm:1.0.3": - version: 1.0.3 - resolution: "@radix-ui/react-collection@npm:1.0.3" +"@radix-ui/react-collection@npm:1.1.7": + version: 1.1.7 + resolution: "@radix-ui/react-collection@npm:1.1.7" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-context": "npm:1.0.1" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-slot": "npm:1.0.2" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-slot": "npm:1.2.3" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/2ac740ab746f411942dc95100f1eb60b9a3670960a805e266533fa1bc7dec31a6dabddd746ab788ebd5a9c22b468e38922f39d30447925515f8e44f0a3b2e56c + checksum: 10/cd53e2a2be82be7bc4014164cac0b42948401a203e5d0294d3947a5193f1d56bd23eb60e878a98dba50d08283254e79c3b873de5f935276b849686a868d51dd5 languageName: node linkType: hard -"@radix-ui/react-compose-refs@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-compose-refs@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.13.10" +"@radix-ui/react-compose-refs@npm:1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-compose-refs@npm:1.1.2" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/2b9a613b6db5bff8865588b6bf4065f73021b3d16c0a90b2d4c23deceeb63612f1f15de188227ebdc5f88222cab031be617a9dd025874c0487b303be3e5cc2a8 + checksum: 10/9a91f0213014ffa40c5b8aae4debb993be5654217e504e35aa7422887eb2d114486d37e53c482d0fffb00cd44f51b5269fcdf397b280c71666fa11b7f32f165d languageName: node linkType: hard -"@radix-ui/react-context@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-context@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.13.10" +"@radix-ui/react-context@npm:1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-context@npm:1.1.2" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/a02187a3bae3a0f1be5fab5ad19c1ef06ceff1028d957e4d9994f0186f594a9c3d93ee34bacb86d1fa8eb274493362944398e1c17054d12cb3b75384f9ae564b + checksum: 10/156088367de42afa3c7e3acf5f0ba7cad6b359f3d17485585e80c2418434a6ed7cac2602eb73bca265d0091a1ad380f9405c069f103983e53497097ff35ba8f2 languageName: node linkType: hard "@radix-ui/react-dialog@npm:^1.0.4": - version: 1.0.5 - resolution: "@radix-ui/react-dialog@npm:1.0.5" + version: 1.1.15 + resolution: "@radix-ui/react-dialog@npm:1.1.15" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/primitive": "npm:1.0.1" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-context": "npm:1.0.1" - "@radix-ui/react-dismissable-layer": "npm:1.0.5" - "@radix-ui/react-focus-guards": "npm:1.0.1" - "@radix-ui/react-focus-scope": "npm:1.0.4" - "@radix-ui/react-id": "npm:1.0.1" - "@radix-ui/react-portal": "npm:1.0.4" - "@radix-ui/react-presence": "npm:1.0.1" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-slot": "npm:1.0.2" - "@radix-ui/react-use-controllable-state": "npm:1.0.1" - aria-hidden: "npm:^1.1.1" - react-remove-scroll: "npm:2.5.5" + "@radix-ui/primitive": "npm:1.1.3" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-dismissable-layer": "npm:1.1.11" + "@radix-ui/react-focus-guards": "npm:1.1.3" + "@radix-ui/react-focus-scope": "npm:1.1.7" + "@radix-ui/react-id": "npm:1.1.1" + "@radix-ui/react-portal": "npm:1.1.9" + "@radix-ui/react-presence": "npm:1.1.5" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-slot": "npm:1.2.3" + "@radix-ui/react-use-controllable-state": "npm:1.2.2" + aria-hidden: "npm:^1.2.4" + react-remove-scroll: "npm:^2.6.3" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/adbd7301586db712616a0f8dd54a25e7544853cbf61b5d6e279215d479f57ac35157847ee424d54a7e707969a926ca0a7c28934400c9ac224bd0c7cc19229aca + checksum: 10/90ad9ea36d927a05bcc2701b471c2965f6d5d4f446511cd471e62235fc674186997dea081f52e18cb17a1e593828d94da3848e68864fa3acebe29df9b068b240 languageName: node linkType: hard -"@radix-ui/react-direction@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-direction@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.13.10" +"@radix-ui/react-direction@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-direction@npm:1.1.1" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/5336a8b0d4f1cde585d5c2b4448af7b3d948bb63a1aadb37c77771b0e5902dc6266e409cf35fd0edaca7f33e26424be19e64fb8f9d7f7be2d6f1714ea2764210 + checksum: 10/8cc330285f1d06829568042ca9aabd3295be4690ae93683033fc8632b5c4dfc60f5c1312f6e2cae27c196189c719de3cfbcf792ff74800f9ccae0ab4abc1bc92 languageName: node linkType: hard -"@radix-ui/react-dismissable-layer@npm:1.0.5": - version: 1.0.5 - resolution: "@radix-ui/react-dismissable-layer@npm:1.0.5" +"@radix-ui/react-dismissable-layer@npm:1.1.11": + version: 1.1.11 + resolution: "@radix-ui/react-dismissable-layer@npm:1.1.11" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/primitive": "npm:1.0.1" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-use-callback-ref": "npm:1.0.1" - "@radix-ui/react-use-escape-keydown": "npm:1.0.3" + "@radix-ui/primitive": "npm:1.1.3" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + "@radix-ui/react-use-escape-keydown": "npm:1.1.1" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/f1626d69bb50ec226032bb7d8c5abaaf7359c2d7660309b0ed3daaedd91f30717573aac1a1cb82d589b7f915cf464b95a12da0a3b91b6acfefb6fbbc62b992de + checksum: 10/c20772588423379dee47fbe1d45c238c45a3bbe612eaf64a86576bf81821975e256d92ac71f9151e91b94a73068656143a11da9a3e77de7564d2a9926468e37a languageName: node linkType: hard "@radix-ui/react-dropdown-menu@npm:^2.0.5": - version: 2.0.6 - resolution: "@radix-ui/react-dropdown-menu@npm:2.0.6" + version: 2.1.16 + resolution: "@radix-ui/react-dropdown-menu@npm:2.1.16" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/primitive": "npm:1.0.1" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-context": "npm:1.0.1" - "@radix-ui/react-id": "npm:1.0.1" - "@radix-ui/react-menu": "npm:2.0.6" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-use-controllable-state": "npm:1.0.1" + "@radix-ui/primitive": "npm:1.1.3" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-id": "npm:1.1.1" + "@radix-ui/react-menu": "npm:2.1.16" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-controllable-state": "npm:1.2.2" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/efa0728a25ea6689c6f31e02025528a21ca3bdc8a905c551ff356f3a66e024ef7fda62dc38564ac1310b211685357e37329616c72e371974d6bded4170ab43a2 + checksum: 10/da215196b5dde5619cdb424b1b5236159e4bb949974b7f4ffbf047d467c55116229a8f9cf07eae6457afefb4a2b07888bb30542f303045e05d90a4b072941ae2 languageName: node linkType: hard -"@radix-ui/react-focus-guards@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-focus-guards@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.13.10" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/1f8ca8f83b884b3612788d0742f3f054e327856d90a39841a47897dbed95e114ee512362ae314177de226d05310047cabbf66b686ae86ad1b65b6b295be24ef7 - languageName: node - linkType: hard - -"@radix-ui/react-focus-scope@npm:1.0.4": - version: 1.0.4 - resolution: "@radix-ui/react-focus-scope@npm:1.0.4" - dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-use-callback-ref": "npm:1.0.1" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10/3590e74c6b682737c7ac4bf8db41b3df7b09a0320f3836c619e487df9915451e5dafade9923a74383a7366c59e9436f5fff4301d70c0d15928e0e16b36e58bc9 - languageName: node - linkType: hard - -"@radix-ui/react-id@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-id@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-use-layout-effect": "npm:1.0.1" - peerDependencies: - "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/446a453d799cc790dd2a1583ff8328da88271bff64530b5a17c102fa7fb35eece3cf8985359d416f65e330cd81aa7b8fe984ea125fc4f4eaf4b3801d698e49fe - languageName: node - linkType: hard - -"@radix-ui/react-menu@npm:2.0.6": - version: 2.0.6 - resolution: "@radix-ui/react-menu@npm:2.0.6" - dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/primitive": "npm:1.0.1" - "@radix-ui/react-collection": "npm:1.0.3" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-context": "npm:1.0.1" - "@radix-ui/react-direction": "npm:1.0.1" - "@radix-ui/react-dismissable-layer": "npm:1.0.5" - "@radix-ui/react-focus-guards": "npm:1.0.1" - "@radix-ui/react-focus-scope": "npm:1.0.4" - "@radix-ui/react-id": "npm:1.0.1" - "@radix-ui/react-popper": "npm:1.1.3" - "@radix-ui/react-portal": "npm:1.0.4" - "@radix-ui/react-presence": "npm:1.0.1" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-roving-focus": "npm:1.0.4" - "@radix-ui/react-slot": "npm:1.0.2" - "@radix-ui/react-use-callback-ref": "npm:1.0.1" - aria-hidden: "npm:^1.1.1" - react-remove-scroll: "npm:2.5.5" - peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10/8e8c41a46f4fab25b53c5400876f611372491e252d8ef763c3608e571df5aae5524c0a9c210780039b0de6d62affedaa16d189dd4c0148da0984f8f809311032 - languageName: node - linkType: hard - -"@radix-ui/react-popper@npm:1.1.3": +"@radix-ui/react-focus-guards@npm:1.1.3": version: 1.1.3 - resolution: "@radix-ui/react-popper@npm:1.1.3" + resolution: "@radix-ui/react-focus-guards@npm:1.1.3" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/b57878f6cf0ebc3e8d7c5c6bbaad44598daac19c921551ca541c104201048a9a902f3d69196e7a09995fd46e998c309aab64dc30fa184b3609d67d187a6a9c24 + languageName: node + linkType: hard + +"@radix-ui/react-focus-scope@npm:1.1.7": + version: 1.1.7 + resolution: "@radix-ui/react-focus-scope@npm:1.1.7" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/2a7cd00e39e01756999ebf0bdb3401d6a8efa489a7b19e6b629b40bad3022b7b1f616555ccb4b0505bc0ba53e13a1fb51be905db138b16ec39c4fe319fe701d3 + languageName: node + linkType: hard + +"@radix-ui/react-id@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-id@npm:1.1.1" + dependencies: + "@radix-ui/react-use-layout-effect": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/8d68e200778eb3038906870fc869b3d881f4a46715fb20cddd9c76cba42fdaaa4810a3365b6ec2daf0f185b9201fc99d009167f59c7921bc3a139722c2e976db + languageName: node + linkType: hard + +"@radix-ui/react-menu@npm:2.1.16": + version: 2.1.16 + resolution: "@radix-ui/react-menu@npm:2.1.16" + dependencies: + "@radix-ui/primitive": "npm:1.1.3" + "@radix-ui/react-collection": "npm:1.1.7" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-direction": "npm:1.1.1" + "@radix-ui/react-dismissable-layer": "npm:1.1.11" + "@radix-ui/react-focus-guards": "npm:1.1.3" + "@radix-ui/react-focus-scope": "npm:1.1.7" + "@radix-ui/react-id": "npm:1.1.1" + "@radix-ui/react-popper": "npm:1.2.8" + "@radix-ui/react-portal": "npm:1.1.9" + "@radix-ui/react-presence": "npm:1.1.5" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-roving-focus": "npm:1.1.11" + "@radix-ui/react-slot": "npm:1.2.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + aria-hidden: "npm:^1.2.4" + react-remove-scroll: "npm:^2.6.3" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/2ffdfa08822c8c4ffc265d02d16c83d725114f9c0e9b510e73e431306dedddd507ef2861ccd67ec8c0d21cb24cd6401e42f16f3e65b30be627c7e22159151e40 + languageName: node + linkType: hard + +"@radix-ui/react-popper@npm:1.2.8": + version: 1.2.8 + resolution: "@radix-ui/react-popper@npm:1.2.8" dependencies: - "@babel/runtime": "npm:^7.13.10" "@floating-ui/react-dom": "npm:^2.0.0" - "@radix-ui/react-arrow": "npm:1.0.3" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-context": "npm:1.0.1" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-use-callback-ref": "npm:1.0.1" - "@radix-ui/react-use-layout-effect": "npm:1.0.1" - "@radix-ui/react-use-rect": "npm:1.0.1" - "@radix-ui/react-use-size": "npm:1.0.1" - "@radix-ui/rect": "npm:1.0.1" + "@radix-ui/react-arrow": "npm:1.1.7" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" + "@radix-ui/react-use-rect": "npm:1.1.1" + "@radix-ui/react-use-size": "npm:1.1.1" + "@radix-ui/rect": "npm:1.1.1" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/1f70ca09b609122058a58f57fa6bce7e528d96552c9db1a1d214e8e4a9dd305e473dfa0ac7dd400d3d215e54b5cf31020199aca3c2728dc1a716f4c7510838a5 + checksum: 10/01366054e1e63dd9394f77afb9da3367709478a5adf4436c080fc5bbe9456170192ff9d1425d9fae5b246e1ba95173848f84b6f2a06b21b47d966367ec7cb997 languageName: node linkType: hard -"@radix-ui/react-portal@npm:1.0.4": - version: 1.0.4 - resolution: "@radix-ui/react-portal@npm:1.0.4" +"@radix-ui/react-portal@npm:1.1.9": + version: 1.1.9 + resolution: "@radix-ui/react-portal@npm:1.1.9" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-primitive": "npm:1.0.3" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/c4cf35e2f26a89703189d0eef3ceeeb706ae0832e98e558730a5e929ca7c72c7cb510413a24eca94c7732f8d659a1e81942bec7b90540cb73ce9e4885d040b64 + checksum: 10/bd6be39bf021d5c917e2474ecba411e2625171f7ef96862b9af04bbd68833bb3662a7f1fbdeb5a7a237111b10e811e76d2cd03e957dadd6e668ef16541bfbd68 languageName: node linkType: hard -"@radix-ui/react-presence@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-presence@npm:1.0.1" +"@radix-ui/react-presence@npm:1.1.5": + version: 1.1.5 + resolution: "@radix-ui/react-presence@npm:1.1.5" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-use-layout-effect": "npm:1.0.1" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/406f0b5a54ea4e7881e15bddc3863234bb14bf3abd4a6e56ea57c6df6f9265a9ad5cfa158e3a98614f0dcbbb7c5f537e1f7158346e57cc3f29b522d62cf28823 + checksum: 10/4cdb05844c18877efb4b9739b46b7e5850b81d7ede994e75b5d62e8153a43c6e16b3ff9e55ff716e20b74b99b9415a94e97fd636bcb8698d5bbf7ab7b8663f9b languageName: node linkType: hard -"@radix-ui/react-primitive@npm:1.0.3": - version: 1.0.3 - resolution: "@radix-ui/react-primitive@npm:1.0.3" +"@radix-ui/react-primitive@npm:2.1.3": + version: 2.1.3 + resolution: "@radix-ui/react-primitive@npm:2.1.3" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-slot": "npm:1.0.2" + "@radix-ui/react-slot": "npm:1.2.3" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/bedb934ac07c710dc5550a7bfc7065d47e099d958cde1d37e4b1947ae5451f1b7e6f8ff5965e242578bf2c619065e6038c3a3aa779e5eafa7da3e3dbc685799f + checksum: 10/1dbbf932a3527f4e62f210bb72944eff605c3e38c8d3275ed5a5c570c02820ab156169756a65ad9a638d2089a828a04a7903795377384e98c87d0ca456303253 languageName: node linkType: hard -"@radix-ui/react-roving-focus@npm:1.0.4": - version: 1.0.4 - resolution: "@radix-ui/react-roving-focus@npm:1.0.4" +"@radix-ui/react-primitive@npm:2.1.4": + version: 2.1.4 + resolution: "@radix-ui/react-primitive@npm:2.1.4" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/primitive": "npm:1.0.1" - "@radix-ui/react-collection": "npm:1.0.3" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-context": "npm:1.0.1" - "@radix-ui/react-direction": "npm:1.0.1" - "@radix-ui/react-id": "npm:1.0.1" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-use-callback-ref": "npm:1.0.1" - "@radix-ui/react-use-controllable-state": "npm:1.0.1" + "@radix-ui/react-slot": "npm:1.2.4" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/a23ffb1e3e29a8209b94ce3857bf559dcf2175c4f316169dc47d018e8e94cd018dc914331a1d1762f32448e2594b7c8945efaa7059056f9940ce92cc35cc7026 + checksum: 10/a718537c2e66d541e72cc6b92bcf8ba6a7a0b4a30072efadeac0a517eb36f8acc55f2983cc1e345dc0eae18166ebcace4dab0299ed87a79e8ac3f653df9ee437 languageName: node linkType: hard -"@radix-ui/react-slot@npm:1.0.2": - version: 1.0.2 - resolution: "@radix-ui/react-slot@npm:1.0.2" +"@radix-ui/react-roving-focus@npm:1.1.11": + version: 1.1.11 + resolution: "@radix-ui/react-roving-focus@npm:1.1.11" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-compose-refs": "npm:1.0.1" + "@radix-ui/primitive": "npm:1.1.3" + "@radix-ui/react-collection": "npm:1.1.7" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-direction": "npm:1.1.1" + "@radix-ui/react-id": "npm:1.1.1" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + "@radix-ui/react-use-controllable-state": "npm:1.2.2" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/734866561e991438fbcf22af06e56b272ed6ee8f7b536489ee3bf2f736f8b53bf6bc14ebde94834aa0aceda854d018a0ce20bb171defffbaed1f566006cbb887 + "@types/react-dom": + optional: true + checksum: 10/0eddafa942332c95622ab8b53cce2fa25fd0dcaf4797218e9e6725da0734a81a438852cdcb3f588521018f68d38c6c5e50c64fda78c655f4e69dd45681ecc5e7 + languageName: node + linkType: hard + +"@radix-ui/react-slot@npm:1.2.3": + version: 1.2.3 + resolution: "@radix-ui/react-slot@npm:1.2.3" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.2" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/fe484c2741e31d9c20a8fb53c5790a73c0664e2bea35e27f4d484a90c42135fcfffe11a08abfcacb7a8ee2faf013471f0e856818f3ddac8ac51ceb8869e0fd08 + languageName: node + linkType: hard + +"@radix-ui/react-slot@npm:1.2.4": + version: 1.2.4 + resolution: "@radix-ui/react-slot@npm:1.2.4" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.2" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/b37e37455b92789758980359d73ab5a5f5d1c12af480c775519bd15c556b891642d472accf05b30d520751489ca74cdb8fd7866064abc7942f0437371be28e51 languageName: node linkType: hard "@radix-ui/react-tooltip@npm:^1.0.6": - version: 1.0.7 - resolution: "@radix-ui/react-tooltip@npm:1.0.7" + version: 1.2.8 + resolution: "@radix-ui/react-tooltip@npm:1.2.8" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/primitive": "npm:1.0.1" - "@radix-ui/react-compose-refs": "npm:1.0.1" - "@radix-ui/react-context": "npm:1.0.1" - "@radix-ui/react-dismissable-layer": "npm:1.0.5" - "@radix-ui/react-id": "npm:1.0.1" - "@radix-ui/react-popper": "npm:1.1.3" - "@radix-ui/react-portal": "npm:1.0.4" - "@radix-ui/react-presence": "npm:1.0.1" - "@radix-ui/react-primitive": "npm:1.0.3" - "@radix-ui/react-slot": "npm:1.0.2" - "@radix-ui/react-use-controllable-state": "npm:1.0.1" - "@radix-ui/react-visually-hidden": "npm:1.0.3" + "@radix-ui/primitive": "npm:1.1.3" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-dismissable-layer": "npm:1.1.11" + "@radix-ui/react-id": "npm:1.1.1" + "@radix-ui/react-popper": "npm:1.2.8" + "@radix-ui/react-portal": "npm:1.1.9" + "@radix-ui/react-presence": "npm:1.1.5" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-slot": "npm:1.2.3" + "@radix-ui/react-use-controllable-state": "npm:1.2.2" + "@radix-ui/react-visually-hidden": "npm:1.2.3" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/8f075a78db9bfe3dac251266feeb771923176d388c3232f9bad8d85417b5d80d2470697e1c7cae6765d3af16e48552ab9810137c2db193bc37e61b97388e92e8 + checksum: 10/e31857628d998b69616b8994f9627d387ed7bfa453b94e3b18ad2c04de83caf5fcca0ef2f304b1d343e00f183e937d883247d81e386dcc76c7c7c268484bc47c languageName: node linkType: hard -"@radix-ui/react-use-callback-ref@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-use-callback-ref@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.13.10" +"@radix-ui/react-use-callback-ref@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-callback-ref@npm:1.1.1" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/b9fd39911c3644bbda14a84e4fca080682bef84212b8d8931fcaa2d2814465de242c4cfd8d7afb3020646bead9c5e539d478cea0a7031bee8a8a3bb164f3bc4c + checksum: 10/cde8c40f1d4e79e6e71470218163a746858304bad03758ac84dc1f94247a046478e8e397518350c8d6609c84b7e78565441d7505bb3ed573afce82cfdcd19faf languageName: node linkType: hard -"@radix-ui/react-use-controllable-state@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-use-controllable-state@npm:1.0.1" +"@radix-ui/react-use-controllable-state@npm:1.2.2": + version: 1.2.2 + resolution: "@radix-ui/react-use-controllable-state@npm:1.2.2" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-use-callback-ref": "npm:1.0.1" + "@radix-ui/react-use-effect-event": "npm:0.0.2" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/dee2be1937d293c3a492cb6d279fc11495a8f19dc595cdbfe24b434e917302f9ac91db24e8cc5af9a065f3f209c3423115b5442e65a5be9fd1e9091338972be9 + checksum: 10/a100bff3ddecb753dab17444147273c9f70046c5949712c52174b259622eaef12acbf7ebcf289bae4e714eb84d0a7317c1aa44064cd997f327d77b62bc732a7c languageName: node linkType: hard -"@radix-ui/react-use-escape-keydown@npm:1.0.3": - version: 1.0.3 - resolution: "@radix-ui/react-use-escape-keydown@npm:1.0.3" +"@radix-ui/react-use-effect-event@npm:0.0.2": + version: 0.0.2 + resolution: "@radix-ui/react-use-effect-event@npm:0.0.2" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-use-callback-ref": "npm:1.0.1" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/c6ed0d9ce780f67f924980eb305af1f6cce2a8acbaf043a58abe0aa3cc551d9aa76ccee14531df89bbee302ead7ecc7fce330886f82d4672c5eda52f357ef9b8 + checksum: 10/5a1950a30a399ea7e4b98154da9f536737a610de80189b7aacd4f064a89a3cd0d2a48571d527435227252e72e872bdb544ff6ffcfbdd02de2efd011be4aaa902 languageName: node linkType: hard -"@radix-ui/react-use-layout-effect@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-use-layout-effect@npm:1.0.1" +"@radix-ui/react-use-escape-keydown@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-escape-keydown@npm:1.1.1" dependencies: - "@babel/runtime": "npm:^7.13.10" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/bed9c7e8de243a5ec3b93bb6a5860950b0dba359b6680c84d57c7a655e123dec9b5891c5dfe81ab970652e7779fe2ad102a23177c7896dde95f7340817d47ae5 + checksum: 10/0eb0756c2c55ddcde9ff01446ab01c085ab2bf799173e97db7ef5f85126f9e8600225570801a1f64740e6d14c39ffe8eed7c14d29737345a5797f4622ac96f6f languageName: node linkType: hard -"@radix-ui/react-use-rect@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-use-rect@npm:1.0.1" - dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/rect": "npm:1.0.1" +"@radix-ui/react-use-layout-effect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-layout-effect@npm:1.1.1" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/433f07e61e04eb222349825bb05f3591fca131313a1d03709565d6226d8660bd1d0423635553f95ee4fcc25c8f2050972d848808d753c388e2a9ae191ebf17f3 + checksum: 10/bad2ba4f206e6255263582bedfb7868773c400836f9a1b423c0b464ffe4a17e13d3f306d1ce19cf7a19a492e9d0e49747464f2656451bb7c6a99f5a57bd34de2 languageName: node linkType: hard -"@radix-ui/react-use-size@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/react-use-size@npm:1.0.1" +"@radix-ui/react-use-rect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-rect@npm:1.1.1" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-use-layout-effect": "npm:1.0.1" + "@radix-ui/rect": "npm:1.1.1" peerDependencies: "@types/react": "*" - react: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/6cc150ad1e9fa85019c225c5a5d50a0af6cdc4653dad0c21b4b40cd2121f36ee076db326c43e6bc91a69766ccff5a84e917d27970176b592577deea3c85a3e26 + checksum: 10/116461bebc49472f7497e66a9bd413541181b3d00c5e0aaeef45d790dc1fbd7c8dcea80b169ea273306228b9a3c2b70067e902d1fd5004b3057e3bbe35b9d55d languageName: node linkType: hard -"@radix-ui/react-visually-hidden@npm:1.0.3, @radix-ui/react-visually-hidden@npm:^1.0.3": - version: 1.0.3 - resolution: "@radix-ui/react-visually-hidden@npm:1.0.3" +"@radix-ui/react-use-size@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-size@npm:1.1.1" dependencies: - "@babel/runtime": "npm:^7.13.10" - "@radix-ui/react-primitive": "npm:1.0.3" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/64e61f65feb67ffc80e1fc4a8d5e32480fb6d68475e2640377e021178dead101568cba5f936c9c33e6c142c7cf2fb5d76ad7b23ef80e556ba142d56cf306147b + languageName: node + linkType: hard + +"@radix-ui/react-visually-hidden@npm:1.2.3": + version: 1.2.3 + resolution: "@radix-ui/react-visually-hidden@npm:1.2.3" + dependencies: + "@radix-ui/react-primitive": "npm:2.1.3" peerDependencies: "@types/react": "*" "@types/react-dom": "*" - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true "@types/react-dom": optional: true - checksum: 10/2e9d0c8253f97e7d6ffb2e52a5cfd40ba719f813b39c3e2e42c496d54408abd09ef66b5aec4af9b8ab0553215e32452a5d0934597a49c51dd90dc39181ed0d57 + checksum: 10/42296bde1ddf4af4e7445e914c35d6bc8406d6ede49f0a959a553e75b3ed21da09fda80a81c48d8ec058ed8129ce7137499d02ee26f90f0d3eaa2417922d6509 languageName: node linkType: hard -"@radix-ui/rect@npm:1.0.1": - version: 1.0.1 - resolution: "@radix-ui/rect@npm:1.0.1" +"@radix-ui/react-visually-hidden@npm:^1.0.3": + version: 1.2.4 + resolution: "@radix-ui/react-visually-hidden@npm:1.2.4" dependencies: - "@babel/runtime": "npm:^7.13.10" - checksum: 10/e25492cb8a683246161d781f0f3205f79507280a60f50eb763f06e8b6fa211b940b784aa581131ed76695bd5df5d1033a6246b43a6996cf8959a326fe4d3eb00 + "@radix-ui/react-primitive": "npm:2.1.4" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/43bbb68532748fabe8bec52628a7876714889f67e1a21d627c30816e53b68042ece849eef61fbd854af9c528b157cdd483ad032668c95d627ef27edd2530253f languageName: node linkType: hard -"@react-aria/autocomplete@npm:3.0.0-rc.4": - version: 3.0.0-rc.4 - resolution: "@react-aria/autocomplete@npm:3.0.0-rc.4" +"@radix-ui/rect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/rect@npm:1.1.1" + checksum: 10/b6c5eb787640775b53dd52fa47218a089f0a0d8220d3ebff079c0b754e1fb82d89b6bdf08a82fd0d59549bdeb52678c0cca091c302da49dcf74c3c989cb55678 + languageName: node + linkType: hard + +"@react-aria/autocomplete@npm:3.0.0-rc.6": + version: 3.0.0-rc.6 + resolution: "@react-aria/autocomplete@npm:3.0.0-rc.6" dependencies: - "@react-aria/combobox": "npm:^3.14.1" - "@react-aria/focus": "npm:^3.21.3" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/listbox": "npm:^3.15.1" - "@react-aria/searchfield": "npm:^3.8.10" - "@react-aria/textfield": "npm:^3.18.3" - "@react-aria/utils": "npm:^3.32.0" + "@react-aria/combobox": "npm:^3.15.0" + "@react-aria/focus": "npm:^3.21.5" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/listbox": "npm:^3.15.3" + "@react-aria/searchfield": "npm:^3.8.12" + "@react-aria/textfield": "npm:^3.18.5" + "@react-aria/utils": "npm:^3.33.1" "@react-stately/autocomplete": "npm:3.0.0-beta.4" - "@react-stately/combobox": "npm:^3.12.1" - "@react-types/autocomplete": "npm:3.0.0-alpha.36" - "@react-types/button": "npm:^3.14.1" - "@react-types/shared": "npm:^3.32.1" + "@react-stately/combobox": "npm:^3.13.0" + "@react-types/autocomplete": "npm:3.0.0-alpha.38" + "@react-types/button": "npm:^3.15.1" + "@react-types/shared": "npm:^3.33.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/3901ea60c43ff68b381a04db64577de07e46bc63e95c7cb122ace6053a0c54f58ba206704a2cd19799499f4a31ce6413ba65e8af39e675b5cedd66e9a5870729 + checksum: 10/5368f7597369d4b6054562a1812c6fce4ef9276a9618d1a7d043a89e9788415ed5a519ac1172edbaf4e458e5ca179ee6eca354f279515807eb7da8789c0ded90 languageName: node linkType: hard -"@react-aria/breadcrumbs@npm:^3.5.30": - version: 3.5.30 - resolution: "@react-aria/breadcrumbs@npm:3.5.30" +"@react-aria/breadcrumbs@npm:^3.5.32": + version: 3.5.32 + resolution: "@react-aria/breadcrumbs@npm:3.5.32" dependencies: - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/link": "npm:^3.8.7" - "@react-aria/utils": "npm:^3.32.0" - "@react-types/breadcrumbs": "npm:^3.7.17" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/link": "npm:^3.8.9" + "@react-aria/utils": "npm:^3.33.1" + "@react-types/breadcrumbs": "npm:^3.7.19" + "@react-types/shared": "npm:^3.33.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/f1244b6bcacb8cc3351ec2df070e2c7cec671f8beeee7bb83fbc0d062f66688c6fb9e7a1db36e4cbabb94e9349de525979a98697541e466b0d89c9cbf891a1c8 + checksum: 10/37c6153bc330aea36ea55f891170c39f4f19be9b26ed366524e047e54503b7935118c12b57f8952e4e13d2a11835503f50b16952eec7e9477341c77d867b4d9d languageName: node linkType: hard -"@react-aria/button@npm:^3.14.3": - version: 3.14.3 - resolution: "@react-aria/button@npm:3.14.3" +"@react-aria/button@npm:^3.14.5": + version: 3.14.5 + resolution: "@react-aria/button@npm:3.14.5" dependencies: - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/toolbar": "npm:3.0.0-beta.22" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/toggle": "npm:^3.9.3" - "@react-types/button": "npm:^3.14.1" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/toolbar": "npm:3.0.0-beta.24" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/toggle": "npm:^3.9.5" + "@react-types/button": "npm:^3.15.1" + "@react-types/shared": "npm:^3.33.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/383046c3b4ea5972f81f56251fc19b207d6d811d7ac06db866ccd809dbcc1b72358f4b1535fc65817929496c1acb15f7c2611e4777a371333bb1b150b954ccb8 + checksum: 10/944f33637f555b4c3d74c83969623f49fee65ddbd58c14badd04ebbff65a5cd09eb2ebf931ad275403ac824f122fd46a9b63804d33c27380747f7dd4d60c2666 languageName: node linkType: hard -"@react-aria/calendar@npm:^3.9.3": - version: 3.9.3 - resolution: "@react-aria/calendar@npm:3.9.3" +"@react-aria/calendar@npm:^3.9.5": + version: 3.9.5 + resolution: "@react-aria/calendar@npm:3.9.5" dependencies: - "@internationalized/date": "npm:^3.10.1" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" + "@internationalized/date": "npm:^3.12.0" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" "@react-aria/live-announcer": "npm:^3.4.4" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/calendar": "npm:^3.9.1" - "@react-types/button": "npm:^3.14.1" - "@react-types/calendar": "npm:^3.8.1" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/calendar": "npm:^3.9.3" + "@react-types/button": "npm:^3.15.1" + "@react-types/calendar": "npm:^3.8.3" + "@react-types/shared": "npm:^3.33.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/32f7b1cf1c447fb8343d15b468cc79efa0206dd20f7541b7858734f7abfe562a669e0be027f89b4e365a0479eb1cd4c704cde9e03128c68a41e9d16fbde36cad + checksum: 10/358de2ab043c9369be3ca7738757c780c7c8612db7b38ae1d2af47fd16a1d8ec26f4db390df573ff5253d256e74e9163ad2a78a189ef5db60a5709d8b5f55aa1 languageName: node linkType: hard -"@react-aria/checkbox@npm:^3.16.3": - version: 3.16.3 - resolution: "@react-aria/checkbox@npm:3.16.3" +"@react-aria/checkbox@npm:^3.16.5": + version: 3.16.5 + resolution: "@react-aria/checkbox@npm:3.16.5" dependencies: - "@react-aria/form": "npm:^3.1.3" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/label": "npm:^3.7.23" - "@react-aria/toggle": "npm:^3.12.3" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/checkbox": "npm:^3.7.3" - "@react-stately/form": "npm:^3.2.2" - "@react-stately/toggle": "npm:^3.9.3" - "@react-types/checkbox": "npm:^3.10.2" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/form": "npm:^3.1.5" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/label": "npm:^3.7.25" + "@react-aria/toggle": "npm:^3.12.5" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/checkbox": "npm:^3.7.5" + "@react-stately/form": "npm:^3.2.4" + "@react-stately/toggle": "npm:^3.9.5" + "@react-types/checkbox": "npm:^3.10.4" + "@react-types/shared": "npm:^3.33.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/ef7f9f08a9d2953ae5788525932bd8da5fb0bcb1423a5a5551b7ca7ae74b306d9d47b303dfb374f5a7084fcdfc49dc8d779ccd55979f5811844ad5f20931d602 + checksum: 10/3df98e59115545b323aee85e24a8cd19a812fb363c4641a7e7c3310280eb9d0e8c26b9c80b97f0ad3e5c8f7c84b9a320c38d05f78aaab5e3737c1aa01f19dd0a languageName: node linkType: hard -"@react-aria/collections@npm:^3.0.1": - version: 3.0.1 - resolution: "@react-aria/collections@npm:3.0.1" +"@react-aria/collections@npm:^3.0.3": + version: 3.0.3 + resolution: "@react-aria/collections@npm:3.0.3" dependencies: - "@react-aria/interactions": "npm:^3.26.0" + "@react-aria/interactions": "npm:^3.27.1" "@react-aria/ssr": "npm:^3.9.10" - "@react-aria/utils": "npm:^3.32.0" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/utils": "npm:^3.33.1" + "@react-types/shared": "npm:^3.33.1" "@swc/helpers": "npm:^0.5.0" - use-sync-external-store: "npm:^1.4.0" + use-sync-external-store: "npm:^1.6.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/e05897ccd0c994c2a257f6c9dbe167a4d7e5fbeae053833a746870072868ae3bc3f92c7e6f30e4c0d314b1f9450c8d1ffb67600ca89f6d5914577f5cc32ca363 + checksum: 10/099da897b6af195aef54f214facc16cadf524c2c194fd80eab795ae22789277a5870e961ac3307b4c9ab7595f64aa13b8e834c2e545ef6aed4817101361bff50 languageName: node linkType: hard -"@react-aria/color@npm:^3.1.3": - version: 3.1.3 - resolution: "@react-aria/color@npm:3.1.3" +"@react-aria/color@npm:^3.1.5": + version: 3.1.5 + resolution: "@react-aria/color@npm:3.1.5" dependencies: - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/numberfield": "npm:^3.12.3" - "@react-aria/slider": "npm:^3.8.3" - "@react-aria/spinbutton": "npm:^3.7.0" - "@react-aria/textfield": "npm:^3.18.3" - "@react-aria/utils": "npm:^3.32.0" - "@react-aria/visually-hidden": "npm:^3.8.29" - "@react-stately/color": "npm:^3.9.3" - "@react-stately/form": "npm:^3.2.2" - "@react-types/color": "npm:^3.1.2" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/numberfield": "npm:^3.12.5" + "@react-aria/slider": "npm:^3.8.5" + "@react-aria/spinbutton": "npm:^3.7.2" + "@react-aria/textfield": "npm:^3.18.5" + "@react-aria/utils": "npm:^3.33.1" + "@react-aria/visually-hidden": "npm:^3.8.31" + "@react-stately/color": "npm:^3.9.5" + "@react-stately/form": "npm:^3.2.4" + "@react-types/color": "npm:^3.1.4" + "@react-types/shared": "npm:^3.33.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/4ec6de312403d8c98b9af3676a5778a0bc5fcaaf27055390d245ec79dee781cfb70a0747e2769526844041049f702451477debc9574817bc9e32897b821f4ba4 + checksum: 10/7a1d96fdc5e87c9d9eea364f57d580d739fafcb71c6c97b4c558088376b5aa3d7e6b0000da8fb6a42aa28bcfafd85afb62247c51c716ba33a71d741747bfa29b languageName: node linkType: hard -"@react-aria/combobox@npm:^3.14.1": - version: 3.14.1 - resolution: "@react-aria/combobox@npm:3.14.1" +"@react-aria/combobox@npm:^3.15.0": + version: 3.15.0 + resolution: "@react-aria/combobox@npm:3.15.0" dependencies: - "@react-aria/focus": "npm:^3.21.3" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/listbox": "npm:^3.15.1" + "@react-aria/focus": "npm:^3.21.5" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/listbox": "npm:^3.15.3" "@react-aria/live-announcer": "npm:^3.4.4" - "@react-aria/menu": "npm:^3.19.4" - "@react-aria/overlays": "npm:^3.31.0" - "@react-aria/selection": "npm:^3.27.0" - "@react-aria/textfield": "npm:^3.18.3" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/combobox": "npm:^3.12.1" - "@react-stately/form": "npm:^3.2.2" - "@react-types/button": "npm:^3.14.1" - "@react-types/combobox": "npm:^3.13.10" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/menu": "npm:^3.21.0" + "@react-aria/overlays": "npm:^3.31.2" + "@react-aria/selection": "npm:^3.27.2" + "@react-aria/textfield": "npm:^3.18.5" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/collections": "npm:^3.12.10" + "@react-stately/combobox": "npm:^3.13.0" + "@react-stately/form": "npm:^3.2.4" + "@react-types/button": "npm:^3.15.1" + "@react-types/combobox": "npm:^3.14.0" + "@react-types/shared": "npm:^3.33.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/66348b7688b2cfe9c03eca3f0c72b7f1ce852859517b0661b094eb64bda255697df612a9feca550ac4d5b97d63ba291e57c37935604cfa1262cbfc9189046758 + checksum: 10/aca358e7be417159787b9b598754e94fbb88280eff6401f65facc48cb62da8485d3b0652bcf329f01e0081d20a4925b94f318dab7fed34341f38bb09b9bf7202 languageName: node linkType: hard -"@react-aria/datepicker@npm:^3.15.3": - version: 3.15.3 - resolution: "@react-aria/datepicker@npm:3.15.3" +"@react-aria/datepicker@npm:^3.16.1": + version: 3.16.1 + resolution: "@react-aria/datepicker@npm:3.16.1" dependencies: - "@internationalized/date": "npm:^3.10.1" + "@internationalized/date": "npm:^3.12.0" "@internationalized/number": "npm:^3.6.5" "@internationalized/string": "npm:^3.2.7" - "@react-aria/focus": "npm:^3.21.3" - "@react-aria/form": "npm:^3.1.3" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/label": "npm:^3.7.23" - "@react-aria/spinbutton": "npm:^3.7.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/datepicker": "npm:^3.15.3" - "@react-stately/form": "npm:^3.2.2" - "@react-types/button": "npm:^3.14.1" - "@react-types/calendar": "npm:^3.8.1" - "@react-types/datepicker": "npm:^3.13.3" - "@react-types/dialog": "npm:^3.5.22" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/focus": "npm:^3.21.5" + "@react-aria/form": "npm:^3.1.5" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/label": "npm:^3.7.25" + "@react-aria/spinbutton": "npm:^3.7.2" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/datepicker": "npm:^3.16.1" + "@react-stately/form": "npm:^3.2.4" + "@react-types/button": "npm:^3.15.1" + "@react-types/calendar": "npm:^3.8.3" + "@react-types/datepicker": "npm:^3.13.5" + "@react-types/dialog": "npm:^3.5.24" + "@react-types/shared": "npm:^3.33.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/86e4bd36111421f040ac1d5940d48cb4f3d8bdf5d1246da2adfaa6a6817620575040134d0d280cdeff11f46dc281ea1c5af55781a978414aef321ea770f70f1f + checksum: 10/c8a9e5d0fc48c51c86e720007e0be835e33f722e4e15248a3e7232c6a78148784cab85f18fdd5ccfc1c7632d1c89e6602c5118abecbbab0097577c6daaf95bb2 languageName: node linkType: hard -"@react-aria/dialog@npm:^3.5.32": - version: 3.5.32 - resolution: "@react-aria/dialog@npm:3.5.32" +"@react-aria/dialog@npm:^3.5.34": + version: 3.5.34 + resolution: "@react-aria/dialog@npm:3.5.34" dependencies: - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/overlays": "npm:^3.31.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-types/dialog": "npm:^3.5.22" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/overlays": "npm:^3.31.2" + "@react-aria/utils": "npm:^3.33.1" + "@react-types/dialog": "npm:^3.5.24" + "@react-types/shared": "npm:^3.33.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/015d5637ab99d1b751209d743557e638d81533032e4948cc46d5041503508e1f6cda284f6212395dc157d6b58af07d43e5fdc79490153d4fbab3aa15c418dd68 + checksum: 10/9857bf323175341425a0f79952363c62aaebbe6271a3fa3ae9b4b34fb1190e072dd08ffd21ef13a722b6add079142d4728acd112c7cc6d0005ac00d3bb4c9cb1 languageName: node linkType: hard -"@react-aria/disclosure@npm:^3.1.1": - version: 3.1.1 - resolution: "@react-aria/disclosure@npm:3.1.1" +"@react-aria/disclosure@npm:^3.1.3": + version: 3.1.3 + resolution: "@react-aria/disclosure@npm:3.1.3" dependencies: "@react-aria/ssr": "npm:^3.9.10" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/disclosure": "npm:^3.0.9" - "@react-types/button": "npm:^3.14.1" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/disclosure": "npm:^3.0.11" + "@react-types/button": "npm:^3.15.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/8b9d60450e5815031ec32f58c7962588b7ab1ab80606cf5a169a47dd23c320e856dd3426600ff8b6717c194888d8ddb483488de033efa651b1fdfffe47cec524 + checksum: 10/cc64f4131d8985f48ce2becacd536a74ea4f291e157409414119389fe5ec9034ade2332f27efed09e3b50ac04bbe1a7a2a707ca355e7daef3e59349ba56f41b3 languageName: node linkType: hard -"@react-aria/dnd@npm:^3.11.4": - version: 3.11.4 - resolution: "@react-aria/dnd@npm:3.11.4" +"@react-aria/dnd@npm:^3.11.6": + version: 3.11.6 + resolution: "@react-aria/dnd@npm:3.11.6" dependencies: "@internationalized/string": "npm:^3.2.7" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" "@react-aria/live-announcer": "npm:^3.4.4" - "@react-aria/overlays": "npm:^3.31.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/dnd": "npm:^3.7.2" - "@react-types/button": "npm:^3.14.1" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/overlays": "npm:^3.31.2" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/collections": "npm:^3.12.10" + "@react-stately/dnd": "npm:^3.7.4" + "@react-types/button": "npm:^3.15.1" + "@react-types/shared": "npm:^3.33.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/34edadce52664faaa7cc9a63eef1340e4ac7d62b3447818965793e2f0537a6630acaf530a4c8dd4026102e281f5157c43d29a0d8b7a14bc5ea0641bcddb8edb3 + checksum: 10/1d11e7de438154d72ad6acd394919bbc4e82be3beba9d552e7b59590f6a90f671e146d10cad9db1bcb5db9991b05c0c04bbce991d4e24508548079005f2d5920 languageName: node linkType: hard -"@react-aria/focus@npm:^3.21.3": - version: 3.21.3 - resolution: "@react-aria/focus@npm:3.21.3" +"@react-aria/focus@npm:^3.21.5": + version: 3.21.5 + resolution: "@react-aria/focus@npm:3.21.5" dependencies: - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/utils": "npm:^3.33.1" + "@react-types/shared": "npm:^3.33.1" "@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/a69c20b0c95364672f9f201829bf4201d9ef01ea628579b6cf6762b777e05fdbbbd234ab60fba03e313543d79bc13c2d812120b415d3f4680214e2dc2df5a5a6 + checksum: 10/cfc2698626a221b978213104d7d5354e2ab24467397e4c74879a0cb23733a004ca19c40fa55de0927b7d3d9156562b00c4e1e84e20f4894afd706cd8a31400e5 languageName: node linkType: hard -"@react-aria/form@npm:^3.1.3": - version: 3.1.3 - resolution: "@react-aria/form@npm:3.1.3" +"@react-aria/form@npm:^3.1.5": + version: 3.1.5 + resolution: "@react-aria/form@npm:3.1.5" dependencies: - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/form": "npm:^3.2.2" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/form": "npm:^3.2.4" + "@react-types/shared": "npm:^3.33.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/4dbbe0e78c81feef83bd720c2c441e220e51f99d038639fac5b00014739ac892b40b055649735993c2e41d5f731194c16d82112bda90a28a5abc406201e071b0 + checksum: 10/696f6ae4325cf3cfdc8d8fff6a4f614b3a96345ff1c1d0a96875ba8c82c70954acf174ed6d44d397026b598b567bdd75ba84a0ce400b7eb30963cc45df2f2f56 languageName: node linkType: hard -"@react-aria/grid@npm:^3.14.6": - version: 3.14.6 - resolution: "@react-aria/grid@npm:3.14.6" +"@react-aria/grid@npm:^3.14.8": + version: 3.14.8 + resolution: "@react-aria/grid@npm:3.14.8" dependencies: - "@react-aria/focus": "npm:^3.21.3" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" + "@react-aria/focus": "npm:^3.21.5" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" "@react-aria/live-announcer": "npm:^3.4.4" - "@react-aria/selection": "npm:^3.27.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/grid": "npm:^3.11.7" - "@react-stately/selection": "npm:^3.20.7" - "@react-types/checkbox": "npm:^3.10.2" - "@react-types/grid": "npm:^3.3.6" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/selection": "npm:^3.27.2" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/collections": "npm:^3.12.10" + "@react-stately/grid": "npm:^3.11.9" + "@react-stately/selection": "npm:^3.20.9" + "@react-types/checkbox": "npm:^3.10.4" + "@react-types/grid": "npm:^3.3.8" + "@react-types/shared": "npm:^3.33.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/a62695e47044de335628a3802afa05d72e75b9510c2d51d7f20f72875ece717ff411d75f0f0286fb286f222decc7870b885ef82522598a41b5b78775aaedccdf + checksum: 10/7a7bf816b48a441661628aaf3f439b1a7f04fc4719f7c52c7ee6f1530d60df447c6b74a7cfe740bb3b8e344a0931f22092be9496c90c1a24bedb05900cce958d languageName: node linkType: hard -"@react-aria/gridlist@npm:^3.14.2": - version: 3.14.2 - resolution: "@react-aria/gridlist@npm:3.14.2" +"@react-aria/gridlist@npm:^3.14.4": + version: 3.14.4 + resolution: "@react-aria/gridlist@npm:3.14.4" dependencies: - "@react-aria/focus": "npm:^3.21.3" - "@react-aria/grid": "npm:^3.14.6" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/selection": "npm:^3.27.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/list": "npm:^3.13.2" - "@react-stately/tree": "npm:^3.9.4" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/focus": "npm:^3.21.5" + "@react-aria/grid": "npm:^3.14.8" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/selection": "npm:^3.27.2" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/list": "npm:^3.13.4" + "@react-stately/tree": "npm:^3.9.6" + "@react-types/shared": "npm:^3.33.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/d9ddc1744386f33dddfd66fa664054ffaaeb987a1af7ffe36bee971ac3f07b076e7bdee63d01510f7d5a69a2df0f22eae1578dfd428bc024e3a2a7d848716da4 + checksum: 10/94d25960ee8f1e6d5325ddced6e15b3be93461ab454bc20aa5267ffcc086d5bfbf176aeaa53f8ad29ffc406dcbab4a02ac393439cc3cd93a8c243fddb4629809 languageName: node linkType: hard -"@react-aria/i18n@npm:^3.12.14": - version: 3.12.14 - resolution: "@react-aria/i18n@npm:3.12.14" +"@react-aria/i18n@npm:^3.12.16": + version: 3.12.16 + resolution: "@react-aria/i18n@npm:3.12.16" dependencies: - "@internationalized/date": "npm:^3.10.1" + "@internationalized/date": "npm:^3.12.0" "@internationalized/message": "npm:^3.1.8" "@internationalized/number": "npm:^3.6.5" "@internationalized/string": "npm:^3.2.7" "@react-aria/ssr": "npm:^3.9.10" - "@react-aria/utils": "npm:^3.32.0" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/utils": "npm:^3.33.1" + "@react-types/shared": "npm:^3.33.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/87b10d152fc104892621ad344c536fd5308fb3be5954bd9ef7a4cd58fde3ada75feb4f9ed0cb08abd93eb4ec7b43485c7954640bf6cc06a899547e1fbebf594a + checksum: 10/45cf923204c97cab9baa98dbfa2e4ea1c72218939af93992affe3b4b733125533ed7e86d2ed5410b86eb299f2e9afddcc97907fe8523d7027cc5aee1796e7c8d languageName: node linkType: hard -"@react-aria/interactions@npm:^3.26.0": - version: 3.26.0 - resolution: "@react-aria/interactions@npm:3.26.0" +"@react-aria/interactions@npm:^3.27.1": + version: 3.27.1 + resolution: "@react-aria/interactions@npm:3.27.1" dependencies: "@react-aria/ssr": "npm:^3.9.10" - "@react-aria/utils": "npm:^3.32.0" + "@react-aria/utils": "npm:^3.33.1" "@react-stately/flags": "npm:^3.1.2" - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.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/181383a7b029ed32436c25d2212f800c89e85f10eea56f226b49338056d7197b6bc3db593d357e668aa22ee61f04a30e647265a259ff9a465003f79bca7a2abb + checksum: 10/58ef760bfbede659991d670516ac2c04b6230461ae963370d3fa2a86d9da2f8a27c17f76a6549c95af6845fa40f2c3e23d7574748347b6c3bc9581ca36bbaacd languageName: node linkType: hard -"@react-aria/label@npm:^3.7.23": - version: 3.7.23 - resolution: "@react-aria/label@npm:3.7.23" +"@react-aria/label@npm:^3.7.25": + version: 3.7.25 + resolution: "@react-aria/label@npm:3.7.25" dependencies: - "@react-aria/utils": "npm:^3.32.0" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/utils": "npm:^3.33.1" + "@react-types/shared": "npm:^3.33.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/19b2a4649e7913ea3f9b6eb8e48e9b607c3659f07c630d94446e97157fabdc24d7973fcbd960af1fa0401c8d0163ca24b8b1af5f6f3a31ff8657cf03a61d9d77 + checksum: 10/a20fc05c5d16a7086b0d3979608f5f17ff3b830e1ca1760362e1ae8f54842ae4f2d3222c13c00d49aca6f99620b218640dc01dc2717d7d9bd3e9c4156cbfbdcc languageName: node linkType: hard -"@react-aria/landmark@npm:^3.0.8": - version: 3.0.8 - resolution: "@react-aria/landmark@npm:3.0.8" +"@react-aria/landmark@npm:^3.0.10": + version: 3.0.10 + resolution: "@react-aria/landmark@npm:3.0.10" dependencies: - "@react-aria/utils": "npm:^3.32.0" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/utils": "npm:^3.33.1" + "@react-types/shared": "npm:^3.33.1" "@swc/helpers": "npm:^0.5.0" - use-sync-external-store: "npm:^1.4.0" + use-sync-external-store: "npm:^1.6.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/7711871d004bbce5d8fadec3d81350cb9ce1896b01e4cc5f247003fedb9b785d3ca24947ce3f688ca5a8145004dd2cbbf43f1ac44e884c8ed6a950fd40a9e9f2 + checksum: 10/958a3a4da6968f5cb78b07e78beb1572388d2c5e9d9891080fbabfe482d7bdfba54653a85e4256ec1f4519d1bed02133562c2c777a00621cd6a978c7987a18b8 languageName: node linkType: hard -"@react-aria/link@npm:^3.8.7": - version: 3.8.7 - resolution: "@react-aria/link@npm:3.8.7" +"@react-aria/link@npm:^3.8.9": + version: 3.8.9 + resolution: "@react-aria/link@npm:3.8.9" dependencies: - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-types/link": "npm:^3.6.5" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/utils": "npm:^3.33.1" + "@react-types/link": "npm:^3.6.7" + "@react-types/shared": "npm:^3.33.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/25e35bcf096bf96965cd29fbf8eabaeea14222ee0a1a94bf3d146164cb5f12197d0fb4bbabe8d77aaa61703669d549a77ac2c1b658f7bbc375552d25d9d17d54 + checksum: 10/46fd9abd934823761ec82afa63d70358443ad67343b930e1cd9482f1149b4fb62ea54968c7cc2cced63453d973116ebfdb84111a7708f95ccb312fc54ac84596 languageName: node linkType: hard -"@react-aria/listbox@npm:^3.15.1": - version: 3.15.1 - resolution: "@react-aria/listbox@npm:3.15.1" +"@react-aria/listbox@npm:^3.15.3": + version: 3.15.3 + resolution: "@react-aria/listbox@npm:3.15.3" dependencies: - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/label": "npm:^3.7.23" - "@react-aria/selection": "npm:^3.27.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/list": "npm:^3.13.2" - "@react-types/listbox": "npm:^3.7.4" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/label": "npm:^3.7.25" + "@react-aria/selection": "npm:^3.27.2" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/collections": "npm:^3.12.10" + "@react-stately/list": "npm:^3.13.4" + "@react-types/listbox": "npm:^3.7.6" + "@react-types/shared": "npm:^3.33.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/af7717b65d49d9751e0973a1a6ad27a500d3ae5241ff81ef9c94495a21e17e8c4693f3ed119ad4fc72411933b35c2bc377a7a61a43590df05212a02f3c6c23bf + checksum: 10/9e1037c98d17025ad1164d170081527d8486d986092fb550367dfc92efdc226a85da55ad744e42fea744c86cea5d0f09e081765e53551ba1f212bfc3db936f78 languageName: node linkType: hard @@ -15955,237 +16281,239 @@ __metadata: languageName: node linkType: hard -"@react-aria/menu@npm:^3.19.4": - version: 3.19.4 - resolution: "@react-aria/menu@npm:3.19.4" +"@react-aria/menu@npm:^3.21.0": + version: 3.21.0 + resolution: "@react-aria/menu@npm:3.21.0" dependencies: - "@react-aria/focus": "npm:^3.21.3" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/overlays": "npm:^3.31.0" - "@react-aria/selection": "npm:^3.27.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/menu": "npm:^3.9.9" - "@react-stately/selection": "npm:^3.20.7" - "@react-stately/tree": "npm:^3.9.4" - "@react-types/button": "npm:^3.14.1" - "@react-types/menu": "npm:^3.10.5" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/focus": "npm:^3.21.5" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/overlays": "npm:^3.31.2" + "@react-aria/selection": "npm:^3.27.2" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/collections": "npm:^3.12.10" + "@react-stately/menu": "npm:^3.9.11" + "@react-stately/selection": "npm:^3.20.9" + "@react-stately/tree": "npm:^3.9.6" + "@react-types/button": "npm:^3.15.1" + "@react-types/menu": "npm:^3.10.7" + "@react-types/shared": "npm:^3.33.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/be6e508f7484582e179582c60374387e1dde1b1fc6154bffeff50ea7eddff819cd6a13c9938ae88bd98e5c503a19da793b93303255916c224028c1093b4452ce + checksum: 10/b41e5b19ab3c637f19ea387fd861bfaf868f7619325434abeacc3a047af80ce5a90ae604c60656d2586ad76335cdf830ee8e327d1ed3bdb730e50477c08c420c languageName: node linkType: hard -"@react-aria/meter@npm:^3.4.28": - version: 3.4.28 - resolution: "@react-aria/meter@npm:3.4.28" +"@react-aria/meter@npm:^3.4.30": + version: 3.4.30 + resolution: "@react-aria/meter@npm:3.4.30" dependencies: - "@react-aria/progress": "npm:^3.4.28" - "@react-types/meter": "npm:^3.4.13" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/progress": "npm:^3.4.30" + "@react-types/meter": "npm:^3.4.15" + "@react-types/shared": "npm:^3.33.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/5cc4bba8199ec039213b3ac568180e2be58521ff48e7aa65be571ca656ab7dd27afdcaaf22b87f4260c288b44b3c65abff8c3b95f5b9f6264d6a65b51908f000 + checksum: 10/8d0f274aa3ca9da0cff9ca5ad65444978dd6edea51cf98a5560406e33ec614ebfe73cb1d7738d7cad7c62cf7e39ade0f38fbd65f63b2f7c68d2d8008f316578f languageName: node linkType: hard -"@react-aria/numberfield@npm:^3.12.3": - version: 3.12.3 - resolution: "@react-aria/numberfield@npm:3.12.3" +"@react-aria/numberfield@npm:^3.12.5": + version: 3.12.5 + resolution: "@react-aria/numberfield@npm:3.12.5" dependencies: - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/spinbutton": "npm:^3.7.0" - "@react-aria/textfield": "npm:^3.18.3" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/form": "npm:^3.2.2" - "@react-stately/numberfield": "npm:^3.10.3" - "@react-types/button": "npm:^3.14.1" - "@react-types/numberfield": "npm:^3.8.16" - "@react-types/shared": "npm:^3.32.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/6e4bf8ac54d6d62170988f66dd6d7be20b7c34c07275b5b5b357d79b52d397f5af3486135c3414f028a642a5b50ae87bba9a37c280dd5d42bf2daad75a348a98 - languageName: node - linkType: hard - -"@react-aria/overlays@npm:^3.31.0": - version: 3.31.0 - resolution: "@react-aria/overlays@npm:3.31.0" - dependencies: - "@react-aria/focus": "npm:^3.21.3" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/ssr": "npm:^3.9.10" - "@react-aria/utils": "npm:^3.32.0" - "@react-aria/visually-hidden": "npm:^3.8.29" - "@react-stately/overlays": "npm:^3.6.21" - "@react-types/button": "npm:^3.14.1" - "@react-types/overlays": "npm:^3.9.2" - "@react-types/shared": "npm:^3.32.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/85d345aea71e9fdc4491afbc1c1f3f052548eec2cbb7ee7cee76ae3df659225f7edf22c03db08c78165abcefc28eb19d31875754fa1662d07672393bdd81dca1 - languageName: node - linkType: hard - -"@react-aria/progress@npm:^3.4.28": - version: 3.4.28 - resolution: "@react-aria/progress@npm:3.4.28" - dependencies: - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/label": "npm:^3.7.23" - "@react-aria/utils": "npm:^3.32.0" - "@react-types/progress": "npm:^3.5.16" - "@react-types/shared": "npm:^3.32.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/618e8281582a718ff92341716cda6415176725257d6a49f69de1986662344cdcda6de0bb2d5c91fc463bccba287a1ce9163e059d4fe57d93e5f7f8ad5aef5c2e - languageName: node - linkType: hard - -"@react-aria/radio@npm:^3.12.3": - version: 3.12.3 - resolution: "@react-aria/radio@npm:3.12.3" - dependencies: - "@react-aria/focus": "npm:^3.21.3" - "@react-aria/form": "npm:^3.1.3" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/label": "npm:^3.7.23" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/radio": "npm:^3.11.3" - "@react-types/radio": "npm:^3.9.2" - "@react-types/shared": "npm:^3.32.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/6d16727fd8cc226a509c66e4a596691b4ca2a1d9012c56f982cb2c9b4c7db7f01f18473c6a7fc30d6d26fa94a0956a6ba6ef394640616312a496c4cf9d0d5f38 - languageName: node - linkType: hard - -"@react-aria/searchfield@npm:^3.8.10": - version: 3.8.10 - resolution: "@react-aria/searchfield@npm:3.8.10" - dependencies: - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/textfield": "npm:^3.18.3" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/searchfield": "npm:^3.5.17" - "@react-types/button": "npm:^3.14.1" - "@react-types/searchfield": "npm:^3.6.6" - "@react-types/shared": "npm:^3.32.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/54d0fa2eddb4cdccb3587f3b16b9430bd0df8dfe6d8b2f432e8ef724728bdcc700ffebe49f6edb47747ec00fda8c1d74d7a6257f3095527b89959da707009b73 - languageName: node - linkType: hard - -"@react-aria/select@npm:^3.17.1": - version: 3.17.1 - resolution: "@react-aria/select@npm:3.17.1" - dependencies: - "@react-aria/form": "npm:^3.1.3" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/label": "npm:^3.7.23" - "@react-aria/listbox": "npm:^3.15.1" - "@react-aria/menu": "npm:^3.19.4" - "@react-aria/selection": "npm:^3.27.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-aria/visually-hidden": "npm:^3.8.29" - "@react-stately/select": "npm:^3.9.0" - "@react-types/button": "npm:^3.14.1" - "@react-types/select": "npm:^3.12.0" - "@react-types/shared": "npm:^3.32.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/6e9ca9ab204605b75f8948f27055657965a8da3b0867aec1e2e9fd6f1c505c83409670c6a8a35daab0e519caefe7400fa7f1e411d7b9f968a92308c4d6929738 - languageName: node - linkType: hard - -"@react-aria/selection@npm:^3.27.0": - version: 3.27.0 - resolution: "@react-aria/selection@npm:3.27.0" - dependencies: - "@react-aria/focus": "npm:^3.21.3" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/selection": "npm:^3.20.7" - "@react-types/shared": "npm:^3.32.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/48e9127e511861f2627cc3331fc78c6f68a2a88c4ce1ed6da2f1c0ed782da3da5b631feca96182b1a2d1fcafe5dd66f4e1ec0d7b9fc059834656704cacf657a2 - languageName: node - linkType: hard - -"@react-aria/separator@npm:^3.4.14": - version: 3.4.14 - resolution: "@react-aria/separator@npm:3.4.14" - dependencies: - "@react-aria/utils": "npm:^3.32.0" - "@react-types/shared": "npm:^3.32.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/4767b1fa34b7e114fc0d1cea0730a67f10d4bf04d9ae8d9a68e94e795df9c37987000c04e0d0aced808c4a44d98e14ea71040694a6ebdf30dfdda20f2a382be3 - languageName: node - linkType: hard - -"@react-aria/slider@npm:^3.8.3": - version: 3.8.3 - resolution: "@react-aria/slider@npm:3.8.3" - dependencies: - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/label": "npm:^3.7.23" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/slider": "npm:^3.7.3" - "@react-types/shared": "npm:^3.32.1" - "@react-types/slider": "npm:^3.8.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/a2071cee32abd00653f77707f5d56cb163e770e66074504731df0c14183930673de39891b25b7b6f44f84b4941e12d9195bfd431a92bcfe8799424e6d3046600 - languageName: node - linkType: hard - -"@react-aria/spinbutton@npm:^3.7.0": - version: 3.7.0 - resolution: "@react-aria/spinbutton@npm:3.7.0" - dependencies: - "@react-aria/i18n": "npm:^3.12.14" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" "@react-aria/live-announcer": "npm:^3.4.4" - "@react-aria/utils": "npm:^3.32.0" - "@react-types/button": "npm:^3.14.1" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/spinbutton": "npm:^3.7.2" + "@react-aria/textfield": "npm:^3.18.5" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/form": "npm:^3.2.4" + "@react-stately/numberfield": "npm:^3.11.0" + "@react-types/button": "npm:^3.15.1" + "@react-types/numberfield": "npm:^3.8.18" + "@react-types/shared": "npm:^3.33.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/11dc1997f0e2fb345bf07b8669b7bfcd46cdcc739aa047dfa3d2fbc8a53e452f9e31c3fcd1d1b564c9c79a9629350d695a172fe0c9c3a13b21ed54f411ffb4e7 + checksum: 10/c0dd7690c854573be0abb0e9492297dccf0eb2cd09759281a9ce23825853c05ed6fd46ba71bce1a8fa814bca1bb28060c2de19abaeca5a2d7a3000352d7975ea + languageName: node + linkType: hard + +"@react-aria/overlays@npm:^3.31.2": + version: 3.31.2 + resolution: "@react-aria/overlays@npm:3.31.2" + dependencies: + "@react-aria/focus": "npm:^3.21.5" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/ssr": "npm:^3.9.10" + "@react-aria/utils": "npm:^3.33.1" + "@react-aria/visually-hidden": "npm:^3.8.31" + "@react-stately/flags": "npm:^3.1.2" + "@react-stately/overlays": "npm:^3.6.23" + "@react-types/button": "npm:^3.15.1" + "@react-types/overlays": "npm:^3.9.4" + "@react-types/shared": "npm:^3.33.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/fdd732da9c5d1f4848a59052978992ff4a0dffb24567af6b688bdc0497fa21f6e5978d6d36e3607741685cf542ee70a9c60fa0775704cd3b1627726cf9468cb9 + languageName: node + linkType: hard + +"@react-aria/progress@npm:^3.4.30": + version: 3.4.30 + resolution: "@react-aria/progress@npm:3.4.30" + dependencies: + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/label": "npm:^3.7.25" + "@react-aria/utils": "npm:^3.33.1" + "@react-types/progress": "npm:^3.5.18" + "@react-types/shared": "npm:^3.33.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/f463723e7a252110980e47fc5258e8e60f6ec503351ff06cb633dd46a34fa5b16e806441ba2db6af2590afb55ba69473b3e94b0f3d2c602a3d5a53c9a1e13eec + languageName: node + linkType: hard + +"@react-aria/radio@npm:^3.12.5": + version: 3.12.5 + resolution: "@react-aria/radio@npm:3.12.5" + dependencies: + "@react-aria/focus": "npm:^3.21.5" + "@react-aria/form": "npm:^3.1.5" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/label": "npm:^3.7.25" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/radio": "npm:^3.11.5" + "@react-types/radio": "npm:^3.9.4" + "@react-types/shared": "npm:^3.33.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/9e88093d9eba0fef7ecb7ebc007745b3af9c7f6aa722269233c5b3bdd84d699c4866529f488ac46f08386e8515b3dad717b37faf41cbd9954840a7aa7b5056ac + languageName: node + linkType: hard + +"@react-aria/searchfield@npm:^3.8.12": + version: 3.8.12 + resolution: "@react-aria/searchfield@npm:3.8.12" + dependencies: + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/textfield": "npm:^3.18.5" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/searchfield": "npm:^3.5.19" + "@react-types/button": "npm:^3.15.1" + "@react-types/searchfield": "npm:^3.6.8" + "@react-types/shared": "npm:^3.33.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/12c3bd063b361ea2a9c4b2f4a1b6888b9bd98d419ccdf3684f32f0cafb5a99a19cfe858f002a49f5a969cbf02756656144b0d2d87d46c547b31d64770cfe6fdc + languageName: node + linkType: hard + +"@react-aria/select@npm:^3.17.3": + version: 3.17.3 + resolution: "@react-aria/select@npm:3.17.3" + dependencies: + "@react-aria/form": "npm:^3.1.5" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/label": "npm:^3.7.25" + "@react-aria/listbox": "npm:^3.15.3" + "@react-aria/menu": "npm:^3.21.0" + "@react-aria/selection": "npm:^3.27.2" + "@react-aria/utils": "npm:^3.33.1" + "@react-aria/visually-hidden": "npm:^3.8.31" + "@react-stately/select": "npm:^3.9.2" + "@react-types/button": "npm:^3.15.1" + "@react-types/select": "npm:^3.12.2" + "@react-types/shared": "npm:^3.33.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/0741f0cc13073ae80e1ac3241bc40c54588fc6da4eb74360becb585d723b021b7f9cea7b6365bb22a307e8539b307fbc2c0b31a304118f10eda0a98f503a303a + languageName: node + linkType: hard + +"@react-aria/selection@npm:^3.27.2": + version: 3.27.2 + resolution: "@react-aria/selection@npm:3.27.2" + dependencies: + "@react-aria/focus": "npm:^3.21.5" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/selection": "npm:^3.20.9" + "@react-types/shared": "npm:^3.33.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/b6730dad9f3f532780a1ccd8fab2e1777dab71d8f38e2bbdb94903df4fe8cde81e843a026d8d24a0fe9b4899f1dccdddc29734ad57e4a758618d3a5226ef2dd2 + languageName: node + linkType: hard + +"@react-aria/separator@npm:^3.4.16": + version: 3.4.16 + resolution: "@react-aria/separator@npm:3.4.16" + dependencies: + "@react-aria/utils": "npm:^3.33.1" + "@react-types/shared": "npm:^3.33.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/697775caf657493a469c445cde847bbe800fe6b199ef852a6fce6b9e78775b126ee76089ebd86d3defe640506d2c218dc34a737745cbc2ee4175aa87ad06be82 + languageName: node + linkType: hard + +"@react-aria/slider@npm:^3.8.5": + version: 3.8.5 + resolution: "@react-aria/slider@npm:3.8.5" + dependencies: + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/label": "npm:^3.7.25" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/slider": "npm:^3.7.5" + "@react-types/shared": "npm:^3.33.1" + "@react-types/slider": "npm:^3.8.4" + "@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/d832288bf797b4815ad1611f2117a3ccac8c514daf4801cefb8e3e6a3f9683ac038d795506870641f9047448d4f7dc07b67bb5a0c65b779d3b86266921492a81 + languageName: node + linkType: hard + +"@react-aria/spinbutton@npm:^3.7.2": + version: 3.7.2 + resolution: "@react-aria/spinbutton@npm:3.7.2" + dependencies: + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/live-announcer": "npm:^3.4.4" + "@react-aria/utils": "npm:^3.33.1" + "@react-types/button": "npm:^3.15.1" + "@react-types/shared": "npm:^3.33.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/ccf48a64cbc6942d35eebc79c1559baa9eccae6a4d1c4cc6026d9d6b6aa09a5eeb8de75126173c4c457647bb43867f10bc9548bc151769f75ab461d0e34aeb34 languageName: node linkType: hard @@ -16200,242 +16528,242 @@ __metadata: languageName: node linkType: hard -"@react-aria/switch@npm:^3.7.9": - version: 3.7.9 - resolution: "@react-aria/switch@npm:3.7.9" +"@react-aria/switch@npm:^3.7.11": + version: 3.7.11 + resolution: "@react-aria/switch@npm:3.7.11" dependencies: - "@react-aria/toggle": "npm:^3.12.3" - "@react-stately/toggle": "npm:^3.9.3" - "@react-types/shared": "npm:^3.32.1" - "@react-types/switch": "npm:^3.5.15" + "@react-aria/toggle": "npm:^3.12.5" + "@react-stately/toggle": "npm:^3.9.5" + "@react-types/shared": "npm:^3.33.1" + "@react-types/switch": "npm:^3.5.17" "@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/d03fc7c082253fb84bd7949705a9672b87876710c5fd73df987b80753e749f42955822adf8f4ba2b586543848574743dd69ff89461bc994ff4156e03a2003a2f + checksum: 10/d4a3ad78cfe264b7f595287c3b516f9d46d38523d6e5b1562a4f86ebe2c099bbca7203ccdf395634484f278dd69f9821cdc1c5a80f6d1dc7c7a73b27f507939a languageName: node linkType: hard -"@react-aria/table@npm:^3.17.9": - version: 3.17.9 - resolution: "@react-aria/table@npm:3.17.9" +"@react-aria/table@npm:^3.17.11": + version: 3.17.11 + resolution: "@react-aria/table@npm:3.17.11" dependencies: - "@react-aria/focus": "npm:^3.21.3" - "@react-aria/grid": "npm:^3.14.6" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" + "@react-aria/focus": "npm:^3.21.5" + "@react-aria/grid": "npm:^3.14.8" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" "@react-aria/live-announcer": "npm:^3.4.4" - "@react-aria/utils": "npm:^3.32.0" - "@react-aria/visually-hidden": "npm:^3.8.29" - "@react-stately/collections": "npm:^3.12.8" + "@react-aria/utils": "npm:^3.33.1" + "@react-aria/visually-hidden": "npm:^3.8.31" + "@react-stately/collections": "npm:^3.12.10" "@react-stately/flags": "npm:^3.1.2" - "@react-stately/table": "npm:^3.15.2" - "@react-types/checkbox": "npm:^3.10.2" - "@react-types/grid": "npm:^3.3.6" - "@react-types/shared": "npm:^3.32.1" - "@react-types/table": "npm:^3.13.4" + "@react-stately/table": "npm:^3.15.4" + "@react-types/checkbox": "npm:^3.10.4" + "@react-types/grid": "npm:^3.3.8" + "@react-types/shared": "npm:^3.33.1" + "@react-types/table": "npm:^3.13.6" "@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/daa03e2eb669034ac8c40f5cae1215a9b7ac059f249f5cd64d3fe541546ac10fc14d44bdb00b02f2bd2fb078a0218e1d08a74f1322713605c5c6fed416770722 + checksum: 10/66747f768cacf954e285a4b823c971f27d07f58fff06de65ee9c5cd8f10ba2acbb1675f3b8757ceece5c6bb2997694e0d2b1e7ebb9e7e06f61d8e3f491c72589 languageName: node linkType: hard -"@react-aria/tabs@npm:^3.10.9": - version: 3.10.9 - resolution: "@react-aria/tabs@npm:3.10.9" +"@react-aria/tabs@npm:^3.11.1": + version: 3.11.1 + resolution: "@react-aria/tabs@npm:3.11.1" dependencies: - "@react-aria/focus": "npm:^3.21.3" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/selection": "npm:^3.27.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/tabs": "npm:^3.8.7" - "@react-types/shared": "npm:^3.32.1" - "@react-types/tabs": "npm:^3.3.20" + "@react-aria/focus": "npm:^3.21.5" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/selection": "npm:^3.27.2" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/tabs": "npm:^3.8.9" + "@react-types/shared": "npm:^3.33.1" + "@react-types/tabs": "npm:^3.3.22" "@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/c685ae601dd7bd1e3a69c0d066cf1dc06d8105d14f12056909b66d5bb36ef1697048fbb0f0c48d653e71904f512533510a13c590f23bba39bc3322e458a6c636 + checksum: 10/dc8d9ff2f316fadc6bdde2447f1eb9f013fcf97d18c21e61572de1a304da6fb2794594ccb3de2f3237e0094ef78f8df19d01c07c10ed0723116e90fe00e2cd73 languageName: node linkType: hard -"@react-aria/tag@npm:^3.7.3": - version: 3.7.3 - resolution: "@react-aria/tag@npm:3.7.3" +"@react-aria/tag@npm:^3.8.1": + version: 3.8.1 + resolution: "@react-aria/tag@npm:3.8.1" dependencies: - "@react-aria/gridlist": "npm:^3.14.2" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/label": "npm:^3.7.23" - "@react-aria/selection": "npm:^3.27.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/list": "npm:^3.13.2" - "@react-types/button": "npm:^3.14.1" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/gridlist": "npm:^3.14.4" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/label": "npm:^3.7.25" + "@react-aria/selection": "npm:^3.27.2" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/list": "npm:^3.13.4" + "@react-types/button": "npm:^3.15.1" + "@react-types/shared": "npm:^3.33.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/e9be2003bf7ec6fbb4956be12098089be184b1eba05f2c3fd9fe100c7c7b93959090020f96b1967484264a9ec24731d8d7b03c160f3c17e57c9effe73a320a9f + checksum: 10/246529ed55848886a630274ae1a1ce40807653d7d4215ff58472b7d5b6ed2a26c76413ab45c88db023a07ab3c2407edf24b9a525fd557b06f80ae689a32dc9ba languageName: node linkType: hard -"@react-aria/textfield@npm:^3.18.3": - version: 3.18.3 - resolution: "@react-aria/textfield@npm:3.18.3" +"@react-aria/textfield@npm:^3.18.5": + version: 3.18.5 + resolution: "@react-aria/textfield@npm:3.18.5" dependencies: - "@react-aria/form": "npm:^3.1.3" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/label": "npm:^3.7.23" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/form": "npm:^3.2.2" + "@react-aria/form": "npm:^3.1.5" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/label": "npm:^3.7.25" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/form": "npm:^3.2.4" "@react-stately/utils": "npm:^3.11.0" - "@react-types/shared": "npm:^3.32.1" - "@react-types/textfield": "npm:^3.12.6" + "@react-types/shared": "npm:^3.33.1" + "@react-types/textfield": "npm:^3.12.8" "@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/3983663883915e1aafd61caad890811be61de5ff0d6310dae01d9cb8c4abc2915a9b3604fc5817b2559f7ec561105f8afb544d07e38835138ed070217aeada98 + checksum: 10/fbad840d923ad9c63462995b9fd7a845775e8e67b8624cee26223a09252f420f5cc3e307e6f08a338fb057dbcc3b18bc6cf4fb1337e27f193efe5ce454beb3bf languageName: node linkType: hard -"@react-aria/toast@npm:^3.0.9": - version: 3.0.9 - resolution: "@react-aria/toast@npm:3.0.9" +"@react-aria/toast@npm:^3.0.11": + version: 3.0.11 + resolution: "@react-aria/toast@npm:3.0.11" dependencies: - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/landmark": "npm:^3.0.8" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/toast": "npm:^3.1.2" - "@react-types/button": "npm:^3.14.1" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/landmark": "npm:^3.0.10" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/toast": "npm:^3.1.3" + "@react-types/button": "npm:^3.15.1" + "@react-types/shared": "npm:^3.33.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/f63cf143eec3e763ddd2885172685c6b85aaac1fb4423ced81aeceba06121788664909bfde862815e2562a7acd03c54df9a83e3fd0ddc60885806f76df30d2bf + checksum: 10/06369d0b65e4c1309914e07e73584d60743dde0425f276fb531ac85f3a5ecc3dbde295f759a64167af3aec624afd472ac17dd8516e74354ec2d875d21f2e71e4 languageName: node linkType: hard -"@react-aria/toggle@npm:^3.12.3": - version: 3.12.3 - resolution: "@react-aria/toggle@npm:3.12.3" +"@react-aria/toggle@npm:^3.12.5": + version: 3.12.5 + resolution: "@react-aria/toggle@npm:3.12.5" dependencies: - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/toggle": "npm:^3.9.3" - "@react-types/checkbox": "npm:^3.10.2" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/toggle": "npm:^3.9.5" + "@react-types/checkbox": "npm:^3.10.4" + "@react-types/shared": "npm:^3.33.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/78f48f6024c864b8ea3d7a23f8ecfaaa7af625a470b229b080338262d9934c849be99f323180730c931da76326241aa4ef94b362c74bdbcbfc6fc71848e67300 + checksum: 10/43c70eb64f13c9c9aea40e0ed5db51f36736ea3688646d47cb1e68c14f56e6473a08569806532a3aa84c7d23c21bc5a62535672e23183fd2713d57ca1e10c379 languageName: node linkType: hard -"@react-aria/toolbar@npm:3.0.0-beta.22": - version: 3.0.0-beta.22 - resolution: "@react-aria/toolbar@npm:3.0.0-beta.22" +"@react-aria/toolbar@npm:3.0.0-beta.24": + version: 3.0.0-beta.24 + resolution: "@react-aria/toolbar@npm:3.0.0-beta.24" dependencies: - "@react-aria/focus": "npm:^3.21.3" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/utils": "npm:^3.32.0" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/focus": "npm:^3.21.5" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/utils": "npm:^3.33.1" + "@react-types/shared": "npm:^3.33.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/587ddaff3b11e58e4d0fb0cef59d8d7fc0481eb43f473c3cbaf6fe174a5e9ff52323636f767e2ada6b41e74ae73bab0a816846232ec1e33f35709cf91abcf49c + checksum: 10/04daf89e29c916454721d8b9462e2bfa7e1a883df2f6a3550425bf96fcd969a7bd60cef28da41aa5f24e0a34fa44fc1dd142dcedb7161476dc00aebecc78c398 languageName: node linkType: hard -"@react-aria/tooltip@npm:^3.9.0": - version: 3.9.0 - resolution: "@react-aria/tooltip@npm:3.9.0" +"@react-aria/tooltip@npm:^3.9.2": + version: 3.9.2 + resolution: "@react-aria/tooltip@npm:3.9.2" dependencies: - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/tooltip": "npm:^3.5.9" - "@react-types/shared": "npm:^3.32.1" - "@react-types/tooltip": "npm:^3.5.0" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/tooltip": "npm:^3.5.11" + "@react-types/shared": "npm:^3.33.1" + "@react-types/tooltip": "npm:^3.5.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/7c29f50c25ea4242efceaed6ec4c4183711c444874edbb69e4d96233482e62396ddc86e948cd46f2e8f3cd02f1a5fb9539b2c40e529a1fb41314ffdeb81ea2db + checksum: 10/b702c1402318dc5e020bab2d08dc63ee6d94a90d8f00efbb6ed01109cb30829b98231fdb7b940219837053c102eb02904867aff6d080b31ca5b7b0e102913799 languageName: node linkType: hard -"@react-aria/tree@npm:^3.1.5": - version: 3.1.5 - resolution: "@react-aria/tree@npm:3.1.5" +"@react-aria/tree@npm:^3.1.7": + version: 3.1.7 + resolution: "@react-aria/tree@npm:3.1.7" dependencies: - "@react-aria/gridlist": "npm:^3.14.2" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/selection": "npm:^3.27.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/tree": "npm:^3.9.4" - "@react-types/button": "npm:^3.14.1" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/gridlist": "npm:^3.14.4" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/selection": "npm:^3.27.2" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/tree": "npm:^3.9.6" + "@react-types/button": "npm:^3.15.1" + "@react-types/shared": "npm:^3.33.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/7af49904f33d80bc7cfc5d60cded17737034518a961b39fbfe492da6c9683bf290de5f4c0f960ee4176631dc129928811f57e5510ce0fef5cb7cbdd3fc663ca4 + checksum: 10/fdb8451d2756c8096d65e7e433117fabdc2de7202305510188fe34f0ace88e026971cdea0970c1c827522f64e2498f6bed0cfb78e904cfa167ae243440087ed9 languageName: node linkType: hard -"@react-aria/utils@npm:^3.32.0": - version: 3.32.0 - resolution: "@react-aria/utils@npm:3.32.0" +"@react-aria/utils@npm:^3.33.1": + version: 3.33.1 + resolution: "@react-aria/utils@npm:3.33.1" dependencies: "@react-aria/ssr": "npm:^3.9.10" "@react-stately/flags": "npm:^3.1.2" "@react-stately/utils": "npm:^3.11.0" - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" "@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/be60a6b175568880828f3d2d492386458c78b90574601d3f70689e1fabd3d0af26b6e6006b3ac919af5332e7b096e5982a107b8413cf6c10419049b3914465bf + checksum: 10/676de77e907862fc44963657a370a45242bfbec2cd54a2ed0935a709315e31415c7c045e2721f067c315418e9aa8c46aa9bd18cb6010820af357c17e335cba47 languageName: node linkType: hard -"@react-aria/virtualizer@npm:^4.1.11": - version: 4.1.11 - resolution: "@react-aria/virtualizer@npm:4.1.11" +"@react-aria/virtualizer@npm:^4.1.13": + version: 4.1.13 + resolution: "@react-aria/virtualizer@npm:4.1.13" dependencies: - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-stately/virtualizer": "npm:^4.4.4" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/virtualizer": "npm:^4.4.6" + "@react-types/shared": "npm:^3.33.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/c5cab6bc99affea02bc3f77618374e3c2e8e4ffa900ebee21a201acbeae1c3e24e9fe75ba810b8aa5432d36841cecddf3e9fde9886e18b9fd35c9db26f73ad24 + checksum: 10/71ad38a1c1997c9f66a16a56743dfc334f71cbf91ef0ae373d553157b784c0e5f2c2bb197fdf88475aebb178a2556ce7a33b8759a82d75dc14cb1720c608bd7a languageName: node linkType: hard -"@react-aria/visually-hidden@npm:^3.8.29": - version: 3.8.29 - resolution: "@react-aria/visually-hidden@npm:3.8.29" +"@react-aria/visually-hidden@npm:^3.8.31": + version: 3.8.31 + resolution: "@react-aria/visually-hidden@npm:3.8.31" dependencies: - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/utils": "npm:^3.32.0" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/utils": "npm:^3.33.1" + "@react-types/shared": "npm:^3.33.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/0009fab6147d069cd0b7cd02245d4a2c2e377596eb080d85725c0f9bc682e6e93713be633ca05877dbaa04e3e5563783b4cb42364005503747dab32ea8d2abc5 + checksum: 10/22a473a153edb94ba167f6bb470a7ec864134bdd0159e6b8801d0564ea02f5e7cc32f935a80f60b38b69863d747f1c6372ffd4006010448383de156551659c8d languageName: node linkType: hard @@ -16474,138 +16802,139 @@ __metadata: languageName: node linkType: hard -"@react-stately/calendar@npm:^3.9.1": - version: 3.9.1 - resolution: "@react-stately/calendar@npm:3.9.1" - dependencies: - "@internationalized/date": "npm:^3.10.1" - "@react-stately/utils": "npm:^3.11.0" - "@react-types/calendar": "npm:^3.8.1" - "@react-types/shared": "npm:^3.32.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/83b6c45ccaad5f888f6491124dfd27d6dfe3ef875a4c698c4d960fdad21749082d3812a4ff925ea63b2a122db31abe765c0c91855c0d7bcb17eae0ebc4c64dca - languageName: node - linkType: hard - -"@react-stately/checkbox@npm:^3.7.3": - version: 3.7.3 - resolution: "@react-stately/checkbox@npm:3.7.3" - dependencies: - "@react-stately/form": "npm:^3.2.2" - "@react-stately/utils": "npm:^3.11.0" - "@react-types/checkbox": "npm:^3.10.2" - "@react-types/shared": "npm:^3.32.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/eb8861fd78a81dd8f27628b17074b011f4d2a26c17f5508452775a942bc4da39cd2b452b0a1de8411b80f62f568478a01a92c61f5b4cb1e2d49d2fdb6784a55b - languageName: node - linkType: hard - -"@react-stately/collections@npm:^3.12.8": - version: 3.12.8 - resolution: "@react-stately/collections@npm:3.12.8" - dependencies: - "@react-types/shared": "npm:^3.32.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/da17c50d9323002f8dc05870265d54afa162575ceba83bc42d8ccd5d0ccf3bc0634d3896086e7975e1e7f1d7497de6f09ca0e5b82a697ad04349d5b59eca5a8f - languageName: node - linkType: hard - -"@react-stately/color@npm:^3.9.3": +"@react-stately/calendar@npm:^3.9.3": version: 3.9.3 - resolution: "@react-stately/color@npm:3.9.3" + resolution: "@react-stately/calendar@npm:3.9.3" + dependencies: + "@internationalized/date": "npm:^3.12.0" + "@react-stately/utils": "npm:^3.11.0" + "@react-types/calendar": "npm:^3.8.3" + "@react-types/shared": "npm:^3.33.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/0faaf9c74cd5ca4b09781614c0daf745f07abc865bb7f10cab605225be65a6c5c45359d9d52de2a4ec2cb84a84572b2078ed07b4da3f4981d20e7a1d95bf6653 + languageName: node + linkType: hard + +"@react-stately/checkbox@npm:^3.7.5": + version: 3.7.5 + resolution: "@react-stately/checkbox@npm:3.7.5" + dependencies: + "@react-stately/form": "npm:^3.2.4" + "@react-stately/utils": "npm:^3.11.0" + "@react-types/checkbox": "npm:^3.10.4" + "@react-types/shared": "npm:^3.33.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/4bee7ba03a8bd7e4b0aeadc138ff888a5c64490ff3172dbbe21e40929501ca70317b53a3aa8c64aa6e35d442fc2c4da4297e7e8e7e8e33f900bb4c2607ba39b5 + languageName: node + linkType: hard + +"@react-stately/collections@npm:^3.12.10": + version: 3.12.10 + resolution: "@react-stately/collections@npm:3.12.10" + dependencies: + "@react-types/shared": "npm:^3.33.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/25b33fcef3ed21398d1b60848d49a69f45a5ff89b974e225f34363c5e2133551ed818cd4841d7a133a51bf24284e6d291739bf4e56b34845c4b12401c16e8e0d + languageName: node + linkType: hard + +"@react-stately/color@npm:^3.9.5": + version: 3.9.5 + resolution: "@react-stately/color@npm:3.9.5" dependencies: "@internationalized/number": "npm:^3.6.5" "@internationalized/string": "npm:^3.2.7" - "@react-stately/form": "npm:^3.2.2" - "@react-stately/numberfield": "npm:^3.10.3" - "@react-stately/slider": "npm:^3.7.3" + "@react-stately/form": "npm:^3.2.4" + "@react-stately/numberfield": "npm:^3.11.0" + "@react-stately/slider": "npm:^3.7.5" "@react-stately/utils": "npm:^3.11.0" - "@react-types/color": "npm:^3.1.2" - "@react-types/shared": "npm:^3.32.1" + "@react-types/color": "npm:^3.1.4" + "@react-types/shared": "npm:^3.33.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/7649122020876679e1ea660c2c286e09bca0d1ce1752729e15705de68bec58c62255d3f1f3172528018e94c55e3dc19a36e76204588efb7a6d06eda09dc2c3fd + checksum: 10/901994e074ac2d2d51f7f956d1de59d4f2fa267caeaa325e8078eb2904fbd4d232cc645baba333fff47c837e3f390513a7d2d3540f4819b0a24243668cd581ec languageName: node linkType: hard -"@react-stately/combobox@npm:^3.12.1": - version: 3.12.1 - resolution: "@react-stately/combobox@npm:3.12.1" +"@react-stately/combobox@npm:^3.13.0": + version: 3.13.0 + resolution: "@react-stately/combobox@npm:3.13.0" dependencies: - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/form": "npm:^3.2.2" - "@react-stately/list": "npm:^3.13.2" - "@react-stately/overlays": "npm:^3.6.21" + "@react-stately/collections": "npm:^3.12.10" + "@react-stately/form": "npm:^3.2.4" + "@react-stately/list": "npm:^3.13.4" + "@react-stately/overlays": "npm:^3.6.23" "@react-stately/utils": "npm:^3.11.0" - "@react-types/combobox": "npm:^3.13.10" - "@react-types/shared": "npm:^3.32.1" + "@react-types/combobox": "npm:^3.14.0" + "@react-types/shared": "npm:^3.33.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/c1198f1b897e50a7ac5aefeb8f28e3873d165d7355eda15279ef762e02c33245035da2672c208cf806290d16bfa2a0a7a4ff0c6cfa893ed39128605a1b265da5 + checksum: 10/84188013c209229c13b2999dff3e8492629d74b74894bae290335fe824f047cf0cab46a01ddff0c0adcd0a5661b349d5e01365eeb72c35b89cb48efb20428c93 languageName: node linkType: hard -"@react-stately/data@npm:^3.15.0": - version: 3.15.0 - resolution: "@react-stately/data@npm:3.15.0" +"@react-stately/data@npm:^3.15.2": + version: 3.15.2 + resolution: "@react-stately/data@npm:3.15.2" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.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/9aac5846536ad65b319507124825aa88043fbff72fbf0a4d4e1d52726eab6a0e876bd125f2ac03bb9e6ba4cd4ae72f2ae398662ef614e46ed34ab64775710776 + checksum: 10/7ca81878ac240e034e16e54146c9b143c770631c5e17ad2ac5edac58ab380b41ff117309119397e8659d99515705659034d9f1dcf01e16a65366b388846c6949 languageName: node linkType: hard -"@react-stately/datepicker@npm:^3.15.3": - version: 3.15.3 - resolution: "@react-stately/datepicker@npm:3.15.3" +"@react-stately/datepicker@npm:^3.16.1": + version: 3.16.1 + resolution: "@react-stately/datepicker@npm:3.16.1" dependencies: - "@internationalized/date": "npm:^3.10.1" + "@internationalized/date": "npm:^3.12.0" + "@internationalized/number": "npm:^3.6.5" "@internationalized/string": "npm:^3.2.7" - "@react-stately/form": "npm:^3.2.2" - "@react-stately/overlays": "npm:^3.6.21" + "@react-stately/form": "npm:^3.2.4" + "@react-stately/overlays": "npm:^3.6.23" "@react-stately/utils": "npm:^3.11.0" - "@react-types/datepicker": "npm:^3.13.3" - "@react-types/shared": "npm:^3.32.1" + "@react-types/datepicker": "npm:^3.13.5" + "@react-types/shared": "npm:^3.33.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/6f9067a112541a944824bf807c820c2b5925c653112e6334e8914d0ee3d33c2caced4e2ba402495aa4867e7b208c9c724fe81b30c0e26adfa8ca041497db5518 + checksum: 10/1b35aaa10e088c004e070e6df253fd5f23cb6a02992fdbc84d4f702f65ee40d7c6ca18df633888c4d71029fc9814ef44c95330714431c046ee2e807547afaa93 languageName: node linkType: hard -"@react-stately/disclosure@npm:^3.0.9": - version: 3.0.9 - resolution: "@react-stately/disclosure@npm:3.0.9" +"@react-stately/disclosure@npm:^3.0.11": + version: 3.0.11 + resolution: "@react-stately/disclosure@npm:3.0.11" dependencies: "@react-stately/utils": "npm:^3.11.0" - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.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/b0458bc5255672af6b587a0b478b55077c481f223abb6094af04eeb8a922774a0da524dce6c540aec014f6efab4eecfa4d43a72eadd94e232dbdd959d49afd36 + checksum: 10/0aa11e08c732f6ff1a4aa8ffd72341ffceaab554c7be52cf78972e40e59ffadf5a124dc6a4355946b68fcfe1326b4c178a38ec33a4fe8539c3e0863b97b2de4a languageName: node linkType: hard -"@react-stately/dnd@npm:^3.7.2": - version: 3.7.2 - resolution: "@react-stately/dnd@npm:3.7.2" +"@react-stately/dnd@npm:^3.7.4": + version: 3.7.4 + resolution: "@react-stately/dnd@npm:3.7.4" dependencies: - "@react-stately/selection": "npm:^3.20.7" - "@react-types/shared": "npm:^3.32.1" + "@react-stately/selection": "npm:^3.20.9" + "@react-types/shared": "npm:^3.33.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/ee10f796d8bce81b20792506ab1ec0dc46ac7cc4c20e9d320db115dc4499a148b35d9fdb3e0d2c8991747b3bc7c70f4f7dca1f17a9b25c6bfce8bcb3f8617230 + checksum: 10/f3c2a59370f76f512f1707592a1f78c9a790b1bfdfb019a786008962c837a2b89f5ef9ef63949411876447f9bd4fe63e8be80b779050c7b3789a7031379c5a67 languageName: node linkType: hard @@ -16618,265 +16947,265 @@ __metadata: languageName: node linkType: hard -"@react-stately/form@npm:^3.2.2": - version: 3.2.2 - resolution: "@react-stately/form@npm:3.2.2" +"@react-stately/form@npm:^3.2.4": + version: 3.2.4 + resolution: "@react-stately/form@npm:3.2.4" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.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/e466309999cb2f28e892b11cdfc7cee2b7d08eefced41d9e346e2c4353dd5fc1864de145a6b332735cdc94b278ff8e6b599489d2aa6945ec5389171f89a633d1 + checksum: 10/c8367bd4f7fcee4c098ddd5dd2c2366a1b942c0a516cc037e0e09efbcb4556abbe112893b0178cc90505ce3dc704ed58c3c9d77fa819fcda8d6929acde61d5c4 languageName: node linkType: hard -"@react-stately/grid@npm:^3.11.7": - version: 3.11.7 - resolution: "@react-stately/grid@npm:3.11.7" +"@react-stately/grid@npm:^3.11.9": + version: 3.11.9 + resolution: "@react-stately/grid@npm:3.11.9" dependencies: - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/selection": "npm:^3.20.7" - "@react-types/grid": "npm:^3.3.6" - "@react-types/shared": "npm:^3.32.1" + "@react-stately/collections": "npm:^3.12.10" + "@react-stately/selection": "npm:^3.20.9" + "@react-types/grid": "npm:^3.3.8" + "@react-types/shared": "npm:^3.33.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/04cb52877bbc6464ddbb3a58d2ce817f7e6f4891a381c2b8117fffa599e6f39d0621e1a7caa423a801237276512dfc2fd4fca4b936991822275163cc10d94269 + checksum: 10/d908503207bbfa23a77bc0dcc30283d652048aee18410123a8478be960eedcfb4aea0b92390a919710b5b29487ba80e22caab2d4cd8c3d108eb2cd731d7361f1 languageName: node linkType: hard -"@react-stately/layout@npm:^4.5.2": - version: 4.5.2 - resolution: "@react-stately/layout@npm:4.5.2" +"@react-stately/layout@npm:^4.6.0": + version: 4.6.0 + resolution: "@react-stately/layout@npm:4.6.0" dependencies: - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/table": "npm:^3.15.2" - "@react-stately/virtualizer": "npm:^4.4.4" - "@react-types/grid": "npm:^3.3.6" - "@react-types/shared": "npm:^3.32.1" - "@react-types/table": "npm:^3.13.4" + "@react-stately/collections": "npm:^3.12.10" + "@react-stately/table": "npm:^3.15.4" + "@react-stately/virtualizer": "npm:^4.4.6" + "@react-types/grid": "npm:^3.3.8" + "@react-types/shared": "npm:^3.33.1" + "@react-types/table": "npm:^3.13.6" "@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/03bd63f2e2739420790c47340cc8db07e8a02d11364903d49c99a0f1a0b09fe047a667d9e656ecb2eb0b2c24aa702f05e4be7eeb7758c3fb6a525cc8139ddb9a + checksum: 10/fd72142017bb61bd5a9008a8978b02fe54886f28fe216149c502370f349ac1ad86509d0e473077566fed2e84fb4d707be8f2ac0d087b7b7491cc7e61183aaedd languageName: node linkType: hard -"@react-stately/list@npm:^3.13.2": - version: 3.13.2 - resolution: "@react-stately/list@npm:3.13.2" +"@react-stately/list@npm:^3.13.4": + version: 3.13.4 + resolution: "@react-stately/list@npm:3.13.4" dependencies: - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/selection": "npm:^3.20.7" + "@react-stately/collections": "npm:^3.12.10" + "@react-stately/selection": "npm:^3.20.9" "@react-stately/utils": "npm:^3.11.0" - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.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/ae28932316ef07bd5939cb1c1cf930fb412dbb6d63ce0999d28076599c1ba29923dd5020f46c878f5d64b1a1cdd0ddc9210da0de1a36ade5f53c8624db5814df + checksum: 10/eca09aae2123049ebc1094a94793bd74090fc78cd908381056199ae95fa5e9bef4d0f799b63efe04e7a39e995412e208e7da3c93f748e5e556bccdbfdaf98ad4 languageName: node linkType: hard -"@react-stately/menu@npm:^3.9.9": - version: 3.9.9 - resolution: "@react-stately/menu@npm:3.9.9" +"@react-stately/menu@npm:^3.9.11": + version: 3.9.11 + resolution: "@react-stately/menu@npm:3.9.11" dependencies: - "@react-stately/overlays": "npm:^3.6.21" - "@react-types/menu": "npm:^3.10.5" - "@react-types/shared": "npm:^3.32.1" + "@react-stately/overlays": "npm:^3.6.23" + "@react-types/menu": "npm:^3.10.7" + "@react-types/shared": "npm:^3.33.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/e71ac18a795fd125813d856c5889aaaa0dfd70ce01960e24b1926d2b98e7129610e04e586033c7b063a43594abdc4e88a8cc08f314630076091dad21f670e933 + checksum: 10/863a11dab298ce79c68ed8e016a989ef28a77f2647f6f394649c75b5178acd8b6f3c680d4801973ef9d6a9ae0b28434c55e592ff666243d536241fc5d435e761 languageName: node linkType: hard -"@react-stately/numberfield@npm:^3.10.3": - version: 3.10.3 - resolution: "@react-stately/numberfield@npm:3.10.3" +"@react-stately/numberfield@npm:^3.11.0": + version: 3.11.0 + resolution: "@react-stately/numberfield@npm:3.11.0" dependencies: "@internationalized/number": "npm:^3.6.5" - "@react-stately/form": "npm:^3.2.2" + "@react-stately/form": "npm:^3.2.4" "@react-stately/utils": "npm:^3.11.0" - "@react-types/numberfield": "npm:^3.8.16" + "@react-types/numberfield": "npm:^3.8.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/8e008188c0328b49c725e5ab961854ec94cb345c611dc678782d844e8a637af3419556e34fa364d5cf08895b6c580d5e1a438c17e083f91e3c108ec1eb9f9125 + checksum: 10/e932b0f2e78409e89f892b0b4b807153fa82c9ebef9957e927a45249688cbd7af0d71a939f3a635e4947d45414c8c652244c3f9453286948e36953fbbdc3f31e languageName: node linkType: hard -"@react-stately/overlays@npm:^3.6.21": - version: 3.6.21 - resolution: "@react-stately/overlays@npm:3.6.21" +"@react-stately/overlays@npm:^3.6.23": + version: 3.6.23 + resolution: "@react-stately/overlays@npm:3.6.23" dependencies: "@react-stately/utils": "npm:^3.11.0" - "@react-types/overlays": "npm:^3.9.2" + "@react-types/overlays": "npm:^3.9.4" "@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/18a4b2edb043bdf8c26505fb1eb4ac11cd025c73fb8662944d6339dc7ce9b45cbbd04ed173ec0a5807365f1f95a3a517e6fcd142e49cfa1aa544fa4b412f3439 + checksum: 10/bea8636bc0fe4bfc033a5ddc6d8f8d0f2dd2c8687b6d3fead535b26f908fe27c69206f95b2e0a5751a2bb9ca9a587a75ce45eb5e68bd85a3e9ce561469997d40 languageName: node linkType: hard -"@react-stately/radio@npm:^3.11.3": - version: 3.11.3 - resolution: "@react-stately/radio@npm:3.11.3" +"@react-stately/radio@npm:^3.11.5": + version: 3.11.5 + resolution: "@react-stately/radio@npm:3.11.5" dependencies: - "@react-stately/form": "npm:^3.2.2" + "@react-stately/form": "npm:^3.2.4" "@react-stately/utils": "npm:^3.11.0" - "@react-types/radio": "npm:^3.9.2" - "@react-types/shared": "npm:^3.32.1" + "@react-types/radio": "npm:^3.9.4" + "@react-types/shared": "npm:^3.33.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/87791f9cfb8973c74b0a1e2f517c952e7d74e6f56f46f13e4d618dfb8e796138a265f5bbd08161e3fa228fb571c1db1abfe7c3d177b50d5de67219d299a35e23 + checksum: 10/6bc66989a1a3193b24f770e382e29af379b59aa1423d8ff6c8d8c3d99abefa90ea84421fecc1b52652e010cd4fc5e71494eb47ea4b6bcee22f1e67ea44ad4762 languageName: node linkType: hard -"@react-stately/searchfield@npm:^3.5.17": - version: 3.5.17 - resolution: "@react-stately/searchfield@npm:3.5.17" +"@react-stately/searchfield@npm:^3.5.19": + version: 3.5.19 + resolution: "@react-stately/searchfield@npm:3.5.19" dependencies: "@react-stately/utils": "npm:^3.11.0" - "@react-types/searchfield": "npm:^3.6.6" + "@react-types/searchfield": "npm:^3.6.8" "@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/ae639aeb1b03721e34045997d8aaa46eb7d3e03c162230be5fddcf6d775d0c0d42bb61c204bc2667fb75918b350d147fa0cf7b0e941053248b6a59617270273a + checksum: 10/830cd1a684d02ec67584718dd2dfdbd00bfdc94347f7e9df1b4734b47a723b5c770b7023db1c5ee863e39b5c108766b977e2fb7d0d40b6bc6c5b23a6fc09c530 languageName: node linkType: hard -"@react-stately/select@npm:^3.9.0": - version: 3.9.0 - resolution: "@react-stately/select@npm:3.9.0" +"@react-stately/select@npm:^3.9.2": + version: 3.9.2 + resolution: "@react-stately/select@npm:3.9.2" dependencies: - "@react-stately/form": "npm:^3.2.2" - "@react-stately/list": "npm:^3.13.2" - "@react-stately/overlays": "npm:^3.6.21" + "@react-stately/form": "npm:^3.2.4" + "@react-stately/list": "npm:^3.13.4" + "@react-stately/overlays": "npm:^3.6.23" "@react-stately/utils": "npm:^3.11.0" - "@react-types/select": "npm:^3.12.0" - "@react-types/shared": "npm:^3.32.1" + "@react-types/select": "npm:^3.12.2" + "@react-types/shared": "npm:^3.33.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/32f51d5c7a122eef72f68a226dcbd6c30c79b9a9457b8a0b537fa95bb17c063f5f8421108e345ee6c78e8b31d940f5eb0a51c446924374db5a5e98773b4834f2 + checksum: 10/02e62eac27de8753e5f653e075c3aef336cf52cb28dbb68187d15a75046ed47973661dcb2e46da3044420e11eed35c54698617e39e0f18bece9f4ca97edb46f9 languageName: node linkType: hard -"@react-stately/selection@npm:^3.20.7": - version: 3.20.7 - resolution: "@react-stately/selection@npm:3.20.7" +"@react-stately/selection@npm:^3.20.9": + version: 3.20.9 + resolution: "@react-stately/selection@npm:3.20.9" dependencies: - "@react-stately/collections": "npm:^3.12.8" + "@react-stately/collections": "npm:^3.12.10" "@react-stately/utils": "npm:^3.11.0" - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.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/98619e3283fb73c097577f6857d9b566c85bdeb9ddbcb65ea5c6845cfb9b6327378dc68c71588f5fc24b7ec85aa2c8cbd6accfb8f5ba0a2679d1be28c9f30072 + checksum: 10/9693f3147c81ad2eecf3b29df3f9bdbde4bce89698f6e954f8d7fe49c4ea4288c11258d892e1576dfd790d03b12d3a2b8333351ffb9579e04434b8d9cc796482 languageName: node linkType: hard -"@react-stately/slider@npm:^3.7.3": - version: 3.7.3 - resolution: "@react-stately/slider@npm:3.7.3" +"@react-stately/slider@npm:^3.7.5": + version: 3.7.5 + resolution: "@react-stately/slider@npm:3.7.5" dependencies: "@react-stately/utils": "npm:^3.11.0" - "@react-types/shared": "npm:^3.32.1" - "@react-types/slider": "npm:^3.8.2" + "@react-types/shared": "npm:^3.33.1" + "@react-types/slider": "npm:^3.8.4" "@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/5a7f870da1a4f104c61295398fa3854c5c69834876e6a597aa1bb369e6ad219d24b7109caf8c56677a2744935f648d1fec22fd6f40e927f589b85158f4c62abd + checksum: 10/a54e9aca9a1f4b5a84fe7ed49fbc07ecc2f9c4611a05e2a15751c38787901862cd3dda43009c3413c1200b9a979f2060c76f7623819fdfd94807ee3d2607142d languageName: node linkType: hard -"@react-stately/table@npm:^3.15.2": - version: 3.15.2 - resolution: "@react-stately/table@npm:3.15.2" +"@react-stately/table@npm:^3.15.4": + version: 3.15.4 + resolution: "@react-stately/table@npm:3.15.4" dependencies: - "@react-stately/collections": "npm:^3.12.8" + "@react-stately/collections": "npm:^3.12.10" "@react-stately/flags": "npm:^3.1.2" - "@react-stately/grid": "npm:^3.11.7" - "@react-stately/selection": "npm:^3.20.7" + "@react-stately/grid": "npm:^3.11.9" + "@react-stately/selection": "npm:^3.20.9" "@react-stately/utils": "npm:^3.11.0" - "@react-types/grid": "npm:^3.3.6" - "@react-types/shared": "npm:^3.32.1" - "@react-types/table": "npm:^3.13.4" + "@react-types/grid": "npm:^3.3.8" + "@react-types/shared": "npm:^3.33.1" + "@react-types/table": "npm:^3.13.6" "@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/3b6cf1e561525dcde9c01b500e1d01e87efac2b9bcf75fac8870b1f0618b86e0fab15e1b9bbd83cd345f654f39c52253c5b26c1b22b903b0c3212ff33abbf3c9 + checksum: 10/27b85d7d4541b1d9337c999362eeee21f6656bd7b278ba5419bf1a1c7441f9ff7a682ce87c438c5c8329351af261352867f0bbc057838971913e098ce9653e38 languageName: node linkType: hard -"@react-stately/tabs@npm:^3.8.7": - version: 3.8.7 - resolution: "@react-stately/tabs@npm:3.8.7" +"@react-stately/tabs@npm:^3.8.9": + version: 3.8.9 + resolution: "@react-stately/tabs@npm:3.8.9" dependencies: - "@react-stately/list": "npm:^3.13.2" - "@react-types/shared": "npm:^3.32.1" - "@react-types/tabs": "npm:^3.3.20" + "@react-stately/list": "npm:^3.13.4" + "@react-types/shared": "npm:^3.33.1" + "@react-types/tabs": "npm:^3.3.22" "@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/0a72d94ac45432aa4baae88d691824edac12efd8e2d1579ec0778310a8b136ae83ef95d7066446129a3f0a35e647954a03cb360000a0496c74cbe4512960a33b + checksum: 10/bfb7386c7d648d2152c46a1ef607f2c8caac3a940b7ee94033d4ec655be7ebee8f59d46cc253e012681d04476f97aac794f2269c08e927d2ecf87c1b220539d4 languageName: node linkType: hard -"@react-stately/toast@npm:^3.1.2": - version: 3.1.2 - resolution: "@react-stately/toast@npm:3.1.2" +"@react-stately/toast@npm:^3.1.3": + version: 3.1.3 + resolution: "@react-stately/toast@npm:3.1.3" dependencies: "@swc/helpers": "npm:^0.5.0" - use-sync-external-store: "npm:^1.4.0" + use-sync-external-store: "npm:^1.6.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/771eade3a6f84d7aac3f5766e9cc47826cdb179d58165d12650a843548c13cbf4b4bd3b804a1f367e884e0e28d1d51693cb6f5e7c8391ebdb233cb1dddc015c5 + checksum: 10/7a877ee92bedb11dd5bef2e7506fb99cfa590c9ae556efcc5a814014d6e5f9d81f2c77b0dbc7e844785b3f414e1e6be5eb75436b469a3e357d9c1bed41a0730c languageName: node linkType: hard -"@react-stately/toggle@npm:^3.9.3": - version: 3.9.3 - resolution: "@react-stately/toggle@npm:3.9.3" +"@react-stately/toggle@npm:^3.9.5": + version: 3.9.5 + resolution: "@react-stately/toggle@npm:3.9.5" dependencies: "@react-stately/utils": "npm:^3.11.0" - "@react-types/checkbox": "npm:^3.10.2" - "@react-types/shared": "npm:^3.32.1" + "@react-types/checkbox": "npm:^3.10.4" + "@react-types/shared": "npm:^3.33.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/68aaeb48a54fef76a4da19b186fc42ba65f400605b25ab2ac95b58a35b565f2704397ac4cfaf6d1a09f64cb5ac4946ea078433032c2a0ca79854082681888a47 + checksum: 10/a034d2754e271eebdd6e80c530d1ba788f2c52eda2997970f646276d6a9328b7e86e5b6876473615d416e3ed774dd1adb7356b1460daa6100359473219f04236 languageName: node linkType: hard -"@react-stately/tooltip@npm:^3.5.9": - version: 3.5.9 - resolution: "@react-stately/tooltip@npm:3.5.9" +"@react-stately/tooltip@npm:^3.5.11": + version: 3.5.11 + resolution: "@react-stately/tooltip@npm:3.5.11" dependencies: - "@react-stately/overlays": "npm:^3.6.21" - "@react-types/tooltip": "npm:^3.5.0" + "@react-stately/overlays": "npm:^3.6.23" + "@react-types/tooltip": "npm:^3.5.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 - checksum: 10/67c26c7e1777b61ee74411f12a0c971ad8550b5675eddc746761890b1854488d8774dcc0e91b30e46e5935c9c6751e7c2a38e189eba9a1efb4c97385a4b7a5e9 + checksum: 10/5e006f8ff866af75666b098806579e2469fe02207bf47c9c847832cc1c4417777e08200589c3a81d2f8c41a1a47db76e183750e790f4e4fda9e2df6bd462e98f languageName: node linkType: hard -"@react-stately/tree@npm:^3.9.4": - version: 3.9.4 - resolution: "@react-stately/tree@npm:3.9.4" +"@react-stately/tree@npm:^3.9.6": + version: 3.9.6 + resolution: "@react-stately/tree@npm:3.9.6" dependencies: - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/selection": "npm:^3.20.7" + "@react-stately/collections": "npm:^3.12.10" + "@react-stately/selection": "npm:^3.20.9" "@react-stately/utils": "npm:^3.11.0" - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.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/7a3c785ca54f3139f442731e1a106302f4c0bc5731a95d5fdcb96980e7fce3e430062bfef6bf692fc5a7aa6b8ffe08e3e621c7a671249fba81768554ea21a397 + checksum: 10/a073aeea043302dbec4d4581f5efa96f5fc6627695d553af7f2f307a16a181abe0ba530111b7dde7c592794ce733c0b59af04ed7c3dc1255bf0f67e5572004df languageName: node linkType: hard @@ -16891,335 +17220,335 @@ __metadata: languageName: node linkType: hard -"@react-stately/virtualizer@npm:^4.4.4": - version: 4.4.4 - resolution: "@react-stately/virtualizer@npm:4.4.4" +"@react-stately/virtualizer@npm:^4.4.6": + version: 4.4.6 + resolution: "@react-stately/virtualizer@npm:4.4.6" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.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/c9d8d4b34250b8c0e91811bc618c654bac3e8eabe7a8ec119abc8f5dbbfa19faa4b31575eb0775773fda0f533fc2835e9a233b4ed0e4ae1835ddb5f0521a18aa + checksum: 10/47d8e549b638a6b37badac1bf4a1b2027be38c7121404cbc21c8afbca28131bb69076915944eafdf09246999dac169a5fb966f783c36d87bc337f2e29e2957c9 languageName: node linkType: hard -"@react-types/autocomplete@npm:3.0.0-alpha.36": - version: 3.0.0-alpha.36 - resolution: "@react-types/autocomplete@npm:3.0.0-alpha.36" +"@react-types/autocomplete@npm:3.0.0-alpha.38": + version: 3.0.0-alpha.38 + resolution: "@react-types/autocomplete@npm:3.0.0-alpha.38" dependencies: - "@react-types/combobox": "npm:^3.13.10" - "@react-types/searchfield": "npm:^3.6.6" - "@react-types/shared": "npm:^3.32.1" + "@react-types/combobox": "npm:^3.14.0" + "@react-types/searchfield": "npm:^3.6.8" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/054b3d186d50f02fbd48e5370a447a3cfaafecf3aaddc04cdb0411cf9e170d11ceec5ca6f8fb654fbd32a65a74ae52c7a900af6ebe3683ad94f36860d4e8ee8f + checksum: 10/4134c885eb90bf73b268fa5d8ed67f076ab0eccd76843c530665e1dcc1063bbb5d7ac1a88a57e2cd4866f90c908e90f5d1d9b51753873b5e3d432ab2a3403d51 languageName: node linkType: hard -"@react-types/breadcrumbs@npm:^3.7.17": - version: 3.7.17 - resolution: "@react-types/breadcrumbs@npm:3.7.17" +"@react-types/breadcrumbs@npm:^3.7.19": + version: 3.7.19 + resolution: "@react-types/breadcrumbs@npm:3.7.19" dependencies: - "@react-types/link": "npm:^3.6.5" - "@react-types/shared": "npm:^3.32.1" + "@react-types/link": "npm:^3.6.7" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/e9754d1b8010f3cdfdfcd8abf03cdb5b37dc1038496fcc103569b9f349269b39680979c2293cdc04c376cd1ea7b97b00571d46677b9d4939a60dd01b55f765a6 + checksum: 10/872c3af2ce541dd17180f94e1ceadcd99018463bf63d6a418d80c6e8a36d271633cf8f55c9468d102bde182b3e58450fcd260de53d44fff8dd9f493330270bb6 languageName: node linkType: hard -"@react-types/button@npm:^3.14.1": - version: 3.14.1 - resolution: "@react-types/button@npm:3.14.1" +"@react-types/button@npm:^3.15.1": + version: 3.15.1 + resolution: "@react-types/button@npm:3.15.1" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/bbbf2e5db83f6b925af154199aaad438d950187862c4ca6f82122c0209632c0c358201ec9b0b503f9b96c8b5b765066af41c6472440ca7ea75b75031d7d9eacf + checksum: 10/aa58f33a7f66efc9e2fbfc329853dfa1a2cb840c1de6d0dc54f1e9afd04465fabf24aa513b83871503a089dc7a92a720e16a707300a0a53995e32d509e3b36e7 languageName: node linkType: hard -"@react-types/calendar@npm:^3.8.1": - version: 3.8.1 - resolution: "@react-types/calendar@npm:3.8.1" +"@react-types/calendar@npm:^3.8.3": + version: 3.8.3 + resolution: "@react-types/calendar@npm:3.8.3" dependencies: - "@internationalized/date": "npm:^3.10.1" - "@react-types/shared": "npm:^3.32.1" + "@internationalized/date": "npm:^3.12.0" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/a43039061afd7eac4197dc85f9a4112d41ae8869da79efddd0b206a38cef0154947e29e39c92571d22c29b17bfba3ef71fc684b519904dc701580a5aabae7bd1 + checksum: 10/92c40145eab9c43cc490dfd8dadcc5f39e167914c4822db5c00c5267603fdb310e50781998e685fdf52a650b97bcb02dabdd1ae50b2a8faa95d7e37f6c2d3e41 languageName: node linkType: hard -"@react-types/checkbox@npm:^3.10.2": - version: 3.10.2 - resolution: "@react-types/checkbox@npm:3.10.2" +"@react-types/checkbox@npm:^3.10.4": + version: 3.10.4 + resolution: "@react-types/checkbox@npm:3.10.4" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/3d2f8468ce3326825bc820c958dc52b68f11a18ff15a68de2663115b168fecb165a2381cfecf2d7a254bfba66760590d8d76dabae5f48626a15eddf309206516 + checksum: 10/c87898a75cc751c7adeba2c684a1b346808e76c5f6da3e84980d3540c1dd5a5520ee24caa4e06f855bb9bd460fd64c3db79fdace6ca1b461ff1d04b3b5532e9d languageName: node linkType: hard -"@react-types/color@npm:^3.1.2": - version: 3.1.2 - resolution: "@react-types/color@npm:3.1.2" +"@react-types/color@npm:^3.1.4": + version: 3.1.4 + resolution: "@react-types/color@npm:3.1.4" dependencies: - "@react-types/shared": "npm:^3.32.1" - "@react-types/slider": "npm:^3.8.2" + "@react-types/shared": "npm:^3.33.1" + "@react-types/slider": "npm:^3.8.4" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/a41e786bd17b1815d5b7414f2dcbc79e655c3a4b8fe2dfa68c200c553f922bab5468a2ea272c76cdf6611d7d3f20518ca31b442729ef7b68141d91839e580410 + checksum: 10/999466eda395092c5ed7f65911c6df764088bb102b7f40e380691baf19d8d272bfb6438ba222bfab22d49ee6e8d26840c18daca5112b9b45a3d95555f0a26a28 languageName: node linkType: hard -"@react-types/combobox@npm:^3.13.10": - version: 3.13.10 - resolution: "@react-types/combobox@npm:3.13.10" +"@react-types/combobox@npm:^3.14.0": + version: 3.14.0 + resolution: "@react-types/combobox@npm:3.14.0" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/d640e2dc622a67242987a1a304f86e30d0ba13e853c48dabf35ad278e4af250abc2bbb959ce8ac91797f725db297c5cb20b023219dcbbab48f675cf11c3e0147 + checksum: 10/950b8c945ea15af1420196e3905fd1982685ff6afbd6aa998a4e6792c9e07d314d988778ed5e480024525d0b5ddef3b741631066e6bda0978faf199984875074 languageName: node linkType: hard -"@react-types/datepicker@npm:^3.13.3": - version: 3.13.3 - resolution: "@react-types/datepicker@npm:3.13.3" +"@react-types/datepicker@npm:^3.13.5": + version: 3.13.5 + resolution: "@react-types/datepicker@npm:3.13.5" dependencies: - "@internationalized/date": "npm:^3.10.1" - "@react-types/calendar": "npm:^3.8.1" - "@react-types/overlays": "npm:^3.9.2" - "@react-types/shared": "npm:^3.32.1" + "@internationalized/date": "npm:^3.12.0" + "@react-types/calendar": "npm:^3.8.3" + "@react-types/overlays": "npm:^3.9.4" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/f8e72e8ba6737861ac9eb0553a0ad65708d34c3d989aad394918436fa150d0533be8efcd48cb892739b0b33e31af5eca4ddfde9bc2ac7ff7c3f863cdf18f991d + checksum: 10/13ecd4931572e58e22cb79fca91690b97ef84525754f9367b78e60b1692ae8f3f21c52bd9a0de648c6acfac89ab0c1ed706d4e20e9246c4ab2b695af1567606d languageName: node linkType: hard -"@react-types/dialog@npm:^3.5.22": - version: 3.5.22 - resolution: "@react-types/dialog@npm:3.5.22" +"@react-types/dialog@npm:^3.5.24": + version: 3.5.24 + resolution: "@react-types/dialog@npm:3.5.24" dependencies: - "@react-types/overlays": "npm:^3.9.2" - "@react-types/shared": "npm:^3.32.1" + "@react-types/overlays": "npm:^3.9.4" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/dedac6cf8a85568344242eb6e7f9c6f9dd9fff31711d23586374a1cfc4f3b7b7bed43fdff633d54135792dcdd9941a8a15a34bbbec1a9d4caff7b869b35dd71e + checksum: 10/fbfc265b1ea0105b3815eba7fc05898f0b0fd0d6b9d21d3a38abfc579ce01031bfcfc1cab9a3ffaf781a0842498a2faf33a0b04ca80983eab21bf0f08d76680f languageName: node linkType: hard -"@react-types/form@npm:^3.7.16": - version: 3.7.16 - resolution: "@react-types/form@npm:3.7.16" +"@react-types/form@npm:^3.7.18": + version: 3.7.18 + resolution: "@react-types/form@npm:3.7.18" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/3e8f156318746d31ef7470d366dcc1004f3a59b1a3a3f9e54331921a4b251ff51c6bf76a32be229f6d8c524b5b1292f3c1135597de6b0f52bae2d8c32a542a74 + checksum: 10/af39ed916115da5f11c895a92ed77922bcb282dc1cb0c47d3fc39caddaa900d5fdbdc753065ab52d2475f26ecf05dfa26d5e3da3459c57fa86ac6d4311092136 languageName: node linkType: hard -"@react-types/grid@npm:^3.3.6": - version: 3.3.6 - resolution: "@react-types/grid@npm:3.3.6" +"@react-types/grid@npm:^3.3.8": + version: 3.3.8 + resolution: "@react-types/grid@npm:3.3.8" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/2b4be0fd8f6d07d3feca68039b89e0c13e1e566d27334a6ca7b6368f073ee55cf026833c5af664c78ccd137716aefd4f46b88f1b97709cd8080d7380c79454df + checksum: 10/7330c7f502c5a45c29984dc21ed05fca03bcaccd13ba6f58f1185731e671c5d5860523d2da9b17065a95b24aa42cbfa17df3ade8b848efc536d73c1cd00a5a1b languageName: node linkType: hard -"@react-types/link@npm:^3.6.5": - version: 3.6.5 - resolution: "@react-types/link@npm:3.6.5" +"@react-types/link@npm:^3.6.7": + version: 3.6.7 + resolution: "@react-types/link@npm:3.6.7" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/59fb6011b146cffcdaae94e8fdd9241cb70697f7e518d39e3aacd14f244db6eea7ba3984d8b13d3ce601e6f160da36fd1bfa56827b2e1df728505e426a8db353 + checksum: 10/2fd73277820df22b0a4cdee253f1e01bc57d2c7778bc0a4ab8181076a22e6feb02b08b5b358578bb4608e0a8c46ff9a871e5a670262e887b5fe008a20608acc2 languageName: node linkType: hard -"@react-types/listbox@npm:^3.7.4": - version: 3.7.4 - resolution: "@react-types/listbox@npm:3.7.4" +"@react-types/listbox@npm:^3.7.6": + version: 3.7.6 + resolution: "@react-types/listbox@npm:3.7.6" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/79dd3f6a7284b74f5156cc2265b384d2074ddfb7b38e608be855ec4ad1234aa0e7cdbfc0d7ef899e34113781381a6ee18b820dbe3dbee67364c72ac240144522 + checksum: 10/0580910cca9939fe9eef63dd5088408273ff6a358e1c3b6f27d7ac72b08b1456315b4bc41ea0d2e5b22375441ed4d8b1786559b3fc87fe01fb6304a07ecd571c languageName: node linkType: hard -"@react-types/menu@npm:^3.10.5": - version: 3.10.5 - resolution: "@react-types/menu@npm:3.10.5" +"@react-types/menu@npm:^3.10.7": + version: 3.10.7 + resolution: "@react-types/menu@npm:3.10.7" dependencies: - "@react-types/overlays": "npm:^3.9.2" - "@react-types/shared": "npm:^3.32.1" + "@react-types/overlays": "npm:^3.9.4" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/0466f5b7602ccfc8cb8fec3cd2482d587acbfd1701d427c7d79a121eb3f2b137feca70e62fd401aea95022fd616460575c4d5dd541cf3d69d8644ca54a2446c8 + checksum: 10/bc1adcbbec0c719ccf2ed4e49106a644dae0b777d62edea58012a899207cbf3543fff2e5236826feedfdb5ea653244f61b44201714fa1e92eab1619fd3ff67d4 languageName: node linkType: hard -"@react-types/meter@npm:^3.4.13": - version: 3.4.13 - resolution: "@react-types/meter@npm:3.4.13" +"@react-types/meter@npm:^3.4.15": + version: 3.4.15 + resolution: "@react-types/meter@npm:3.4.15" dependencies: - "@react-types/progress": "npm:^3.5.16" + "@react-types/progress": "npm:^3.5.18" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/28b519a4640c72732719c056903fcf4037582984d235c388095319b5005eb9d5d2698306d9f79d1ad61d594b8ceda115acae27c22e95cb771d0e07da30305708 + checksum: 10/a31fc132bec20267ffbd7bea429bcdd37ea4548ccde7ffaa578613ff1298bd5413f46bf2c86c3737d5aef420175545267ffb628b2d5a45dc8563a62a5062226d languageName: node linkType: hard -"@react-types/numberfield@npm:^3.8.16": - version: 3.8.16 - resolution: "@react-types/numberfield@npm:3.8.16" +"@react-types/numberfield@npm:^3.8.18": + version: 3.8.18 + resolution: "@react-types/numberfield@npm:3.8.18" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/aac7b1fa219e9d971579c4632af31701c898d7a0a6bf168095d5b04822cd9c93059fbdb21f1d2fe92c62ed50922c6a8c9fc63d5be755ab94436a5da9c028fede + checksum: 10/a6bbf7c2a022423c1d99ee4a0262581eb36887962cb6f0d592a10c9854fcd74d75eee1a701eb185d6d86062b63c5682c5ec3f97907cbfeca80d2698554795dff languageName: node linkType: hard -"@react-types/overlays@npm:^3.9.2": - version: 3.9.2 - resolution: "@react-types/overlays@npm:3.9.2" +"@react-types/overlays@npm:^3.9.4": + version: 3.9.4 + resolution: "@react-types/overlays@npm:3.9.4" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/6cab7f2cbb813f710696095db1169f902cfe7e4a9aeef496848343ff5116be3782bfea68dffbeaf3f984a0475c2fb6c4a26ad9fb563172c4ec3e47110ca1e672 + checksum: 10/720ca12182243c8796681d3046bce5798f3edaa3dcafb8670a1a5b9254dffa035323dd50533d3b2e102b5418b4563aab91eaa34d728011fbbf4501d2ce79920c languageName: node linkType: hard -"@react-types/progress@npm:^3.5.16": - version: 3.5.16 - resolution: "@react-types/progress@npm:3.5.16" +"@react-types/progress@npm:^3.5.18": + version: 3.5.18 + resolution: "@react-types/progress@npm:3.5.18" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/315b34314dc135a6c9319f4ffd83570fac8c0da377b00d56f17c6eb3d416904682738b522b05d6d8cbb89d3ef92c5118bf8e58eca10fac121da6dcaa40563b8d + checksum: 10/8589f11ba9fa320da82a0b4751bc8576b9153a70facc6986fadf05156d393867f7086667c528e1880eddb51510ac7f026bffb0c667f4af2602c4fcedc264bc05 languageName: node linkType: hard -"@react-types/radio@npm:^3.9.2": - version: 3.9.2 - resolution: "@react-types/radio@npm:3.9.2" +"@react-types/radio@npm:^3.9.4": + version: 3.9.4 + resolution: "@react-types/radio@npm:3.9.4" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/3375d13bbeed28a1b959077e727b43324f4282cf43ec985edbaaa5b3e50b46199c083550882e2a9e4788c74555bfd25f5cc2b0351bf48a768f81a4ba6bf73222 + checksum: 10/3b36b410c1ad64ce3f3e7b524d0216faa317584ebdeeb18aced3f8960fa5efa522890bdaa170c302d8335b89d2a78a7cef8f417f676c52a21bac5d0644a9dbf6 languageName: node linkType: hard -"@react-types/searchfield@npm:^3.6.6": - version: 3.6.6 - resolution: "@react-types/searchfield@npm:3.6.6" +"@react-types/searchfield@npm:^3.6.8": + version: 3.6.8 + resolution: "@react-types/searchfield@npm:3.6.8" dependencies: - "@react-types/shared": "npm:^3.32.1" - "@react-types/textfield": "npm:^3.12.6" + "@react-types/shared": "npm:^3.33.1" + "@react-types/textfield": "npm:^3.12.8" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/942cb80234be5d61cae410c144873d75a243dec0bbf8bb0c696f13d66fd729dabb188bb6214d4bfadeb0d54e14eed3bb7e86c6721206bcb395658b613f22b53f + checksum: 10/db38ca5c20593bbe4f0e7813aa8d967b3f5ff4031a974066e7db63b9561a2fb930836c631f7e37bfd04cfceff26bd9cc6f63a0805152f289a0bf55d5b7f62f03 languageName: node linkType: hard -"@react-types/select@npm:^3.12.0": - version: 3.12.0 - resolution: "@react-types/select@npm:3.12.0" +"@react-types/select@npm:^3.12.2": + version: 3.12.2 + resolution: "@react-types/select@npm:3.12.2" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/66ac890f40b517bd98e974e3bc8083ab0579b3ad1daa5a5ae308256034c75ae95841f40c8a3b76de5704d57f2b07cdce432c17f522216f9b636ecd0b5abf6aa6 + checksum: 10/836bbb555ba491d9256743d8a3f8e9ff52f3ae7d93ad2fa37069d9de5b516ad8edffa4f7444a60cfeb5ed0c59d2ca5c6e6a8b0a14de0066c48c6164068de0d9e languageName: node linkType: hard -"@react-types/shared@npm:^3.32.1": - version: 3.32.1 - resolution: "@react-types/shared@npm:3.32.1" +"@react-types/shared@npm:^3.33.1": + version: 3.33.1 + resolution: "@react-types/shared@npm:3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/33c39d1e23fd73a18519679742ba548c128097831710af4803bec244ae96800271f88dcc4eab958734fc501bb65c17e590028596733761610fb0103c5dea6e36 + checksum: 10/18b9586e4d6f1fda82a471bde7e4dec5bcc95d1a7ece58864fe40bcd9dce175b5c76c4285d8aeed1beba2d397e3e89b521519a917aa97e16847edfa3c67b5df5 languageName: node linkType: hard -"@react-types/slider@npm:^3.8.2": - version: 3.8.2 - resolution: "@react-types/slider@npm:3.8.2" +"@react-types/slider@npm:^3.8.4": + version: 3.8.4 + resolution: "@react-types/slider@npm:3.8.4" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/cb9600a1842ace218be1a8bdb7b6785113c7165fde0cc76682b82cf7809927d2f45f95facb2570e7abb683f28f0a2ed0590c8ec8e948ed8dadffadb8905918d9 + checksum: 10/9c35a288e37845bd7448f0994dcbcf4cb8d13085ba0b5483aabb9a9df18119b03d47bb72ff855d69b71658ee1b8b1b09244a5f3932d6077f03e35c555129a5ae languageName: node linkType: hard -"@react-types/switch@npm:^3.5.15": - version: 3.5.15 - resolution: "@react-types/switch@npm:3.5.15" +"@react-types/switch@npm:^3.5.17": + version: 3.5.17 + resolution: "@react-types/switch@npm:3.5.17" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/f35c075188b93aa07b6ab367a6d5bcc4316bcd5481d47e1b0319944411e3a12b15455c443079159280b6d168da36271f3df8bc55808a7d8249b239f5886f0253 + checksum: 10/3b677ae7645a99a18ba1683dd7cac28d9a77073f715b54b1c549ae931138adda4d581578aec9173de90e8df65143e6067746d350bb1bd74423fe0f73caec5905 languageName: node linkType: hard -"@react-types/table@npm:^3.13.4": - version: 3.13.4 - resolution: "@react-types/table@npm:3.13.4" +"@react-types/table@npm:^3.13.6": + version: 3.13.6 + resolution: "@react-types/table@npm:3.13.6" dependencies: - "@react-types/grid": "npm:^3.3.6" - "@react-types/shared": "npm:^3.32.1" + "@react-types/grid": "npm:^3.3.8" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/2f8c1878c8b9a6515a62c7592a335e9688c95a07e0445a891dd6069cbe26921573db6f7d6d2e0c22a8818ec5ef3f48f242ae24d955a1b18e7868f306fadfe7f5 + checksum: 10/18d5acbf10d7eba5582b71848593f9198519b4b54014974ad66a94a166d4e959390358f778dd6d4e17981e87fa4822c144cc025fb13d5d2395a11f1862273f13 languageName: node linkType: hard -"@react-types/tabs@npm:^3.3.20": - version: 3.3.20 - resolution: "@react-types/tabs@npm:3.3.20" +"@react-types/tabs@npm:^3.3.22": + version: 3.3.22 + resolution: "@react-types/tabs@npm:3.3.22" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/1526cc79cf73692681ac1c2d8c87a2ffb9f120231da0351b09c82fd4c38bf7f140aa845ad3ef7e0763c9270dfb246498bc01cda476aeff8150421e61287b4f2a + checksum: 10/2699b0c631a77b10c84a088f0c5452735dc1b30185578f4cc46bfab0c5b2b11fc96902f8596029890280b6102397e45b04868cc74f518818cc21b0e4a2ca2ee4 languageName: node linkType: hard -"@react-types/textfield@npm:^3.12.6": - version: 3.12.6 - resolution: "@react-types/textfield@npm:3.12.6" +"@react-types/textfield@npm:^3.12.8": + version: 3.12.8 + resolution: "@react-types/textfield@npm:3.12.8" dependencies: - "@react-types/shared": "npm:^3.32.1" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/e223531d29bbaad566f142b44a0f2e42b4ce08eab5661962ad3451391311a604077c49181f89a407aecf8697d59d887f5524f6eff8e9fe7416afda6b1f3ac7b3 + checksum: 10/55c426373168e2d288bc5ae051f2e8016db67f19cdff54194ff5a477799fd74b560362b36badd0071a48a73d41f25ce68c21010bcfcb26bc9e88fc26d137b8ab languageName: node linkType: hard -"@react-types/tooltip@npm:^3.5.0": - version: 3.5.0 - resolution: "@react-types/tooltip@npm:3.5.0" +"@react-types/tooltip@npm:^3.5.2": + version: 3.5.2 + resolution: "@react-types/tooltip@npm:3.5.2" dependencies: - "@react-types/overlays": "npm:^3.9.2" - "@react-types/shared": "npm:^3.32.1" + "@react-types/overlays": "npm:^3.9.4" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/66c20eb0e0d66628e739510e5fba8eb56534beec1887f7efcf1867fb5e43b8da3502c3f865059d08ce672e3c6e02876470497ab77dd3378f9ba6fbb8f1ca1de2 + checksum: 10/d20b25a73efaff881b896e8edcb16faa3fc03437935da458a99bd0781fcb17644903c94949f8ee7067f51d1018e3134171886dffce08b79221b2903635cc590b languageName: node linkType: hard @@ -17249,18 +17578,18 @@ __metadata: linkType: hard "@remixicon/react@npm:^4.6.0": - version: 4.8.0 - resolution: "@remixicon/react@npm:4.8.0" + version: 4.9.0 + resolution: "@remixicon/react@npm:4.9.0" peerDependencies: react: ">=18.2.0" - checksum: 10/10241f2e07826ce7d595cf9687a35c96cc9cf9eb68a9431d7dfa1b9df479dbef302874d28c0502cee2a536cf34722de7c49ed12d10a2b071e4e42ba4a278c516 + checksum: 10/3d8f1d86b2bb20ab5e44d15f18811e928b0886f7710eb7a1516afb9913ba72e46facec5dfee382825139d800bcbb6704c15d0c760d0f977c12257d4af8db3295 languageName: node linkType: hard -"@repeaterjs/repeater@npm:^3.0.4": - version: 3.0.5 - resolution: "@repeaterjs/repeater@npm:3.0.5" - checksum: 10/7478df13bd4631729021b2f43524fe71a4ed04b3c1c2125315078e3954f059f2ff4da5776f9be8f76008df9849e866e5ec56120f41b8bf66d2ec1a7c7bc53229 +"@repeaterjs/repeater@npm:^3.0.4, @repeaterjs/repeater@npm:^3.0.6": + version: 3.0.6 + resolution: "@repeaterjs/repeater@npm:3.0.6" + checksum: 10/25698e822847b776006428f31e2d31fbcb4faccf30c1c8d68d6e1308e58b49afb08764d1dd15536ddd67775cd01fd6c2fb22f039c05a71865448fbcfb2246af2 languageName: node linkType: hard @@ -17558,92 +17887,92 @@ __metadata: languageName: node linkType: hard -"@rspack/binding-darwin-arm64@npm:1.7.6": - version: 1.7.6 - resolution: "@rspack/binding-darwin-arm64@npm:1.7.6" +"@rspack/binding-darwin-arm64@npm:1.7.7": + version: 1.7.7 + resolution: "@rspack/binding-darwin-arm64@npm:1.7.7" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rspack/binding-darwin-x64@npm:1.7.6": - version: 1.7.6 - resolution: "@rspack/binding-darwin-x64@npm:1.7.6" +"@rspack/binding-darwin-x64@npm:1.7.7": + version: 1.7.7 + resolution: "@rspack/binding-darwin-x64@npm:1.7.7" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rspack/binding-linux-arm64-gnu@npm:1.7.6": - version: 1.7.6 - resolution: "@rspack/binding-linux-arm64-gnu@npm:1.7.6" +"@rspack/binding-linux-arm64-gnu@npm:1.7.7": + version: 1.7.7 + resolution: "@rspack/binding-linux-arm64-gnu@npm:1.7.7" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rspack/binding-linux-arm64-musl@npm:1.7.6": - version: 1.7.6 - resolution: "@rspack/binding-linux-arm64-musl@npm:1.7.6" +"@rspack/binding-linux-arm64-musl@npm:1.7.7": + version: 1.7.7 + resolution: "@rspack/binding-linux-arm64-musl@npm:1.7.7" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rspack/binding-linux-x64-gnu@npm:1.7.6": - version: 1.7.6 - resolution: "@rspack/binding-linux-x64-gnu@npm:1.7.6" +"@rspack/binding-linux-x64-gnu@npm:1.7.7": + version: 1.7.7 + resolution: "@rspack/binding-linux-x64-gnu@npm:1.7.7" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rspack/binding-linux-x64-musl@npm:1.7.6": - version: 1.7.6 - resolution: "@rspack/binding-linux-x64-musl@npm:1.7.6" +"@rspack/binding-linux-x64-musl@npm:1.7.7": + version: 1.7.7 + resolution: "@rspack/binding-linux-x64-musl@npm:1.7.7" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rspack/binding-wasm32-wasi@npm:1.7.6": - version: 1.7.6 - resolution: "@rspack/binding-wasm32-wasi@npm:1.7.6" +"@rspack/binding-wasm32-wasi@npm:1.7.7": + version: 1.7.7 + resolution: "@rspack/binding-wasm32-wasi@npm:1.7.7" dependencies: "@napi-rs/wasm-runtime": "npm:1.0.7" conditions: cpu=wasm32 languageName: node linkType: hard -"@rspack/binding-win32-arm64-msvc@npm:1.7.6": - version: 1.7.6 - resolution: "@rspack/binding-win32-arm64-msvc@npm:1.7.6" +"@rspack/binding-win32-arm64-msvc@npm:1.7.7": + version: 1.7.7 + resolution: "@rspack/binding-win32-arm64-msvc@npm:1.7.7" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rspack/binding-win32-ia32-msvc@npm:1.7.6": - version: 1.7.6 - resolution: "@rspack/binding-win32-ia32-msvc@npm:1.7.6" +"@rspack/binding-win32-ia32-msvc@npm:1.7.7": + version: 1.7.7 + resolution: "@rspack/binding-win32-ia32-msvc@npm:1.7.7" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rspack/binding-win32-x64-msvc@npm:1.7.6": - version: 1.7.6 - resolution: "@rspack/binding-win32-x64-msvc@npm:1.7.6" +"@rspack/binding-win32-x64-msvc@npm:1.7.7": + version: 1.7.7 + resolution: "@rspack/binding-win32-x64-msvc@npm:1.7.7" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rspack/binding@npm:1.7.6": - version: 1.7.6 - resolution: "@rspack/binding@npm:1.7.6" +"@rspack/binding@npm:1.7.7": + version: 1.7.7 + resolution: "@rspack/binding@npm:1.7.7" dependencies: - "@rspack/binding-darwin-arm64": "npm:1.7.6" - "@rspack/binding-darwin-x64": "npm:1.7.6" - "@rspack/binding-linux-arm64-gnu": "npm:1.7.6" - "@rspack/binding-linux-arm64-musl": "npm:1.7.6" - "@rspack/binding-linux-x64-gnu": "npm:1.7.6" - "@rspack/binding-linux-x64-musl": "npm:1.7.6" - "@rspack/binding-wasm32-wasi": "npm:1.7.6" - "@rspack/binding-win32-arm64-msvc": "npm:1.7.6" - "@rspack/binding-win32-ia32-msvc": "npm:1.7.6" - "@rspack/binding-win32-x64-msvc": "npm:1.7.6" + "@rspack/binding-darwin-arm64": "npm:1.7.7" + "@rspack/binding-darwin-x64": "npm:1.7.7" + "@rspack/binding-linux-arm64-gnu": "npm:1.7.7" + "@rspack/binding-linux-arm64-musl": "npm:1.7.7" + "@rspack/binding-linux-x64-gnu": "npm:1.7.7" + "@rspack/binding-linux-x64-musl": "npm:1.7.7" + "@rspack/binding-wasm32-wasi": "npm:1.7.7" + "@rspack/binding-win32-arm64-msvc": "npm:1.7.7" + "@rspack/binding-win32-ia32-msvc": "npm:1.7.7" + "@rspack/binding-win32-x64-msvc": "npm:1.7.7" dependenciesMeta: "@rspack/binding-darwin-arm64": optional: true @@ -17665,23 +17994,23 @@ __metadata: optional: true "@rspack/binding-win32-x64-msvc": optional: true - checksum: 10/fec6c978e51f20471e278a07018b414125cf3bccf9c6bd7032ca65603cfe5bf0fdd7f58c156c0640b5dfab05e82a1e1170ac6d1aacaf4f46b61564be77dbe41b + checksum: 10/c860c3318f4f3d79b6a55717e8e01a515cb7bc67ee30ebdedcdcf81b69c6885667bd0b558da89079f6f7260cc355d10388bb8c7482471f5ab768fb64d0161673 languageName: node linkType: hard "@rspack/core@npm:^1.4.11": - version: 1.7.6 - resolution: "@rspack/core@npm:1.7.6" + version: 1.7.7 + resolution: "@rspack/core@npm:1.7.7" dependencies: "@module-federation/runtime-tools": "npm:0.22.0" - "@rspack/binding": "npm:1.7.6" + "@rspack/binding": "npm:1.7.7" "@rspack/lite-tapable": "npm:1.1.0" peerDependencies: "@swc/helpers": ">=0.5.1" peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/9f23c4849926d9ddff34f703ab2be41878bca9e877c130d16d20d911ba4b13f15dfe96d7e86225d7f5a1e48034ab92cccec89f3765f84ff518538f6bb07f1f06 + checksum: 10/f3f56aaeeda237d5862bbd76a0a2f012073f98d16bfebeeee0c961892f9f20434ebbe47e440cb482dc9971018b29b2d79fe2bec3ee99e1dadbcd46676b5155b7 languageName: node linkType: hard @@ -19798,79 +20127,83 @@ __metadata: linkType: hard "@storybook/addon-a11y@npm:^10.3.0-alpha.1": - version: 10.3.0-alpha.1 - resolution: "@storybook/addon-a11y@npm:10.3.0-alpha.1" + version: 10.3.0-alpha.14 + resolution: "@storybook/addon-a11y@npm:10.3.0-alpha.14" dependencies: "@storybook/global": "npm:^5.0.0" axe-core: "npm:^4.2.0" peerDependencies: - storybook: ^10.3.0-alpha.1 - checksum: 10/866033fb0caa993159f280e5bf5af29d642cab47e0c30db23f074a9ebaf937edf41a54da779e452399e7923d13e8a93b7f361b471f6c3e79d1e5ba07f4ee62f7 + storybook: ^10.3.0-alpha.14 + checksum: 10/4c5b25aad081653d1ce76486e6dc0c07d62e390856607c348428a9875b97f90e30d802d19763b6ac00c9fb529279176a2864b3fd855410da5ef5eaea613a1954 languageName: node linkType: hard "@storybook/addon-docs@npm:^10.3.0-alpha.1": - version: 10.3.0-alpha.1 - resolution: "@storybook/addon-docs@npm:10.3.0-alpha.1" + version: 10.3.0-alpha.14 + resolution: "@storybook/addon-docs@npm:10.3.0-alpha.14" dependencies: "@mdx-js/react": "npm:^3.0.0" - "@storybook/csf-plugin": "npm:10.3.0-alpha.1" + "@storybook/csf-plugin": "npm:10.3.0-alpha.14" "@storybook/icons": "npm:^2.0.1" - "@storybook/react-dom-shim": "npm:10.3.0-alpha.1" + "@storybook/react-dom-shim": "npm:10.3.0-alpha.14" react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^10.3.0-alpha.1 - checksum: 10/2eccaeea3c725bba93442e6bdebe6c432bbc6219f41013ee679d87f3814d51bf1f7829a34587563797952dd9d741db3e39e91e92c303955188c9c42302f7c006 + storybook: ^10.3.0-alpha.14 + checksum: 10/a99769d506c0436c44d6d6c156c79063a8d4d521c09a74836880a696a9d51ef646fa6789de3edab391cd9379c96b8df488d01c032f088cf2a6edaef8273a7886 languageName: node linkType: hard "@storybook/addon-links@npm:^10.3.0-alpha.1": - version: 10.3.0-alpha.1 - resolution: "@storybook/addon-links@npm:10.3.0-alpha.1" + version: 10.3.0-alpha.14 + resolution: "@storybook/addon-links@npm:10.3.0-alpha.14" dependencies: "@storybook/global": "npm:^5.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.3.0-alpha.1 + storybook: ^10.3.0-alpha.14 peerDependenciesMeta: react: optional: true - checksum: 10/d50c997bc5edf6289fb7967c0dc19c370422b3b02289642efda0f7fe2977aef16c4f1b11e7a0245ab8a315df4d98196c6d194f178266a0ada76d47604aba98bc + checksum: 10/a7d8933c43b44406b12d521e6054a35dd8fe4f40b872e8f3c9e619dba6e06243f8b51fc8efa9777c554ed1059f5d4cc9f4d186fe1b33398490cab1d39e2f4810 languageName: node linkType: hard -"@storybook/addon-mcp@npm:^0.2.2": - version: 0.2.3 - resolution: "@storybook/addon-mcp@npm:0.2.3" +"@storybook/addon-mcp@npm:^0.3.0": + version: 0.3.4 + resolution: "@storybook/addon-mcp@npm:0.3.4" dependencies: - "@storybook/mcp": "npm:0.2.2" + "@storybook/mcp": "npm:0.5.1" "@tmcp/adapter-valibot": "npm:^0.1.4" "@tmcp/transport-http": "npm:^0.8.0" picoquery: "npm:^2.5.0" tmcp: "npm:^1.16.0" valibot: "npm:1.2.0" peerDependencies: + "@storybook/addon-vitest": ^9.1.16 || ^10.0.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 storybook: ^9.1.16 || ^10.0.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 - checksum: 10/9b4fa821444f6d954a3f64a52fc835380d9196e0e857a058185da3c6dfe2bbaa5276a81bfbbf56294d3ea6fca1e8f85fac14c95e4414ba3ad1ecaceb1909c903 + peerDependenciesMeta: + "@storybook/addon-vitest": + optional: true + checksum: 10/0fb377d8ee96055703cfee48c4a47d25367a090b4e343a0d5db15341137463ae01f167725e7087bc20fe072b474a798a6488452a3afce1b27c47049e65e56177 languageName: node linkType: hard "@storybook/addon-themes@npm:^10.3.0-alpha.1": - version: 10.3.0-alpha.1 - resolution: "@storybook/addon-themes@npm:10.3.0-alpha.1" + version: 10.3.0-alpha.14 + resolution: "@storybook/addon-themes@npm:10.3.0-alpha.14" dependencies: ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^10.3.0-alpha.1 - checksum: 10/801550d5a64075142d278985be22922fa93beee8bcba8b6c7c8b6927b78fe64f8c99f03109868712bb53df5419b8aeb89ec25d3bf1820ea6ef6cc4b1de32fde2 + storybook: ^10.3.0-alpha.14 + checksum: 10/fad0807d2b7750216459a0f771af08c1a6a02252046e88a7ff4f307dc99d96930c63a9f6fe922f984261f9ac42c745fa1c5baf2f5bfa3b739ed1120af8ec950a languageName: node linkType: hard "@storybook/addon-vitest@npm:^10.3.0-alpha.1": - version: 10.3.0-alpha.1 - resolution: "@storybook/addon-vitest@npm:10.3.0-alpha.1" + version: 10.3.0-alpha.14 + resolution: "@storybook/addon-vitest@npm:10.3.0-alpha.14" dependencies: "@storybook/global": "npm:^5.0.0" "@storybook/icons": "npm:^2.0.1" @@ -19878,7 +20211,7 @@ __metadata: "@vitest/browser": ^3.0.0 || ^4.0.0 "@vitest/browser-playwright": ^4.0.0 "@vitest/runner": ^3.0.0 || ^4.0.0 - storybook: ^10.3.0-alpha.1 + storybook: ^10.3.0-alpha.14 vitest: ^3.0.0 || ^4.0.0 peerDependenciesMeta: "@vitest/browser": @@ -19889,32 +20222,32 @@ __metadata: optional: true vitest: optional: true - checksum: 10/2d8ed11531f7e3b520c56e61506a338291d2bace0f15233b151508569e7fa3922dd9ab1be05401a8866c8a309a8e0ba5df99b42876cf13137404d445a04cf88d + checksum: 10/ff0b388ee6fe2b1d1371b7eedfa5d9c0783e72525d405cc777df1493e191a59048327d830fa281a005f882463bba4aff905f8319485e1b1ca175af3030353f04 languageName: node linkType: hard -"@storybook/builder-vite@npm:10.3.0-alpha.1": - version: 10.3.0-alpha.1 - resolution: "@storybook/builder-vite@npm:10.3.0-alpha.1" +"@storybook/builder-vite@npm:10.3.0-alpha.14": + version: 10.3.0-alpha.14 + resolution: "@storybook/builder-vite@npm:10.3.0-alpha.14" dependencies: - "@storybook/csf-plugin": "npm:10.3.0-alpha.1" + "@storybook/csf-plugin": "npm:10.3.0-alpha.14" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^10.3.0-alpha.1 + storybook: ^10.3.0-alpha.14 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10/fc3697c068fdd4375589b4de98e8c3f09e5fe4bb9c1b2bff12b19abbf04f08d62c1b2794aaf9f599acd4feecfe02bc9b92a315c5e52dc6372491e40818f4449a + checksum: 10/2aad8a27c44770b975c8acfba0b254f17f624afa984a6c0ca35a351e94085fe5e91a0123d912e6275f8a796bc78e760a6f0c1457b3a140544c0409b353be823e languageName: node linkType: hard -"@storybook/csf-plugin@npm:10.3.0-alpha.1": - version: 10.3.0-alpha.1 - resolution: "@storybook/csf-plugin@npm:10.3.0-alpha.1" +"@storybook/csf-plugin@npm:10.3.0-alpha.14": + version: 10.3.0-alpha.14 + resolution: "@storybook/csf-plugin@npm:10.3.0-alpha.14" dependencies: unplugin: "npm:^2.3.5" peerDependencies: esbuild: "*" rollup: "*" - storybook: ^10.3.0-alpha.1 + storybook: ^10.3.0-alpha.14 vite: "*" webpack: "*" peerDependenciesMeta: @@ -19926,7 +20259,7 @@ __metadata: optional: true webpack: optional: true - checksum: 10/9ab9ddd3b8aeaf1301828b3e6e3c16a6c8b216e6e73ed0063520f59a2dd2f30cb1d492520d170aa3c064d5f8ad2a27557a858536c1039156799dcafd99123b0e + checksum: 10/e388b4b8781d0c8322206ee1b89b981bf6b5d354b5066ab2868ca9aba88921473194d2ad14834a398f96678b10b068199f22e73bc43eb49597b6d5bd58d32a84 languageName: node linkType: hard @@ -19947,37 +20280,37 @@ __metadata: languageName: node linkType: hard -"@storybook/mcp@npm:0.2.2": - version: 0.2.2 - resolution: "@storybook/mcp@npm:0.2.2" +"@storybook/mcp@npm:0.5.1": + version: 0.5.1 + resolution: "@storybook/mcp@npm:0.5.1" dependencies: "@tmcp/adapter-valibot": "npm:^0.1.4" "@tmcp/transport-http": "npm:^0.8.0" tmcp: "npm:^1.16.0" valibot: "npm:1.2.0" - checksum: 10/0825f2e248c98abe8062a52cc0843bee107f0e64ee885decf19c3c3c453ef3521cb2615bc1aede76a51799cec4ba8494269ba18553b09fbe27dd111027ec86db + checksum: 10/a5924be156db45ac86ee57c325878ee1a46cf3bf9523fc9f231d8f491e3a77f92d74801d138a4af6256cebcd74c8aa5985d0c9fded4fcbdbd4708ccdcc70ee31 languageName: node linkType: hard -"@storybook/react-dom-shim@npm:10.3.0-alpha.1": - version: 10.3.0-alpha.1 - resolution: "@storybook/react-dom-shim@npm:10.3.0-alpha.1" +"@storybook/react-dom-shim@npm:10.3.0-alpha.14": + version: 10.3.0-alpha.14 + resolution: "@storybook/react-dom-shim@npm:10.3.0-alpha.14" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.3.0-alpha.1 - checksum: 10/74d5d7d1b43b35a02bbbf0c75cdb3becb0f9f17d511be1c79fd6ab1c0c0dc3d0f83b04a43e53ecb02e6c34a7734050649241f1b18cd037b9fe59f9088b182b35 + storybook: ^10.3.0-alpha.14 + checksum: 10/3adcc9c8f6b3cb3514eeb2d75efb15de6878915d181004bbc81df14ccc6ab3361f8a025f3c780fa385c73b571bb94b592ad9d1811897013b78206fe623769f82 languageName: node linkType: hard "@storybook/react-vite@npm:^10.3.0-alpha.1": - version: 10.3.0-alpha.1 - resolution: "@storybook/react-vite@npm:10.3.0-alpha.1" + version: 10.3.0-alpha.14 + resolution: "@storybook/react-vite@npm:10.3.0-alpha.14" dependencies: - "@joshwooding/vite-plugin-react-docgen-typescript": "npm:^0.6.3" + "@joshwooding/vite-plugin-react-docgen-typescript": "npm:^0.6.4" "@rollup/pluginutils": "npm:^5.0.2" - "@storybook/builder-vite": "npm:10.3.0-alpha.1" - "@storybook/react": "npm:10.3.0-alpha.1" + "@storybook/builder-vite": "npm:10.3.0-alpha.14" + "@storybook/react": "npm:10.3.0-alpha.14" empathic: "npm:^2.0.0" magic-string: "npm:^0.30.0" react-docgen: "npm:^8.0.0" @@ -19986,401 +20319,421 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.3.0-alpha.1 + storybook: ^10.3.0-alpha.14 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10/dfab9b3259a1fc44e3ff62732fade697bf9dc17896f01baa8084a13492507b155d586606e7e213338cdc60107b669ff3542dfd6a44cb0574a8196cb892ece9e1 + checksum: 10/bce268423eaee1df408b7753e67d02220fd5cf130d3bf918db2f661fcd210a80822313eff8a6f565aaba91d05db9a6ed53d7235851a3ef97b7e07e214367a820 languageName: node linkType: hard -"@storybook/react@npm:10.3.0-alpha.1": - version: 10.3.0-alpha.1 - resolution: "@storybook/react@npm:10.3.0-alpha.1" +"@storybook/react@npm:10.3.0-alpha.14": + version: 10.3.0-alpha.14 + resolution: "@storybook/react@npm:10.3.0-alpha.14" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/react-dom-shim": "npm:10.3.0-alpha.1" + "@storybook/react-dom-shim": "npm:10.3.0-alpha.14" react-docgen: "npm:^8.0.2" + react-docgen-typescript: "npm:^2.2.2" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.3.0-alpha.1 + storybook: ^10.3.0-alpha.14 typescript: ">= 4.9.x" peerDependenciesMeta: typescript: optional: true - checksum: 10/be7682725f97b207cf43d06b5435de4501d9c7cd3174d3988775bdfa49a846f0eb73587780f84f8be9be6270798c423cc882c6d0e394513de777589f1bebd4c2 + checksum: 10/4278726e1c4612ea68d3a0a8d6ef2ff3b9b31c7e93dd7e01178f467c3e879e9b2ceb3462d3232b1bcf4b96968eb5e9f8abca6881959a5c7c1f9be8cc6d1cf4d9 languageName: node linkType: hard -"@swagger-api/apidom-ast@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-ast@npm:1.5.1" +"@swagger-api/apidom-ast@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-ast@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-error": "npm:^1.5.1" + "@swagger-api/apidom-error": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" unraw: "npm:^3.0.0" - checksum: 10/48011d96edbde25ee72c5f0fe2b53ba0a30f71b08280f0e8deee424860ce51de4dd3c6300fbb4bb7f7056697a56eeb2e99fc61cee41fd022f512419a0e79fbf2 + checksum: 10/72a778c5fe6b679561d336718ae2e2c4ed10d3dd4169d8ca804a46fa3d5165c9a6344c7716ec5444d3e51ce69709a98e1fcdd8fabb05836575eed6c5d953d007 languageName: node linkType: hard -"@swagger-api/apidom-core@npm:^1.3.0, @swagger-api/apidom-core@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-core@npm:1.5.1" +"@swagger-api/apidom-core@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-core@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-ast": "npm:^1.5.1" - "@swagger-api/apidom-error": "npm:^1.5.1" + "@swagger-api/apidom-ast": "npm:^1.6.0" + "@swagger-api/apidom-error": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" minim: "npm:~0.23.8" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" short-unique-id: "npm:^5.3.2" ts-mixer: "npm:^6.0.3" - checksum: 10/bb6438508846c3b598d5640685afd0daa3dd641facd2af691efbd4e8b59eaae0aa753ef3204b5f9f1f5041a488446c50eaeb5b9185f9fec557f47bfb4716882a + checksum: 10/f92fba789715ac128f4b9f6ebb99a2739a2610efc74e4e5ba018295974c7b94430930bfc949a776a9279c2c9154c51d738461d16a644dd0427e7344d36d3750b languageName: node linkType: hard -"@swagger-api/apidom-error@npm:^1.3.0, @swagger-api/apidom-error@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-error@npm:1.5.1" +"@swagger-api/apidom-error@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-error@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.20.7" - checksum: 10/229a6d985a572ed2f99b2db0a1a38fa0c50338fbd1b5be0510c93c5b978aa056b95e46ba891bfd5b49c101989bb7290ced6de8ac620823d6bdf18dc879647328 + checksum: 10/00b1c7509ce25f817440b69bd3b03caddd2861490f995dbf7c2d5fb03d503e58fbba65c7d3817c5c0b237f19ca7e3617497d6dea1211e2b220a34594a3c71ed0 languageName: node linkType: hard -"@swagger-api/apidom-json-pointer@npm:^1.3.0, @swagger-api/apidom-json-pointer@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-json-pointer@npm:1.5.1" +"@swagger-api/apidom-json-pointer@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-json-pointer@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-error": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-error": "npm:^1.6.0" "@swaggerexpert/json-pointer": "npm:^2.10.1" - checksum: 10/b41297abbf1591334e34e0eba0075989d79884e2b2dc4083e19d32f26995339601467216c3c6107fa14b0690bdf09eb6ebd4f43d882982441a009efdd0691304 + checksum: 10/28d5d650d21f2eb998a8f18612c9edd0cf71d690abc387a72efd5bd49690dc48c71dd390954fc8f9ba6fced8e357fd12f69e705b44c4091b9f924e740c02314a languageName: node linkType: hard -"@swagger-api/apidom-ns-api-design-systems@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-ns-api-design-systems@npm:1.5.1" +"@swagger-api/apidom-ns-api-design-systems@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-ns-api-design-systems@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-error": "npm:^1.5.1" - "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-error": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/cf93498f7ce2fde4a7ff591046656a1b430e6cb151e29e28d99cd5863fde95946f2aa885c05caaa15d2a1014d48084a91748ae855cf964a3412ef018e469bd71 + checksum: 10/0763ec9275e661549b845d149fbc9b8307374a343c376b0068688c6506231678461964477dc4b63de0b3b7da25a3160f57254db90d1ba75eb0354e74f2ca8e0b languageName: node linkType: hard -"@swagger-api/apidom-ns-arazzo-1@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-ns-arazzo-1@npm:1.5.1" +"@swagger-api/apidom-ns-arazzo-1@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-ns-arazzo-1@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-json-schema-2020-12": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-json-schema-2020-12": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/75670ad35f79c76b246043f2858810ecb8a1ab0230596400cea2544f133a056703b4456b58061a076fa2c458da9f3fccbc4befa2ba9989741f5f824a16500f15 + checksum: 10/1cb5d4bc96caf506402f043fc9b3e551a8c777bdc7e241c22112ad01d1f61f89e78bbaebbc6b2376d507031c4a8de00c92e9d1f019c677d157b66e31ed9ad501 languageName: node linkType: hard -"@swagger-api/apidom-ns-asyncapi-2@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-ns-asyncapi-2@npm:1.5.1" +"@swagger-api/apidom-ns-asyncapi-2@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-ns-asyncapi-2@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-json-schema-draft-7": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-json-schema-draft-7": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/fb85677e8ecc8084711cbc872433ef015a3922278b12c71a6973a7e6329f0598bae52b4d175f3105ee5269daf1eeb524f23906bd985d065dd5b9afed9bd6b34b + checksum: 10/4b701afd154f78ec00bac4a2be4f804eb56cb72a3cf5e33fb6bcfda495d0b2745676a63aea5d44b03d3c92398b259831cafbbb99465467472b5ad270e4431bc7 languageName: node linkType: hard -"@swagger-api/apidom-ns-asyncapi-3@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-ns-asyncapi-3@npm:1.5.1" +"@swagger-api/apidom-ns-asyncapi-3@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-ns-asyncapi-3@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/aa3f8d7f9bc14a1b8cd10d33b432e569473b369674cfb213792f91804772498db90ba1fd3261917d809de884baed475f1f5684fac3e1f4e5ce54bc024696ffa0 + checksum: 10/a63f58441e09a9d68017a5d927c486c3602fb66355bffb17a3179f396736af3acea43946dba3633ac03c325e74bfee00c6a57574661ad3c5c259a88c990cd902 languageName: node linkType: hard -"@swagger-api/apidom-ns-json-schema-2019-09@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-ns-json-schema-2019-09@npm:1.5.1" +"@swagger-api/apidom-ns-json-schema-2019-09@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-ns-json-schema-2019-09@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-error": "npm:^1.5.1" - "@swagger-api/apidom-ns-json-schema-draft-7": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-error": "npm:^1.6.0" + "@swagger-api/apidom-ns-json-schema-draft-7": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.4" - checksum: 10/2dec258b30c9e0e37b8bb41eeb6cdbfd4d0c2a936ac5fd2a101d9dc30668ead260480bbbda0749c57428c4e10372200d41d1af88ed9ba5620b539c8500a43535 + checksum: 10/09c810db219bb67798f6240fffa5ecc24f2a328f0b77cd8d1afbf4a76df19c7ef23b073b78939d747fb81e27c66251ba2479275111c1b6aa78051464a1b264c2 languageName: node linkType: hard -"@swagger-api/apidom-ns-json-schema-2020-12@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-ns-json-schema-2020-12@npm:1.5.1" +"@swagger-api/apidom-ns-json-schema-2020-12@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-ns-json-schema-2020-12@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-error": "npm:^1.5.1" - "@swagger-api/apidom-ns-json-schema-2019-09": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-error": "npm:^1.6.0" + "@swagger-api/apidom-ns-json-schema-2019-09": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.4" - checksum: 10/98a84ec793d6688219cc5e19a93b3f033a3c96a62a858f623cb31dea6d0efeb43dbd302c493ab329d3474756314d1c419f5fe6facd38c1235581940adceef735 + checksum: 10/2a7d9c24f95e0731a96d66d8fcf434538816d7f920b0b0cfda00c99d9ad1381be943675232c13a55aa3aeae0e22de3e10e8a367c2eeb3f0271e8362469618011 languageName: node linkType: hard -"@swagger-api/apidom-ns-json-schema-draft-4@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-ns-json-schema-draft-4@npm:1.5.1" +"@swagger-api/apidom-ns-json-schema-draft-4@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-ns-json-schema-draft-4@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-ast": "npm:^1.5.1" - "@swagger-api/apidom-core": "npm:^1.5.1" + "@swagger-api/apidom-ast": "npm:^1.6.0" + "@swagger-api/apidom-core": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.4" - checksum: 10/12601bbed4cc2c749feee79536f714158c37d2e2359101be398cd41b95cef2d73cd16dc9fc752ca5b6ab9e030160fc2fdb96540cf7961cbbce8ee7cf168458a4 + checksum: 10/82672d4125d77f18f4e81d84224ba1a3cd286e00ab824eb7339a7721c034638b6309328e22796c702f06ff22509b80bcb48a39bbe60a262b3f548c0c5bff182f languageName: node linkType: hard -"@swagger-api/apidom-ns-json-schema-draft-6@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-ns-json-schema-draft-6@npm:1.5.1" +"@swagger-api/apidom-ns-json-schema-draft-6@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-ns-json-schema-draft-6@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-error": "npm:^1.5.1" - "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-error": "npm:^1.6.0" + "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.4" - checksum: 10/b9347686dd533e547e09292e673b89cf900e015961c0ac4cc38f5db0cd4d443992e284547e8e3d92251ba867e55f729fb1da058dbe2a98f3848fc2f562cf9b2c + checksum: 10/b17d3117096ddd5b0f9bede7c335562d79b5815a6409405f87310cc23a302de50a1e6ea39dead436a1a6b5635e6a61be002ebbaf2019d63b8408c45cc50ef9d1 languageName: node linkType: hard -"@swagger-api/apidom-ns-json-schema-draft-7@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-ns-json-schema-draft-7@npm:1.5.1" +"@swagger-api/apidom-ns-json-schema-draft-7@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-ns-json-schema-draft-7@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-error": "npm:^1.5.1" - "@swagger-api/apidom-ns-json-schema-draft-6": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-error": "npm:^1.6.0" + "@swagger-api/apidom-ns-json-schema-draft-6": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.4" - checksum: 10/757ef4c1eef97e967409b860fb0f605dde08cc9b5b3d62a9a973beaf5eb4a1be47332e3e69ec53f0dabab4d7bfa4322ff72af25ae1b138da9a8dec9631741bf3 + checksum: 10/f2b0d3cd115d898627888c03d0301e34607f1e952012264b9a78dc84416c36efe454d87d83ff23fa52633c8fd0a051cfebd010d35d55f1d9b36d2895283c73e2 languageName: node linkType: hard -"@swagger-api/apidom-ns-openapi-2@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-ns-openapi-2@npm:1.5.1" +"@swagger-api/apidom-ns-openapi-2@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-ns-openapi-2@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-error": "npm:^1.5.1" - "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-error": "npm:^1.6.0" + "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/e8e29823f6fe3a2c0fb8616113d8f6a0f258cc1340d35464d438aed958ca02f3d1f32b504d87addf58eafd00b699735efb4f07bfd43a6d081f66019c627eefa9 + checksum: 10/324dbf81b7e2850c45168188bc27fa56ed2991903a10c46843a41b08a23bac2c9ed43fe4a422e04c9a7252b4859a819f40efd2c2c7a202639c7c074d7ae80503 languageName: node linkType: hard -"@swagger-api/apidom-ns-openapi-3-0@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-ns-openapi-3-0@npm:1.5.1" +"@swagger-api/apidom-ns-openapi-3-0@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-ns-openapi-3-0@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-error": "npm:^1.5.1" - "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-error": "npm:^1.6.0" + "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/a90eb037b2a79bcd1fb33adf0a5b28afc89008e283c0ac3ed1a55244323bb1fd436024b8a182e46e2e3e5e320d1b95882b0d31aa31e287ed54d9fa6eea20dce8 + checksum: 10/1d0a5453b3cd13a9e8acdf97d270e9c5abf20a217d6ee051afe7f39066dbf6f8897c09305499b36173f7d24b039806b31693029f640bc5c9e2d256c725f93be8 languageName: node linkType: hard -"@swagger-api/apidom-ns-openapi-3-1@npm:^1.3.0, @swagger-api/apidom-ns-openapi-3-1@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-ns-openapi-3-1@npm:1.5.1" +"@swagger-api/apidom-ns-openapi-3-1@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-ns-openapi-3-1@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-ast": "npm:^1.5.1" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-json-pointer": "npm:^1.5.1" - "@swagger-api/apidom-ns-json-schema-2020-12": "npm:^1.5.1" - "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.5.1" + "@swagger-api/apidom-ast": "npm:^1.6.0" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-json-pointer": "npm:^1.6.0" + "@swagger-api/apidom-ns-json-schema-2020-12": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/ec1da833df7bb8eb0d097cab416353697259c9c084be8d333579c1004e7e7f3e61080aef91ead3bb3148567836e1046a41883dba6522667bab7f45ba81eb376d + checksum: 10/b97037cd1d40f4f74388562daf874a5a01ba4914b163a70ebe942eb31172c6a0eb2578ba97bd1810a4a78563a6c1c1750e38660c5f0b89877f774d13a2cc83d3 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-api-design-systems-json@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-parser-adapter-api-design-systems-json@npm:1.5.1" +"@swagger-api/apidom-ns-openapi-3-2@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-ns-openapi-3-2@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-api-design-systems": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.5.1" + "@swagger-api/apidom-ast": "npm:^1.6.0" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-json-pointer": "npm:^1.6.0" + "@swagger-api/apidom-ns-json-schema-2020-12": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/a150acf9144bfa78148380c6b36e89eaff33670cd6f91399e3e1915cf9bdf38e6485826599d39d3691f07614b40378ef4fab3a6ece12a5736269b601009de089 + ts-mixer: "npm:^6.0.3" + checksum: 10/fafca923e6b535c756df816475383c8034d2dde6ac4d966363f2a980116e79c5f8b1ea4cb25a4e359ab2661e3b3864bec1868ffe70305cd9b6cece8c34e16254 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-api-design-systems-yaml@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-parser-adapter-api-design-systems-yaml@npm:1.5.1" +"@swagger-api/apidom-parser-adapter-api-design-systems-json@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-api-design-systems-json@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-api-design-systems": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-api-design-systems": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/d34fa2ab11e98ef10e2443a26e4a0f4c32d6538bd40cc89b931ce4537afea57258f99c963a06fe8276387d7e707a1c1047b6e7d19f246de2d10ebbc6230500c4 + checksum: 10/8503382273380338fd43bcdd66827078381e0abdc3989bd54619f59342ba6c895fc2745c7b523415ec121de4748719e47154e3dfe1075f921d93435331b44454 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-arazzo-json-1@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-parser-adapter-arazzo-json-1@npm:1.5.1" +"@swagger-api/apidom-parser-adapter-api-design-systems-yaml@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-api-design-systems-yaml@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-arazzo-1": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-api-design-systems": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/e72e06de9a317cb8fb3c659ad3f18cde342727fb98c76e7f26dd6578c9fdf3c3595a52fe1108048f600475b71af855bb7f9550b2df25f6e6efb28d016763477d + checksum: 10/76f0711511fda6348915c905d8ba29cf9698d68a9a655eaa3f7cdb52458d16904782cb379b438c40cb047bc65123e5824f4e055df2c72d7fee7fad259e80e1a6 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-arazzo-yaml-1@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-parser-adapter-arazzo-yaml-1@npm:1.5.1" +"@swagger-api/apidom-parser-adapter-arazzo-json-1@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-arazzo-json-1@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-arazzo-1": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-arazzo-1": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/d5ff82ec40ac84e284509cd2ad8971693349dc04c4ea9a6a76c5bbec74d87abdc136894bc3c223bdc4b3940fcd709df96e941766e4af1e3612fa173bd9f7a551 + checksum: 10/4b44885c9eec28c0d93d46db2387285d14f14aa100bf6154cc7b6a2a2069bc15c1ed6f0fda7083a773a1e5c5d06bdd4e30809444c7d5e5f1dc44664950aae912 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-asyncapi-json-2@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-parser-adapter-asyncapi-json-2@npm:1.5.1" +"@swagger-api/apidom-parser-adapter-arazzo-yaml-1@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-arazzo-yaml-1@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-arazzo-1": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/dfa61bfcf2cf3f15987971640929e5743d58722dc81036a84a51647e7924c1fe8275123552d3962cb535af3cd5b66875bad3e2160f5735f58f15ae656e6b2643 + checksum: 10/98cd104542af2531e321bf68c6d93be774ccaa2b2d0974ff48f664f04eb3608ea5081e8e31b685b4547c8278a348e0b9d04edfbd5affdcdcf59853fbfc4347dc languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-asyncapi-json-3@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-parser-adapter-asyncapi-json-3@npm:1.5.1" +"@swagger-api/apidom-parser-adapter-asyncapi-json-2@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-asyncapi-json-2@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-asyncapi-3": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/afe23d512760cd82157278d2f26f5031f44a28f4ceffcf41137de4646aa89980bfe57eb20ce8bcc34bc235a0573643672433d6b03070f95514eee8bd57ff23a9 + checksum: 10/7d10c58c8234aeab86c77ad9bc99c511d5ced0b15db5da28c268cbfb0569fede419636046cf095cd57684a344d931835dec9bf3a720273fa983d0a4a42c5038d languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-asyncapi-yaml-2@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2@npm:1.5.1" +"@swagger-api/apidom-parser-adapter-asyncapi-json-3@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-asyncapi-json-3@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-asyncapi-3": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/0038b837f7bf67807d331d5109c0ef3b8ff687e97d8bdd0760d374cda37b3606c817bed7a816be6babc1dde49d723f8c132739e9ecd35bdd8195115d6d1d7477 + checksum: 10/571156ce7c28e34dce3e5144c081f81ca9b42c9fdbb712dbcaf69392f1e384bddad2b404ab8c0e6348340732418f06fb0e3bccdae12146725bf3a31f60020bbb languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-asyncapi-yaml-3@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3@npm:1.5.1" +"@swagger-api/apidom-parser-adapter-asyncapi-yaml-2@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-asyncapi-3": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/cdd26ca62459959b169535c011ca3982d4af0155fd1f640885c585c08a36be25796242c3a5edbd170be86573d8944161884cf33e208c6009e57d531e0fd85073 + checksum: 10/0f4cb552becfc589f0ddd0e42f44e85f1ae83b8da8a896ea5c34c28ad0584fc18823d66f3a67c4110cf928fb54b7c4aa63fe3d8c73081aac1433bb19805812e6 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-json@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-parser-adapter-json@npm:1.5.1" +"@swagger-api/apidom-parser-adapter-asyncapi-yaml-3@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-ast": "npm:^1.5.1" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-error": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-asyncapi-3": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.6.0" + "@types/ramda": "npm:~0.30.0" + ramda: "npm:~0.30.0" + ramda-adjunct: "npm:^5.0.0" + checksum: 10/5595d5feb6e623cc70ec650d55384736fe328d41d5ea977db1d3eb848d5c7afd2a7a53d2d441efd4a97461dc5045b0ca6dd9799fa0f9ef37ff7516d89ac0d30c + languageName: node + linkType: hard + +"@swagger-api/apidom-parser-adapter-json@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-json@npm:1.6.0" + dependencies: + "@babel/runtime-corejs3": "npm:^7.26.10" + "@swagger-api/apidom-ast": "npm:^1.6.0" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-error": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" node-gyp: "npm:latest" ramda: "npm:~0.30.0" @@ -20388,147 +20741,180 @@ __metadata: tree-sitter: "npm:=0.21.1" tree-sitter-json: "npm:=0.24.8" web-tree-sitter: "npm:=0.24.5" - checksum: 10/d53ea516aaad96822721b847e6ad96bf09a0a3673e61e94ea17e8abf222edb5dbcdc6bc0280f99b4e94a065ededd740f529b15b4a2dc207ef0156727e40b200f + checksum: 10/0ca2c1d5488fd76aed0482b1716aabebd1cdde61e7d385a7ea7e204562385043f598429edb8f87279672269a9b058ac16c94755db55ec3916403a22db155b077 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-json-2@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-parser-adapter-openapi-json-2@npm:1.5.1" +"@swagger-api/apidom-parser-adapter-openapi-json-2@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-openapi-json-2@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-openapi-2": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-2": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/729194ae5f41ffcb2ab080aa4d2a3ce3b9bf51a8e5622c0fb8f005c69d5f408af8ed0732507392d2e53997c4a3b203234abca59ccdc33b8f8908a85836e40a2c + checksum: 10/8cdf4ce932611eb07f23fbaaadfb6267171f54f82f2a5966b7623bba7c4b3e1453a346e0289438782db1692bfb6d8ab2247474eaea536627886c7ad5111557da languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-json-3-0@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-parser-adapter-openapi-json-3-0@npm:1.5.1" +"@swagger-api/apidom-parser-adapter-openapi-json-3-0@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-openapi-json-3-0@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/7e76c6de8364e0831cd584d984145676bab5e35497dca744c1c1601d681ed4c906f951bc05487500c838ec96d023b660f145b17b73a871d6712f5d9ef20622ec + checksum: 10/8008051ea25c555a2a567ab6e209bab359eeaea03c5c8775dbda4bc9e3e23f47f0a44a43db8feaf17293ea88a86d82eef83be7ef582702e03a73f8d60cf4c010 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-json-3-1@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-parser-adapter-openapi-json-3-1@npm:1.5.1" +"@swagger-api/apidom-parser-adapter-openapi-json-3-1@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-openapi-json-3-1@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/1bcfba97aa4df7db1479fcd91d0187e3980140ec9481654f6f5d022a74030e17bdf6f07c13ccd7f03a9796d67a018759c40dc8d0617d670e57a70da8b3f9c278 + checksum: 10/f1cf4654f1961cd4a26afda093d5f907f491887e950e9dd7a30109648da38c41672b44009a39e255e0354acda6c729daa9367d6bd1a352859f8c4f1760e0f9ad languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-yaml-2@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-2@npm:1.5.1" +"@swagger-api/apidom-parser-adapter-openapi-json-3-2@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-openapi-json-3-2@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-openapi-2": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-3-2": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/e49c4bc313f024aa96547dba8d49ce83a682054088e1f36ba3dccfaa125866dd78cbf953c597d0f707a3b99ef2c1851560a3f09f0cbd8a53f3c785660407e7f4 + checksum: 10/5fb16b4e9d353d62c360d53658a365ec3ed61fbb09b4664699cd5152661a5d1917fe33efbd555cf9553d7b0397f380c9cefa1d30047578730b4912ffd54e1d10 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-yaml-3-0@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0@npm:1.5.1" +"@swagger-api/apidom-parser-adapter-openapi-yaml-2@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-2@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-2": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/f9d5da693e0869f3e4264d31f53a218692d5f019c8494033d9f5031df095afa108fe0fb868eb5ab282a7a9bf9013a7a186580e677a3925d1ed75d5ced56e0779 + checksum: 10/e0512203b5c8071204da3fae6e882dfc07807f0a6de5faf57584ec089b3c543a0043dfb772ec2e2a09aa21fdadcb13bf8055bed14ca28be8a63c7c4bdd02e301 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-yaml-3-1@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1@npm:1.5.1" +"@swagger-api/apidom-parser-adapter-openapi-yaml-3-0@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/d8e787cb840d76ea57d7d89fc2d18a67c0e8c16547232d104253aba27440b8c8f18db1afb7c9880d9a1e30b2f3a80b1574e20a7cb8e2013ee195e1f950d9d89f + checksum: 10/daf3d67372a1f8805e5b612c4fd82ecda89fe0c74a75968830a05fc61d4d3e87174de4d26e3ccf3c1ad334c893470e854c69ea3b6f9cc0a6db95a2e2d3ef35c4 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.5.1": - version: 1.5.1 - resolution: "@swagger-api/apidom-parser-adapter-yaml-1-2@npm:1.5.1" +"@swagger-api/apidom-parser-adapter-openapi-yaml-3-1@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-ast": "npm:^1.5.1" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-error": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.6.0" + "@types/ramda": "npm:~0.30.0" + ramda: "npm:~0.30.0" + ramda-adjunct: "npm:^5.0.0" + checksum: 10/58995dfe113abf9ba0301e3a9fdf72a494e87526f87ccdd170e659545489c68bcd362d0f9fa31fb99cdb7f8e400fab75b02c3222798a3d3cacee30064a7fb181 + languageName: node + linkType: hard + +"@swagger-api/apidom-parser-adapter-openapi-yaml-3-2@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2@npm:1.6.0" + dependencies: + "@babel/runtime-corejs3": "npm:^7.26.10" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-3-2": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.6.0" + "@types/ramda": "npm:~0.30.0" + ramda: "npm:~0.30.0" + ramda-adjunct: "npm:^5.0.0" + checksum: 10/793c8222647f9582b74128feab6b886904768e3f02f65266af220785b7adfa18978f861735b3514d1817a7df7bb56f467079acf4bd75efef181ca678b9dbb61c + languageName: node + linkType: hard + +"@swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-parser-adapter-yaml-1-2@npm:1.6.0" + dependencies: + "@babel/runtime-corejs3": "npm:^7.26.10" + "@swagger-api/apidom-ast": "npm:^1.6.0" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-error": "npm:^1.6.0" "@tree-sitter-grammars/tree-sitter-yaml": "npm:=0.7.1" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" tree-sitter: "npm:=0.22.4" web-tree-sitter: "npm:=0.24.5" - checksum: 10/33f3d22156e96347d9e63da0028d0eed7bcf3d4500e038f6605c17a630e5d5a171f3fa044c9cb3d3bc3dfed9eb8ac72de0ca52255b809cf25b7e2b02a1426a7b + checksum: 10/de1823718a080ab938d665011760eba042679b953355b58b6e81de5a45f00e0e8e1d8eb4133964bf788986a59e34bc086c395ce59ffe8da57836af1930697300 languageName: node linkType: hard -"@swagger-api/apidom-reference@npm:^1.3.0": - version: 1.5.1 - resolution: "@swagger-api/apidom-reference@npm:1.5.1" +"@swagger-api/apidom-reference@npm:^1.6.0": + version: 1.6.0 + resolution: "@swagger-api/apidom-reference@npm:1.6.0" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.5.1" - "@swagger-api/apidom-error": "npm:^1.5.1" - "@swagger-api/apidom-json-pointer": "npm:^1.5.1" - "@swagger-api/apidom-ns-arazzo-1": "npm:^1.5.1" - "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.5.1" - "@swagger-api/apidom-ns-openapi-2": "npm:^1.5.1" - "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.5.1" - "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-api-design-systems-json": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-arazzo-json-1": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-asyncapi-json-3": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-openapi-json-2": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "npm:^1.5.1" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.5.1" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-error": "npm:^1.6.0" + "@swagger-api/apidom-json-pointer": "npm:^1.6.0" + "@swagger-api/apidom-ns-arazzo-1": "npm:^1.6.0" + "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-2": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-3-2": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-arazzo-json-1": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-asyncapi-json-3": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-openapi-json-2": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-openapi-json-3-2": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2": "npm:^1.6.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.6.0" "@types/ramda": "npm:~0.30.0" axios: "npm:^1.12.2" minimatch: "npm:^10.2.1" @@ -20547,6 +20933,8 @@ __metadata: optional: true "@swagger-api/apidom-ns-openapi-3-1": optional: true + "@swagger-api/apidom-ns-openapi-3-2": + optional: true "@swagger-api/apidom-parser-adapter-api-design-systems-json": optional: true "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": @@ -20571,15 +20959,19 @@ __metadata: optional: true "@swagger-api/apidom-parser-adapter-openapi-json-3-1": optional: true + "@swagger-api/apidom-parser-adapter-openapi-json-3-2": + optional: true "@swagger-api/apidom-parser-adapter-openapi-yaml-2": optional: true "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": optional: true "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": optional: true + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-2": + optional: true "@swagger-api/apidom-parser-adapter-yaml-1-2": optional: true - checksum: 10/eaf3e39f2447ea440303145fea52ee3cef333ac0c805f68cb0416964aaa0057c03f8e6963421768152cf8fa9ca691f54829211d6b201375ce7ac54ba32ca3823 + checksum: 10/c522d87d226554a7844705a1841c27cc5daebd4f1cc6f01c0d39d65bad96865408b8ffdbdc98f99f976dc9d1278f7b6d887ca00b09bdb0f8cf02fbcd257cee43 languageName: node linkType: hard @@ -20601,90 +20993,90 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.15.6": - version: 1.15.6 - resolution: "@swc/core-darwin-arm64@npm:1.15.6" +"@swc/core-darwin-arm64@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-darwin-arm64@npm:1.15.18" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.15.6": - version: 1.15.6 - resolution: "@swc/core-darwin-x64@npm:1.15.6" +"@swc/core-darwin-x64@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-darwin-x64@npm:1.15.18" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.15.6": - version: 1.15.6 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.6" +"@swc/core-linux-arm-gnueabihf@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.18" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.15.6": - version: 1.15.6 - resolution: "@swc/core-linux-arm64-gnu@npm:1.15.6" +"@swc/core-linux-arm64-gnu@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-arm64-gnu@npm:1.15.18" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.15.6": - version: 1.15.6 - resolution: "@swc/core-linux-arm64-musl@npm:1.15.6" +"@swc/core-linux-arm64-musl@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-arm64-musl@npm:1.15.18" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.15.6": - version: 1.15.6 - resolution: "@swc/core-linux-x64-gnu@npm:1.15.6" +"@swc/core-linux-x64-gnu@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-x64-gnu@npm:1.15.18" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.15.6": - version: 1.15.6 - resolution: "@swc/core-linux-x64-musl@npm:1.15.6" +"@swc/core-linux-x64-musl@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-x64-musl@npm:1.15.18" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.15.6": - version: 1.15.6 - resolution: "@swc/core-win32-arm64-msvc@npm:1.15.6" +"@swc/core-win32-arm64-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-win32-arm64-msvc@npm:1.15.18" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.15.6": - version: 1.15.6 - resolution: "@swc/core-win32-ia32-msvc@npm:1.15.6" +"@swc/core-win32-ia32-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-win32-ia32-msvc@npm:1.15.18" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.15.6": - version: 1.15.6 - resolution: "@swc/core-win32-x64-msvc@npm:1.15.6" +"@swc/core-win32-x64-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-win32-x64-msvc@npm:1.15.18" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.15.6": - version: 1.15.6 - resolution: "@swc/core@npm:1.15.6" + version: 1.15.18 + resolution: "@swc/core@npm:1.15.18" dependencies: - "@swc/core-darwin-arm64": "npm:1.15.6" - "@swc/core-darwin-x64": "npm:1.15.6" - "@swc/core-linux-arm-gnueabihf": "npm:1.15.6" - "@swc/core-linux-arm64-gnu": "npm:1.15.6" - "@swc/core-linux-arm64-musl": "npm:1.15.6" - "@swc/core-linux-x64-gnu": "npm:1.15.6" - "@swc/core-linux-x64-musl": "npm:1.15.6" - "@swc/core-win32-arm64-msvc": "npm:1.15.6" - "@swc/core-win32-ia32-msvc": "npm:1.15.6" - "@swc/core-win32-x64-msvc": "npm:1.15.6" + "@swc/core-darwin-arm64": "npm:1.15.18" + "@swc/core-darwin-x64": "npm:1.15.18" + "@swc/core-linux-arm-gnueabihf": "npm:1.15.18" + "@swc/core-linux-arm64-gnu": "npm:1.15.18" + "@swc/core-linux-arm64-musl": "npm:1.15.18" + "@swc/core-linux-x64-gnu": "npm:1.15.18" + "@swc/core-linux-x64-musl": "npm:1.15.18" + "@swc/core-win32-arm64-msvc": "npm:1.15.18" + "@swc/core-win32-ia32-msvc": "npm:1.15.18" + "@swc/core-win32-x64-msvc": "npm:1.15.18" "@swc/counter": "npm:^0.1.3" "@swc/types": "npm:^0.1.25" peerDependencies: @@ -20713,7 +21105,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/d17d1b5e6e81f8c262b668f237b863dd612eaa19fc35e74067b2a27ba3998c0c511ec7f49cd91c02f61fa77dd1b5d7e65d293736f65ec45d6c13989904b1c7eb + checksum: 10/ef198b9feb6eee034e3a912c37988ece2885fec35419e8245d467adbc1fc47a5c3e61869d1bdbe6fff76cbd9186ef278120cbb9746d5f7446576f4a7f15c2dcd languageName: node linkType: hard @@ -20725,11 +21117,11 @@ __metadata: linkType: hard "@swc/helpers@npm:^0.5.0, @swc/helpers@npm:^0.5.17, @swc/helpers@npm:^0.5.8": - version: 0.5.17 - resolution: "@swc/helpers@npm:0.5.17" + version: 0.5.19 + resolution: "@swc/helpers@npm:0.5.19" dependencies: tslib: "npm:^2.8.0" - checksum: 10/1fc8312a78f1f99c8ec838585445e99763eeebff2356100738cdfdb8ad47d2d38df678ee6edd93a90fe319ac52da67adc14ac00eb82b606c5fb8ebc5d06ec2a2 + checksum: 10/3fd365fb3265f97e1241bcbcea9bfa5e15e03c630424e1b54597e00d30be2c271cb0c74f45e1739c6bc5ae892647302fab412de5138941aa96e66aebf4586700 languageName: node linkType: hard @@ -20838,7 +21230,7 @@ __metadata: languageName: node linkType: hard -"@testing-library/jest-dom@npm:^6.0.0, @testing-library/jest-dom@npm:^6.6.3": +"@testing-library/jest-dom@npm:^6.0.0, @testing-library/jest-dom@npm:^6.9.1": version: 6.9.1 resolution: "@testing-library/jest-dom@npm:6.9.1" dependencies: @@ -20903,6 +21295,20 @@ __metadata: languageName: node linkType: hard +"@theguild/federation-composition@npm:^0.21.3": + version: 0.21.3 + resolution: "@theguild/federation-composition@npm:0.21.3" + dependencies: + constant-case: "npm:^3.0.4" + debug: "npm:4.4.3" + json5: "npm:^2.2.3" + lodash.sortby: "npm:^4.7.0" + peerDependencies: + graphql: ^16.0.0 + checksum: 10/9c2f8a9e758e19e20443b146b88b1c1d8eaef4214988fe0cb33c57498090d1ab94f5bbff182d7a5f73c9613e997690093afd256c929718f8d3caea524f385a43 + languageName: node + linkType: hard + "@tmcp/adapter-valibot@npm:^0.1.4": version: 0.1.5 resolution: "@tmcp/adapter-valibot@npm:0.1.5" @@ -21008,13 +21414,6 @@ __metadata: languageName: node linkType: hard -"@trysound/sax@npm:0.2.0": - version: 0.2.0 - resolution: "@trysound/sax@npm:0.2.0" - checksum: 10/7379713eca480ac0d9b6c7b063e06b00a7eac57092354556c81027066eb65b61ea141a69d0cc2e15d32e05b2834d4c9c2184793a5e36bbf5daf05ee5676af18c - languageName: node - linkType: hard - "@ts-graphviz/adapter@npm:^2.0.6": version: 2.0.6 resolution: "@ts-graphviz/adapter@npm:2.0.6" @@ -21962,9 +22361,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.151": - version: 4.17.23 - resolution: "@types/lodash@npm:4.17.23" - checksum: 10/05935534a44aadef67c2158b2fb4a042a226970088106a40ddc67e4f063783149fe5cf02279d7dd4a1e72c98d9189b9430face659645dbf77270f8c4c3e387f5 + version: 4.17.24 + resolution: "@types/lodash@npm:4.17.24" + checksum: 10/0f2082565f60f9787eefc046edc38458054512be5a8b3584ef0bad5fd9e85d0ab55ec5a1fbfae1ed6ba015cf1f9e837d5fb4da1f99fc60b8f74b2a46146fb00f languageName: node linkType: hard @@ -22095,6 +22494,15 @@ __metadata: languageName: node linkType: hard +"@types/mute-stream@npm:^0.0.4": + version: 0.0.4 + resolution: "@types/mute-stream@npm:0.0.4" + dependencies: + "@types/node": "npm:*" + checksum: 10/af8d83ad7b68ea05d9357985daf81b6c9b73af4feacb2f5c2693c7fd3e13e5135ef1bd083ce8d5bdc8e97acd28563b61bb32dec4e4508a8067fcd31b8a098632 + languageName: node + linkType: hard + "@types/mysql@npm:2.15.27": version: 2.15.27 resolution: "@types/mysql@npm:2.15.27" @@ -22179,6 +22587,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^20.10.7": + version: 20.19.37 + resolution: "@types/node@npm:20.19.37" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10/7e0d561d0d980109a0b64c6f797584a69d706421f18c0b24850206a15a4286443e676032abfa438590f25e4dfc3561ffc8d7f5c0c90edf08b705f6f9c35febba + languageName: node + linkType: hard + "@types/node@npm:^22.13.14": version: 22.19.1 resolution: "@types/node@npm:22.19.1" @@ -22189,11 +22606,11 @@ __metadata: linkType: hard "@types/nodemailer@npm:^7.0.0": - version: 7.0.10 - resolution: "@types/nodemailer@npm:7.0.10" + version: 7.0.11 + resolution: "@types/nodemailer@npm:7.0.11" dependencies: "@types/node": "npm:*" - checksum: 10/1c6ee3c93c5ba98cb1106e4e06bdf0df13ab27ca3b44468c6a1712fc4249f3bb7f0cd912248d6523b94cd163b31e036b5eeda40d81e1707ae5ab5df197b3da28 + checksum: 10/a657038e919603f1e7136bd47c631c4ef2857576f57d5024d37a2581d4993ef352681e9264604747db7055347e19818add8ab9f964be0f116dd437f5d31ff0e5 languageName: node linkType: hard @@ -22311,12 +22728,12 @@ __metadata: languageName: node linkType: hard -"@types/pg-pool@npm:2.0.6": - version: 2.0.6 - resolution: "@types/pg-pool@npm:2.0.6" +"@types/pg-pool@npm:2.0.7": + version: 2.0.7 + resolution: "@types/pg-pool@npm:2.0.7" dependencies: "@types/pg": "npm:*" - checksum: 10/cc54ce97115effc982bd052f79901a78215e76554aca0ecc92e78eb907e4fb2962924039369cd9aaf48075f1637593ce14647c62d3a2eb03789ce5d1c6df750b + checksum: 10/b2ac51f1e98cd97ef8ee9c09f4db6bb369dfa406dc41533b13a3b7c6e3a5c8c1d52ee139f8bc453b5b5c0125d1fedea610c230696a722ec9176076455e6f267a languageName: node linkType: hard @@ -22373,6 +22790,15 @@ __metadata: languageName: node linkType: hard +"@types/proper-lockfile@npm:^4": + version: 4.1.4 + resolution: "@types/proper-lockfile@npm:4.1.4" + dependencies: + "@types/retry": "npm:*" + checksum: 10/b0d1b8e84a563b2c5f869f7ff7542b1d83dec03d1c9d980847cbb189865f44b4a854673cdde59767e41bcb8c31932e613ac43822d358a6f8eede6b79ccfceb1d + languageName: node + linkType: hard + "@types/protocol-buffers-schema@npm:^3.4.3": version: 3.4.3 resolution: "@types/protocol-buffers-schema@npm:3.4.3" @@ -22383,9 +22809,9 @@ __metadata: linkType: hard "@types/qs@npm:*, @types/qs@npm:^6.9.6": - version: 6.14.0 - resolution: "@types/qs@npm:6.14.0" - checksum: 10/1909205514d22b3cbc7c2314e2bd8056d5f05dfb21cf4377f0730ee5e338ea19957c41735d5e4806c746176563f50005bbab602d8358432e25d900bdf4970826 + version: 6.15.0 + resolution: "@types/qs@npm:6.15.0" + checksum: 10/871162881f1c83e61d0c8c243c65549be5dddf33a6911f3324edeebd4087207b1174644da9a3afaa20cf494c5288d2a1ece09e10e4822f755339f14a05c339ea languageName: node linkType: hard @@ -22561,6 +22987,13 @@ __metadata: languageName: node linkType: hard +"@types/retry@npm:*": + version: 0.12.5 + resolution: "@types/retry@npm:0.12.5" + checksum: 10/3fb6bf91835ca0eb2987567d6977585235a7567f8aeb38b34a8bb7bbee57ac050ed6f04b9998cda29701b8c893f5dfe315869bc54ac17e536c9235637fe351a2 + languageName: node + linkType: hard + "@types/retry@npm:0.12.0": version: 0.12.0 resolution: "@types/retry@npm:0.12.0" @@ -22662,6 +23095,13 @@ __metadata: languageName: node linkType: hard +"@types/shell-quote@npm:^1.7.5": + version: 1.7.5 + resolution: "@types/shell-quote@npm:1.7.5" + checksum: 10/32b4d697c7d23dbadf40713692c47f1595f083a3b3deea76cb18e30a05d197aa9205d2b87f6d92edb60cda120b51e35d32bda96ed9b0a7e32921eed2deb4559e + languageName: node + linkType: hard + "@types/sinon@npm:^17.0.3": version: 17.0.3 resolution: "@types/sinon@npm:17.0.3" @@ -22930,6 +23370,13 @@ __metadata: languageName: node linkType: hard +"@types/wrap-ansi@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/wrap-ansi@npm:3.0.0" + checksum: 10/8aa644946ca4e859668c36b8e2bcf2ac4bdee59dac760414730ea57be8a93ae9166ebd40a088f2ab714843aaea2a2a67f0e6e6ec11cfc9c8701b2466ca1c4089 + languageName: node + linkType: hard + "@types/ws@npm:*, @types/ws@npm:^8.0.0, @types/ws@npm:^8.5.10": version: 8.18.1 resolution: "@types/ws@npm:8.18.1" @@ -22995,51 +23442,51 @@ __metadata: linkType: hard "@typescript-eslint/eslint-plugin@npm:^8.17.0": - version: 8.54.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.54.0" + version: 8.57.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.57.0" dependencies: "@eslint-community/regexpp": "npm:^4.12.2" - "@typescript-eslint/scope-manager": "npm:8.54.0" - "@typescript-eslint/type-utils": "npm:8.54.0" - "@typescript-eslint/utils": "npm:8.54.0" - "@typescript-eslint/visitor-keys": "npm:8.54.0" + "@typescript-eslint/scope-manager": "npm:8.57.0" + "@typescript-eslint/type-utils": "npm:8.57.0" + "@typescript-eslint/utils": "npm:8.57.0" + "@typescript-eslint/visitor-keys": "npm:8.57.0" ignore: "npm:^7.0.5" natural-compare: "npm:^1.4.0" ts-api-utils: "npm:^2.4.0" peerDependencies: - "@typescript-eslint/parser": ^8.54.0 - eslint: ^8.57.0 || ^9.0.0 + "@typescript-eslint/parser": ^8.57.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10/8f1c74ac77d7a84ae3f201bb09cb67271662befed036266af1eaa0653d09b545353441640516c1c86e0a94939887d32f0473c61a642488b14d46533742bfbd1b + checksum: 10/515ed019b16ff2ed4dacb1c2f1cd94227f16f93a8fe086d2bd60f78e6a36ffb20a048d55ddafdac4359d88d16f727c31b36814dba7479c4998f6ad0cc1da2c77 languageName: node linkType: hard "@typescript-eslint/parser@npm:^8.16.0": - version: 8.54.0 - resolution: "@typescript-eslint/parser@npm:8.54.0" + version: 8.57.0 + resolution: "@typescript-eslint/parser@npm:8.57.0" dependencies: - "@typescript-eslint/scope-manager": "npm:8.54.0" - "@typescript-eslint/types": "npm:8.54.0" - "@typescript-eslint/typescript-estree": "npm:8.54.0" - "@typescript-eslint/visitor-keys": "npm:8.54.0" + "@typescript-eslint/scope-manager": "npm:8.57.0" + "@typescript-eslint/types": "npm:8.57.0" + "@typescript-eslint/typescript-estree": "npm:8.57.0" + "@typescript-eslint/visitor-keys": "npm:8.57.0" debug: "npm:^4.4.3" peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10/d2e09462c9966ef3deeba71d9e41d1d4876c61eea65888c93a3db6fba48b89a2165459c6519741d40e969da05ed98d3f4c87a7f56c5521ab5699743cc315f6cb + checksum: 10/9f51f8d8a81475d9870f380d9d737b9b59d89a0b7c8f9dce87e23b566d2b95986980717104dc87e2aa207de7ea0880f83963675fbe703c5531016dcacbc4c389 languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.54.0": - version: 8.54.0 - resolution: "@typescript-eslint/project-service@npm:8.54.0" +"@typescript-eslint/project-service@npm:8.57.0": + version: 8.57.0 + resolution: "@typescript-eslint/project-service@npm:8.57.0" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.54.0" - "@typescript-eslint/types": "npm:^8.54.0" + "@typescript-eslint/tsconfig-utils": "npm:^8.57.0" + "@typescript-eslint/types": "npm:^8.57.0" debug: "npm:^4.4.3" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10/93f0483f6bbcf7cf776a53a130f7606f597fba67cf111e1897873bf1531efaa96e4851cfd461da0f0cc93afbdb51e47bcce11cf7dd4fb68b7030c7f9f240b92f + checksum: 10/4333c1ac52490926c780b2556d903b3d679d280e60b425d38ae851efa457ebe65b8aa9e1e88651e035527926a368cb52099f4bc395de7ec70f848430576c5db4 languageName: node linkType: hard @@ -23053,38 +23500,38 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.54.0, @typescript-eslint/scope-manager@npm:^8.51.0": - version: 8.54.0 - resolution: "@typescript-eslint/scope-manager@npm:8.54.0" +"@typescript-eslint/scope-manager@npm:8.57.0, @typescript-eslint/scope-manager@npm:^8.56.0": + version: 8.57.0 + resolution: "@typescript-eslint/scope-manager@npm:8.57.0" dependencies: - "@typescript-eslint/types": "npm:8.54.0" - "@typescript-eslint/visitor-keys": "npm:8.54.0" - checksum: 10/3474f3197e8647754393dee62b3145c9de71eaa66c8a68f61c8283aa332141803885db9c96caa6a51f78128ad9ef92f774a90361655e57bd951d5b57eb76f914 + "@typescript-eslint/types": "npm:8.57.0" + "@typescript-eslint/visitor-keys": "npm:8.57.0" + checksum: 10/72a7086b1605f55dea36909d74e21b023ebd438b393e6ceb736ecc711f487d0add6d4f3648c1fc6c1a01faecd2a7a1f8839f92d8e7fa27f3937000f1fece2e33 languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.54.0, @typescript-eslint/tsconfig-utils@npm:^8.54.0": - version: 8.54.0 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.54.0" +"@typescript-eslint/tsconfig-utils@npm:8.57.0, @typescript-eslint/tsconfig-utils@npm:^8.57.0": + version: 8.57.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.57.0" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10/e9d6b29538716f007919bfcee94f09b7f8e7d2b684ad43d1a3c8d43afb9f0539c7707f84a34f42054e31c8c056b0ccf06575d89e860b4d34632ffefaefafe1fc + checksum: 10/cd451a0d1b19faa16314986bcb5aeb4bd98a77f23d4d627304434fc423689a675d6c009f19316006cdca4b83183951fcd8b56d721e595bb6b0d9d52ad0f43c5b languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.54.0": - version: 8.54.0 - resolution: "@typescript-eslint/type-utils@npm:8.54.0" +"@typescript-eslint/type-utils@npm:8.57.0": + version: 8.57.0 + resolution: "@typescript-eslint/type-utils@npm:8.57.0" dependencies: - "@typescript-eslint/types": "npm:8.54.0" - "@typescript-eslint/typescript-estree": "npm:8.54.0" - "@typescript-eslint/utils": "npm:8.54.0" + "@typescript-eslint/types": "npm:8.57.0" + "@typescript-eslint/typescript-estree": "npm:8.57.0" + "@typescript-eslint/utils": "npm:8.57.0" debug: "npm:^4.4.3" ts-api-utils: "npm:^2.4.0" peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10/60e92fb32274abd70165ce6f4187e4cffa55416374c63731d7de8fdcfb7a558b4dd48909ff1ad38ac39d2ea1248ec54d6ce38dbc065fd34529a217fc2450d5b1 + checksum: 10/7ee7ca9090b973f77754e83aebf80c8263f02150109b844ccebb8f5db130b90b95af38343e875ade23fc520a197754107f3706fa0432ae2c32a32e95f1399350 languageName: node linkType: hard @@ -23095,10 +23542,10 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:8.54.0, @typescript-eslint/types@npm:^8.54.0": - version: 8.54.0 - resolution: "@typescript-eslint/types@npm:8.54.0" - checksum: 10/c25cc0bdf90fb150cf6ce498897f43fe3adf9e872562159118f34bd91a9bfab5f720cb1a41f3cdf253b2e840145d7d372089b7cef5156624ef31e98d34f91b31 +"@typescript-eslint/types@npm:8.57.0, @typescript-eslint/types@npm:^8.57.0": + version: 8.57.0 + resolution: "@typescript-eslint/types@npm:8.57.0" + checksum: 10/ba23a4deeb5a89b9b99fee35f58d662901f236000d0f6bcada5143a2ef5ec831c7909e9192def8a48d18f8c3327b78bf3e9c02d770b4a4d721a0422b97ca1e29 languageName: node linkType: hard @@ -23121,37 +23568,37 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.54.0, @typescript-eslint/typescript-estree@npm:^8.23.0": - version: 8.54.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.54.0" +"@typescript-eslint/typescript-estree@npm:8.57.0, @typescript-eslint/typescript-estree@npm:^8.23.0": + version: 8.57.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.57.0" dependencies: - "@typescript-eslint/project-service": "npm:8.54.0" - "@typescript-eslint/tsconfig-utils": "npm:8.54.0" - "@typescript-eslint/types": "npm:8.54.0" - "@typescript-eslint/visitor-keys": "npm:8.54.0" + "@typescript-eslint/project-service": "npm:8.57.0" + "@typescript-eslint/tsconfig-utils": "npm:8.57.0" + "@typescript-eslint/types": "npm:8.57.0" + "@typescript-eslint/visitor-keys": "npm:8.57.0" debug: "npm:^4.4.3" - minimatch: "npm:^9.0.5" + minimatch: "npm:^10.2.2" semver: "npm:^7.7.3" tinyglobby: "npm:^0.2.15" ts-api-utils: "npm:^2.4.0" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10/3a545037c6f9319251d3ba44cf7a3216b1372422469e27f7ed3415244ebf42553da1ab4644da42d3f0ae2706a8cad12529ffebcb2e75406f74e3b30b812d010d + checksum: 10/eae6027de9b8e0d5c443ad77219689c59dd02085867ea34c0613c93d625cbb9c517fe514fcc38061d49bd39422ca1f170764473b21db178e1db39deeeca6458b languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.54.0, @typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.48.0, @typescript-eslint/utils@npm:^8.51.0": - version: 8.54.0 - resolution: "@typescript-eslint/utils@npm:8.54.0" +"@typescript-eslint/utils@npm:8.57.0, @typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.48.0, @typescript-eslint/utils@npm:^8.56.0": + version: 8.57.0 + resolution: "@typescript-eslint/utils@npm:8.57.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.9.1" - "@typescript-eslint/scope-manager": "npm:8.54.0" - "@typescript-eslint/types": "npm:8.54.0" - "@typescript-eslint/typescript-estree": "npm:8.54.0" + "@typescript-eslint/scope-manager": "npm:8.57.0" + "@typescript-eslint/types": "npm:8.57.0" + "@typescript-eslint/typescript-estree": "npm:8.57.0" peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10/9f88a2a7ab3e11aa0ff7f99c0e66a0cf2cba10b640def4c64a4f4ef427fecfb22f28dbe5697535915eb01f6507515ac43e45e0ff384bf82856e3420194d9ffdd + checksum: 10/76e3c8eb9f6e28e4cf1359a1b32facaa7523464baeeba8f00a8d68a5a40b3d5d79cfffe48e85d365b06637b6ea6474f63f08a5b5844b2595c2e552e067dc9449 languageName: node linkType: hard @@ -23179,13 +23626,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.54.0": - version: 8.54.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.54.0" +"@typescript-eslint/visitor-keys@npm:8.57.0": + version: 8.57.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.57.0" dependencies: - "@typescript-eslint/types": "npm:8.54.0" - eslint-visitor-keys: "npm:^4.2.1" - checksum: 10/cca5380ee30250302ee1459e5a0a38de8c16213026dbbff3d167fa7d71d012f31d60ac4483ad45ebd13f2ac963d1ca52dd5f22759a68d4ee57626e421769187a + "@typescript-eslint/types": "npm:8.57.0" + eslint-visitor-keys: "npm:^5.0.0" + checksum: 10/049edd9e6a5e919bed84bffeefa3d3d944295183feaeb175119c17bcbefa051f10e0e135e4a4dc545c5aa781bd11a276ec5e62fd1211f6692c06a84036b8c4c5 languageName: node linkType: hard @@ -23848,42 +24295,44 @@ __metadata: languageName: node linkType: hard -"@whatwg-node/events@npm:^0.1.0": - version: 0.1.1 - resolution: "@whatwg-node/events@npm:0.1.1" - checksum: 10/3a356ca23522190201e27446cfd7ebf1cf96815ddb9d1ba5da0a00bbe6c1d28b4094862104411101fbedd47c758b25fe3683033f6a3e80933029efd664c33567 - languageName: node - linkType: hard - -"@whatwg-node/fetch@npm:^0.9.0": - version: 0.9.14 - resolution: "@whatwg-node/fetch@npm:0.9.14" +"@whatwg-node/disposablestack@npm:^0.0.6": + version: 0.0.6 + resolution: "@whatwg-node/disposablestack@npm:0.0.6" dependencies: - "@whatwg-node/node-fetch": "npm:^0.5.0" - urlpattern-polyfill: "npm:^9.0.0" - checksum: 10/74cdaf82abc2eaa15790fe1a15c8d1208bed090956888c8f35ba622b977e75027edef6b85705b0e7680497f478bd90bf0b784b486de95c84a2806e19d65a1f0c + "@whatwg-node/promise-helpers": "npm:^1.0.0" + tslib: "npm:^2.6.3" + checksum: 10/1b73b7b95a7abad77f81fa30044239da6d5fe636c5c9f259571087437ed093996c484e0e0df00860a37244d7b9e518f7d156161f335040ddda27e7ed2e21ecb2 languageName: node linkType: hard -"@whatwg-node/node-fetch@npm:^0.5.0": - version: 0.5.0 - resolution: "@whatwg-node/node-fetch@npm:0.5.0" +"@whatwg-node/fetch@npm:^0.10.13": + version: 0.10.13 + resolution: "@whatwg-node/fetch@npm:0.10.13" dependencies: - "@whatwg-node/events": "npm:^0.1.0" - busboy: "npm:^1.6.0" - fast-querystring: "npm:^1.1.1" - fast-url-parser: "npm:^1.1.3" - tslib: "npm:^2.3.1" - checksum: 10/b779288b07296e0be60e90e338de46b9d0c892d2e38b99bd04d062bbd4acb429607afc92b9bfdbf10841cef6f4b531e63415197583677de69a07e7b2e39350b9 + "@whatwg-node/node-fetch": "npm:^0.8.3" + urlpattern-polyfill: "npm:^10.0.0" + checksum: 10/23e1dd0242962fa5d253a07ddf37fd4df31a5065d65c0e74edb11616415adb1456df76f9df9eeca28ed7454f0bf39304af5bae33c66d8b05399de4a7779675e8 languageName: node linkType: hard -"@whatwg-node/promise-helpers@npm:^1.0.0": - version: 1.3.0 - resolution: "@whatwg-node/promise-helpers@npm:1.3.0" +"@whatwg-node/node-fetch@npm:^0.8.3": + version: 0.8.5 + resolution: "@whatwg-node/node-fetch@npm:0.8.5" + dependencies: + "@fastify/busboy": "npm:^3.1.1" + "@whatwg-node/disposablestack": "npm:^0.0.6" + "@whatwg-node/promise-helpers": "npm:^1.3.2" + tslib: "npm:^2.6.3" + checksum: 10/6ba6c4ff8af66487a17abcc1b3cc6a6fc5b03268af6211db53da5585f7360924c09a19fb9814de33dde8dafa98708b367538c772fe7102c15804f1bace297ef1 + languageName: node + linkType: hard + +"@whatwg-node/promise-helpers@npm:^1.0.0, @whatwg-node/promise-helpers@npm:^1.2.1, @whatwg-node/promise-helpers@npm:^1.2.4, @whatwg-node/promise-helpers@npm:^1.3.2": + version: 1.3.2 + resolution: "@whatwg-node/promise-helpers@npm:1.3.2" dependencies: tslib: "npm:^2.6.3" - checksum: 10/4971319c340f4c16755dd3fe84772e28bcc550e1f723ae10f94e02910ded545f88e65da0a44499912e23c27ed5f7458e1303a99cdd5818792c5958205ee386b4 + checksum: 10/22513e7075d2e6e067399f6b3065a1f280d77aab2cc8699fe5bf9496a76ea7ede2cf4d46fad6f033d0ad686f974c52f85335c3dcddd656d1c8700636713f94a9 languageName: node linkType: hard @@ -24760,12 +25209,12 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.14.1, acorn@npm:^8.15.0, acorn@npm:^8.4.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": - version: 8.15.0 - resolution: "acorn@npm:8.15.0" +"acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.14.1, acorn@npm:^8.15.0, acorn@npm:^8.16.0, acorn@npm:^8.4.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": + version: 8.16.0 + resolution: "acorn@npm:8.16.0" bin: acorn: bin/acorn - checksum: 10/77f2de5051a631cf1729c090e5759148459cdb76b5f5c70f890503d629cf5052357b0ce783c0f976dd8a93c5150f59f6d18df1def3f502396a20f81282482fa4 + checksum: 10/690c673bb4d61b38ef82795fab58526471ad7f7e67c0e40c4ff1e10ecd80ce5312554ef633c9995bfc4e6d170cef165711f9ca9e49040b62c0c66fbf2dd3df2b languageName: node linkType: hard @@ -25059,7 +25508,7 @@ __metadata: languageName: node linkType: hard -"ansi-regex@npm:*, ansi-regex@npm:^6.0.1": +"ansi-regex@npm:*, ansi-regex@npm:^6.0.1, ansi-regex@npm:^6.2.2": version: 6.2.2 resolution: "ansi-regex@npm:6.2.2" checksum: 10/9b17ce2c6daecc75bcd5966b9ad672c23b184dc3ed9bf3c98a0702f0d2f736c15c10d461913568f2cf527a5e64291c7473358885dd493305c84a1cfed66ba94f @@ -25267,12 +25716,12 @@ __metadata: languageName: node linkType: hard -"aria-hidden@npm:^1.1.1": - version: 1.2.3 - resolution: "aria-hidden@npm:1.2.3" +"aria-hidden@npm:^1.2.4": + version: 1.2.6 + resolution: "aria-hidden@npm:1.2.6" dependencies: tslib: "npm:^2.0.0" - checksum: 10/cd7f8474f1bef2dadce8fc74ef6d0fa8c9a477ee3c9e49fc3698e5e93a62014140c520266ee28969d63b5ab474144fe48b6182d010feb6a223f7a73928e6660a + checksum: 10/1914e5a36225dccdb29f0b88cc891eeca736cdc5b0c905ab1437b90b28b5286263ed3a221c75b7dc788f25b942367be0044b2ac8ccf073a72e07a50b1d964202 languageName: node linkType: hard @@ -25782,14 +26231,14 @@ __metadata: languageName: node linkType: hard -"axios@npm:1.13.5, axios@npm:^1.0.0, axios@npm:^1.12.0, axios@npm:^1.12.2, axios@npm:^1.13.0, axios@npm:^1.13.5, axios@npm:^1.7.4": - version: 1.13.5 - resolution: "axios@npm:1.13.5" +"axios@npm:^1.0.0, axios@npm:^1.12.0, axios@npm:^1.12.2, axios@npm:^1.13.0, axios@npm:^1.13.5, axios@npm:^1.13.6, axios@npm:^1.7.4": + version: 1.13.6 + resolution: "axios@npm:1.13.6" dependencies: follow-redirects: "npm:^1.15.11" form-data: "npm:^4.0.5" proxy-from-env: "npm:^1.1.0" - checksum: 10/db726d09902565ef9a0632893530028310e2ec2b95b727114eca1b101450b00014133dfc3871cffc87983fb922bca7e4874d7e2826d1550a377a157cdf3f05b6 + checksum: 10/a7ed83c2af3ef21d64609df0f85e76893a915a864c5934df69241001d0578082d6521a0c730bf37518ee458821b5695957cb10db9fc705f2a8996c8686ea7a89 languageName: node linkType: hard @@ -26267,9 +26716,9 @@ __metadata: linkType: hard "bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.11.8, bn.js@npm:^4.11.9": - version: 4.12.0 - resolution: "bn.js@npm:4.12.0" - checksum: 10/10f8db196d3da5adfc3207d35d0a42aa29033eb33685f20ba2c36cadfe2de63dad05df0a20ab5aae01b418d1c4b3d4d205273085262fa020d17e93ff32b67527 + version: 4.12.3 + resolution: "bn.js@npm:4.12.3" + checksum: 10/57ed5a055f946f3e009f1589c45a5242db07f3dddfc72e4506f0dd9d8b145f0dbee4edabc2499288f3fc338eb712fb96a1c623a2ed2bcd49781df1a64db64dd1 languageName: node linkType: hard @@ -27201,10 +27650,10 @@ __metadata: languageName: node linkType: hard -"cjs-module-lexer@npm:^2.1.0": - version: 2.1.1 - resolution: "cjs-module-lexer@npm:2.1.1" - checksum: 10/db4331152f4414f3a451865554290eb7d146699aa2aefe1504891951d791ab450f1a75b0477630a4f2942be26f257c4e0627fbfed29d322b5266956b3643f3c1 +"cjs-module-lexer@npm:^2.1.0, cjs-module-lexer@npm:^2.2.0": + version: 2.2.0 + resolution: "cjs-module-lexer@npm:2.2.0" + checksum: 10/fc8eb5c1919504366d8260a150d93c4e857740e770467dc59ca0cc34de4b66c93075559a5af65618f359187866b1be40e036f4e1a1bab2f1e06001c216415f74 languageName: node linkType: hard @@ -27239,12 +27688,12 @@ __metadata: linkType: hard "cleye@npm:^2.2.1": - version: 2.2.1 - resolution: "cleye@npm:2.2.1" + version: 2.3.0 + resolution: "cleye@npm:2.3.0" dependencies: terminal-columns: "npm:^2.0.0" - type-flag: "npm:^4.0.3" - checksum: 10/4d24a7e4863d38b9a40c01ca332baa7526b320c14fc488e7fb2d3f54b0ad5baa8c5defa6fc6adc079f50f629409056d9da74be6611f6394b709c9062d39b2216 + type-flag: "npm:^4.1.0" + checksum: 10/a6fe9d8431db05907d1699099de338e2a227843ebb85d2b540d609d649dd35b482f6534105daa2930dd18649dd0bfd0526d43c1d746eec10195fda31784953c7 languageName: node linkType: hard @@ -27286,7 +27735,7 @@ __metadata: languageName: node linkType: hard -"cli-spinners@npm:^2.5.0": +"cli-spinners@npm:^2.5.0, cli-spinners@npm:^2.9.2": version: 2.9.2 resolution: "cli-spinners@npm:2.9.2" checksum: 10/a0a863f442df35ed7294424f5491fa1756bd8d2e4ff0c8736531d886cec0ece4d85e8663b77a5afaf1d296e3cbbebff92e2e99f52bbea89b667cbe789b994794 @@ -27521,17 +27970,17 @@ __metadata: languageName: node linkType: hard -"codemirror-graphql@npm:^2.0.10, codemirror-graphql@npm:^2.0.13": - version: 2.0.13 - resolution: "codemirror-graphql@npm:2.0.13" +"codemirror-graphql@npm:^2.2.1": + version: 2.2.4 + resolution: "codemirror-graphql@npm:2.2.4" dependencies: "@types/codemirror": "npm:^0.0.90" - graphql-language-service: "npm:5.2.2" + graphql-language-service: "npm:5.5.0" peerDependencies: "@codemirror/language": 6.0.0 codemirror: ^5.65.3 - graphql: ^15.5.0 || ^16.0.0 - checksum: 10/1a759c3b549c5fa24fffaeb8799156bea0a37c5bf672b09af1a672c89bc4464409ead99e79189644e8c3b4b9746472ef9d5b9f6387cc3fa2f16bfb2c4b75d4db + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 + checksum: 10/89e8994588c6145578b707694509854ccdff40c19e82d15dff832c152cadc8978f3688b804b8e75f656d3b7dfa5dc5e6344e854eda5141f0d92c0a505fda1371 languageName: node linkType: hard @@ -28032,6 +28481,17 @@ __metadata: languageName: node linkType: hard +"constant-case@npm:^3.0.4": + version: 3.0.4 + resolution: "constant-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case: "npm:^2.0.2" + checksum: 10/6c3346d51afc28d9fae922e966c68eb77a19d94858dba230dd92d7b918b37d36db50f0311e9ecf6847e43e934b1c01406a0936973376ab17ec2c471fbcfb2cf3 + languageName: node + linkType: hard + "constants-browserify@npm:^1.0.0": version: 1.0.0 resolution: "constants-browserify@npm:1.0.0" @@ -28947,7 +29407,7 @@ __metadata: languageName: node linkType: hard -"dataloader@npm:^2.0.0, dataloader@npm:^2.2.2": +"dataloader@npm:^2.0.0, dataloader@npm:^2.2.3": version: 2.2.3 resolution: "dataloader@npm:2.2.3" checksum: 10/83fe6259abe00ae64c5f48252ef59d8e5fcabda9fd4d26685f14a76eeca596bf6f9500d9f22a0094c50c3ea782a0977728f9367e232dfa0fdb5c9d646de279b2 @@ -29016,7 +29476,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.3": +"debug@npm:4, debug@npm:4.4.3, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -29761,15 +30221,15 @@ __metadata: languageName: node linkType: hard -"dompurify@npm:^3.1.7, dompurify@npm:^3.2.4": - version: 3.3.1 - resolution: "dompurify@npm:3.3.1" +"dompurify@npm:^3.1.7, dompurify@npm:^3.3.2": + version: 3.3.2 + resolution: "dompurify@npm:3.3.2" dependencies: "@types/trusted-types": "npm:^2.0.7" dependenciesMeta: "@types/trusted-types": optional: true - checksum: 10/f71cca489e628591165d16e8cf4fa4f0d3e2ee48db4d73e9d2c5bedc6f915c92f9e9f101f8c4ba790bec0cdffe7f4e1747f5e31c69dc53ce7ae20a81ff6b0022 + checksum: 10/3ca02559677ce6d9583a500f21ffbb6b9e88f1af99f69fa0d0d9442cddbac98810588c869f8b435addb5115492d6e49870024bca322169b941bafedb99c7f281 languageName: node linkType: hard @@ -29837,7 +30297,7 @@ __metadata: languageName: node linkType: hard -"dset@npm:^3.1.2, dset@npm:^3.1.4": +"dset@npm:^3.1.2": version: 3.1.4 resolution: "dset@npm:3.1.4" checksum: 10/6268c9e2049c8effe6e5a1952f02826e8e32468b5ced781f15f8f3b1c290da37626246fec014fbdd1503413f981dff6abd8a4c718ec9952fd45fccb6ac9de43f @@ -30112,13 +30572,13 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.18.0, enhanced-resolve@npm:^5.19.0": - version: 5.19.0 - resolution: "enhanced-resolve@npm:5.19.0" +"enhanced-resolve@npm:^5.18.0, enhanced-resolve@npm:^5.20.0": + version: 5.20.0 + resolution: "enhanced-resolve@npm:5.20.0" dependencies: graceful-fs: "npm:^4.2.4" tapable: "npm:^2.3.0" - checksum: 10/b537d52173bf1ba903c623f96a43ea3b51466ee2b606b2fcca30d73d453fd79c8683dccbb83523de27cd02763c906f11486e2591a4335e6afe49fa5ad6e67b83 + checksum: 10/ba22699e4b46dc1be6441c359636ebcdd5028229219a7d6ba10f39996401f950967f8297ddf3284d0ee8e33c8133a8742696154e383cc08d8bd2bf80ba87df97 languageName: node linkType: hard @@ -30153,13 +30613,6 @@ __metadata: languageName: node linkType: hard -"entities@npm:~2.1.0": - version: 2.1.0 - resolution: "entities@npm:2.1.0" - checksum: 10/fe71642e42e108540b0324dea03e00f3dbad93617c601bfcf292c3f852c236af3e58469219c4653f6f05df781a446f3b82105b8d26b936d0fa246b0103f2f951 - languageName: node - linkType: hard - "env-paths@npm:^2.2.0, env-paths@npm:^2.2.1": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -30826,55 +31279,54 @@ __metadata: linkType: hard "eslint-plugin-storybook@npm:^10.3.0-alpha.1": - version: 10.3.0-alpha.1 - resolution: "eslint-plugin-storybook@npm:10.3.0-alpha.1" + version: 10.3.0-alpha.14 + resolution: "eslint-plugin-storybook@npm:10.3.0-alpha.14" dependencies: "@typescript-eslint/utils": "npm:^8.48.0" peerDependencies: eslint: ">=8" - storybook: ^10.3.0-alpha.1 - checksum: 10/2b3b6c11709c536b9639f7ed91bfb0cb5035129f0cbb245abd59f208ce179a0f99b558e3f8e89987a83d80ab30395dfc0560f401295123edf221cafba7a05c1a + storybook: ^10.3.0-alpha.14 + checksum: 10/851c3912503ff014aac3e0d964574fbecae381b5f77239a07e0b036e2ae1c258d3e279c8e82bbb0c9f286be57883aa2d78c5330e3da3c6719ad4805d8e330b70 languageName: node linkType: hard "eslint-plugin-testing-library@npm:^7.0.0": - version: 7.15.4 - resolution: "eslint-plugin-testing-library@npm:7.15.4" + version: 7.16.0 + resolution: "eslint-plugin-testing-library@npm:7.16.0" dependencies: - "@typescript-eslint/scope-manager": "npm:^8.51.0" - "@typescript-eslint/utils": "npm:^8.51.0" + "@typescript-eslint/scope-manager": "npm:^8.56.0" + "@typescript-eslint/utils": "npm:^8.56.0" peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - checksum: 10/be717d041ce6d368f7dd422813f620f455aff6c7c61a9364287af32fe18d31ca88467c2bf50e742bf470820db5f7bea617aac232c8e0a61daadcee8b76428786 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + checksum: 10/6581b41d25838c61b818f6b80e998ff2d72a646531c1c017023842fe5523a9ea3e9e75e7ce0d84abee9d73309602d64911985cbaace8dfbb0a94415da1b213a8 languageName: node linkType: hard "eslint-plugin-unused-imports@npm:^4.1.4": - version: 4.3.0 - resolution: "eslint-plugin-unused-imports@npm:4.3.0" + version: 4.4.1 + resolution: "eslint-plugin-unused-imports@npm:4.4.1" peerDependencies: "@typescript-eslint/eslint-plugin": ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 - eslint: ^9.0.0 || ^8.0.0 + eslint: ^10.0.0 || ^9.0.0 || ^8.0.0 peerDependenciesMeta: "@typescript-eslint/eslint-plugin": optional: true - checksum: 10/64ec1f686d90f18a27c27273a0338bad6964a611f497b81c5e7eace9d9a4f38d96070d7c389c2626095a9e5e7dbcafccc6444fa2933c9a7649996a7ca875738f + checksum: 10/b420fd55c393a6fdacfdbd0d1adf4cd44bed9a6584f05245091a6716272c57f38154d04b76f253619d8bf22823c0b9d630ef6b5b09edad6e51b8a1f7aec56c22 languageName: node linkType: hard "eslint-rspack-plugin@npm:^4.2.1": - version: 4.3.0 - resolution: "eslint-rspack-plugin@npm:4.3.0" + version: 4.4.1 + resolution: "eslint-rspack-plugin@npm:4.4.1" dependencies: "@types/eslint": "npm:^8.56.10" jest-worker: "npm:^29.7.0" micromatch: "npm:^4.0.8" normalize-path: "npm:^3.0.0" - schema-utils: "npm:^4.2.0" tinyglobby: "npm:^0.2.15" peerDependencies: - eslint: ^8.0.0 || ^9.0.0 - checksum: 10/e312c993474cd4df180f6790359936b581391ac660769ce94b7e00e96322aedbbebc8d9132969db72b04d4588ccf40dd17c45d8f5d48df625a015a819c215ae3 + eslint: ^8.0.0 || ^9.0.0 || ^10.0.0 + checksum: 10/310bddfd2818d885d382e481b510e68648fff735ba1bbf93cb03f191d92c109baca4a0072c4fa00c28347fbba03aabfe890075d2e2cade557ab9e3d5feb7a149 languageName: node linkType: hard @@ -30905,10 +31357,10 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^4.2.1": - version: 4.2.1 - resolution: "eslint-visitor-keys@npm:4.2.1" - checksum: 10/3ee00fc6a7002d4b0ffd9dc99e13a6a7882c557329e6c25ab254220d71e5c9c4f89dca4695352949ea678eb1f3ba912a18ef8aac0a7fe094196fd92f441bfce2 +"eslint-visitor-keys@npm:^5.0.0": + version: 5.0.1 + resolution: "eslint-visitor-keys@npm:5.0.1" + checksum: 10/f9cc1a57b75e0ef949545cac33d01e8367e302de4c1483266ed4d8646ee5c306376660196bbb38b004e767b7043d1e661cb4336b49eff634a1bbe75c1db709ec languageName: node linkType: hard @@ -31371,7 +31823,7 @@ __metadata: "@backstage/plugin-search-backend-node": "workspace:^" "@backstage/plugin-signals-backend": "workspace:^" "@backstage/plugin-techdocs-backend": "workspace:^" - "@opentelemetry/auto-instrumentations-node": "npm:^0.67.0" + "@opentelemetry/auto-instrumentations-node": "npm:^0.71.0" "@opentelemetry/exporter-prometheus": "npm:^0.211.0" "@opentelemetry/sdk-node": "npm:^0.211.0" example-app: "link:../app" @@ -31526,23 +31978,14 @@ __metadata: languageName: node linkType: hard -"express-rate-limit@npm:^7.5.0": - version: 7.5.1 - resolution: "express-rate-limit@npm:7.5.1" - peerDependencies: - express: ">= 4.11" - checksum: 10/357c3398450144ab7bbce2841d0bf4f93a0f3fd9d1d5ed9a0ee331b557af969cc790941dc37b47f8d9b5672964aa0e31666f770e1f48b334dc7d1e69f6433040 - languageName: node - linkType: hard - -"express-rate-limit@npm:^8.2.1": - version: 8.2.1 - resolution: "express-rate-limit@npm:8.2.1" +"express-rate-limit@npm:^8.2.1, express-rate-limit@npm:^8.2.2": + version: 8.3.0 + resolution: "express-rate-limit@npm:8.3.0" dependencies: - ip-address: "npm:10.0.1" + ip-address: "npm:10.1.0" peerDependencies: express: ">= 4.11" - checksum: 10/7cbf70df2e88e590e463d2d8f93380775b2ea181d97f2c50c2ff9f2c666c247f83109a852b21d9c99ccc5762119101f281f54a27252a2f1a0a918be6d71f955b + checksum: 10/e896a66fecc10639e65873186fdfb71f19d6af650220eb7ea5450725215c3eed8dc6ddcfa1e68a9db8c9facc3326fbc281512ad3ccd8f107f42a2466ce12c18c languageName: node linkType: hard @@ -31660,13 +32103,6 @@ __metadata: languageName: node linkType: hard -"extract-files@npm:^11.0.0": - version: 11.0.0 - resolution: "extract-files@npm:11.0.0" - checksum: 10/02bf0dde9617d67795e38a182d8bf58828a7c5d77762623ff05e72d461a0e980071a860e2503231db2cc8824d8da35cefb1750937dcbe018cb0e67e37f20a7be - languageName: node - linkType: hard - "fast-copy@npm:^3.0.2": version: 3.0.2 resolution: "fast-copy@npm:3.0.2" @@ -31743,7 +32179,7 @@ __metadata: languageName: node linkType: hard -"fast-querystring@npm:^1.0.0, fast-querystring@npm:^1.1.1": +"fast-querystring@npm:^1.0.0": version: 1.1.2 resolution: "fast-querystring@npm:1.1.2" dependencies: @@ -31787,15 +32223,6 @@ __metadata: languageName: node linkType: hard -"fast-url-parser@npm:^1.1.3": - version: 1.1.3 - resolution: "fast-url-parser@npm:1.1.3" - dependencies: - punycode: "npm:^1.3.2" - checksum: 10/6d33f46ce9776f7f3017576926207a950ca39bc5eb78fc794404f2288fe494720f9a119084b75569bd9eb09d2b46678bfaf39c191fb2c808ef3c833dc8982752 - languageName: node - linkType: hard - "fast-xml-parser@npm:4.4.1, fast-xml-parser@npm:^4.4.1": version: 4.4.1 resolution: "fast-xml-parser@npm:4.4.1" @@ -31941,7 +32368,7 @@ __metadata: languageName: node linkType: hard -"figures@npm:^3.0.0": +"figures@npm:^3.0.0, figures@npm:^3.2.0": version: 3.2.0 resolution: "figures@npm:3.2.0" dependencies: @@ -32139,15 +32566,15 @@ __metadata: linkType: hard "find-process@npm:^2.0.0": - version: 2.0.0 - resolution: "find-process@npm:2.0.0" + version: 2.1.0 + resolution: "find-process@npm:2.1.0" dependencies: chalk: "npm:~4.1.2" commander: "npm:^12.1.0" loglevel: "npm:^1.9.2" bin: - find-process: dist/bin/find-process.js - checksum: 10/a8f23b72e7b67c8f039c2234116f68439c067e8d0f90484b53814aac04f1891cf322c7cf4b4193a14b91a19ba9c68bd8bcfb8a556626e6667dcaaefbca3294f9 + find-process: dist/cjs/bin/find-process.js + checksum: 10/5ccaaddf66eacf74eb2605ddd8d8aaa4515029dc78eed2b0a39ceef3a4f1ae229046d30ff44845fd496b6306e2c750a7abad8db16f0683be7c32aa502dc61a21 languageName: node linkType: hard @@ -32230,10 +32657,10 @@ __metadata: languageName: node linkType: hard -"flatted@npm:3.3.3, flatted@npm:^3.1.0, flatted@npm:^3.2.7": - version: 3.3.3 - resolution: "flatted@npm:3.3.3" - checksum: 10/8c96c02fbeadcf4e8ffd0fa24983241e27698b0781295622591fc13585e2f226609d95e422bcf2ef044146ffacb6b68b1f20871454eddf75ab3caa6ee5f4a1fe +"flatted@npm:^3.1.0, flatted@npm:^3.2.7, flatted@npm:^3.3.4": + version: 3.4.0 + resolution: "flatted@npm:3.4.0" + checksum: 10/6007896f62acb93c811aef52d962c5cd2fa3c703eacfc4146865c3fc8964d3e466c66e10b1c175a84ce0a207fdc761e2e70d7ca982c82604257d45866169af6a languageName: node linkType: hard @@ -32529,14 +32956,14 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:11.3.3, fs-extra@npm:^11.0.0, fs-extra@npm:^11.1.0, fs-extra@npm:^11.2.0, fs-extra@npm:~11.3.0": - version: 11.3.3 - resolution: "fs-extra@npm:11.3.3" +"fs-extra@npm:11.3.4, fs-extra@npm:^11.0.0, fs-extra@npm:^11.1.0, fs-extra@npm:^11.2.0, fs-extra@npm:~11.3.0": + version: 11.3.4 + resolution: "fs-extra@npm:11.3.4" dependencies: graceful-fs: "npm:^4.2.0" jsonfile: "npm:^6.0.1" universalify: "npm:^2.0.0" - checksum: 10/daeaefafbebe8fa6efd2fb96fc926f2c952be5877811f00a6794f0d64e0128e3d0d93368cd328f8f063b45deacf385c40e3d931aa46014245431cd2f4f89c67a + checksum: 10/1b8deea9c540a2efe63c750bc9e1ba6238115579d1571d67fe8fb58e3fb6df19aba29fd4ebb81217cf0bf5bce0df30ca68dbc3e06f6652b856edd385ce0ff649 languageName: node linkType: hard @@ -32756,7 +33183,7 @@ __metadata: languageName: node linkType: hard -"gcp-metadata@npm:^6.0.0, gcp-metadata@npm:^6.1.0": +"gcp-metadata@npm:^6.1.0": version: 6.1.0 resolution: "gcp-metadata@npm:6.1.0" dependencies: @@ -32987,6 +33414,15 @@ __metadata: languageName: node linkType: hard +"get-value@npm:^3.0.1": + version: 3.0.1 + resolution: "get-value@npm:3.0.1" + dependencies: + isobject: "npm:^3.0.1" + checksum: 10/3ba777d33448181d8b6c21ce11f31194257119d32dbd632b27db6e3f27fe78100405b4dd93137eea9f4aa2d0a9c623b13747d728d472a7ca6cb93046c3f521a1 + languageName: node + linkType: hard + "getopts@npm:2.3.0": version: 2.3.0 resolution: "getopts@npm:2.3.0" @@ -33063,7 +33499,7 @@ __metadata: languageName: node linkType: hard -"glob-to-regex.js@npm:^1.0.1": +"glob-to-regex.js@npm:^1.0.0, glob-to-regex.js@npm:^1.0.1": version: 1.2.0 resolution: "glob-to-regex.js@npm:1.2.0" peerDependencies: @@ -33079,14 +33515,14 @@ __metadata: languageName: node linkType: hard -"glob@npm:13.0.3": - version: 13.0.3 - resolution: "glob@npm:13.0.3" +"glob@npm:13.0.6, glob@npm:^13.0.1": + version: 13.0.6 + resolution: "glob@npm:13.0.6" dependencies: - minimatch: "npm:^10.2.0" - minipass: "npm:^7.1.2" - path-scurry: "npm:^2.0.0" - checksum: 10/38a9c78de078f61df1a555badf1198833c298fd3320520c93504a499f14afed6c37c892c576119f05e4d94981a4550a1a2246e826ffa6894a427004aff5b78b2 + minimatch: "npm:^10.2.2" + minipass: "npm:^7.1.3" + path-scurry: "npm:^2.0.2" + checksum: 10/201ad69e5f0aa74e1d8c00a481581f8b8c804b6a4fbfabeeb8541f5d756932800331daeba99b58fb9e4cd67e12ba5a7eba5b82fb476691588418060b84353214 languageName: node linkType: hard @@ -33106,7 +33542,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^11.0.1, glob@npm:^11.1.0": +"glob@npm:^11.0.1": version: 11.1.0 resolution: "glob@npm:11.1.0" dependencies: @@ -33453,35 +33889,33 @@ __metadata: languageName: node linkType: hard -"graphiql@npm:3.1.1": - version: 3.1.1 - resolution: "graphiql@npm:3.1.1" +"graphiql@npm:^3.9.0": + version: 3.9.0 + resolution: "graphiql@npm:3.9.0" dependencies: - "@graphiql/react": "npm:^0.20.3" - "@graphiql/toolkit": "npm:^0.9.1" - graphql-language-service: "npm:^5.2.0" - markdown-it: "npm:^12.2.0" + "@graphiql/react": "npm:^0.29.0" + react-compiler-runtime: "npm:19.1.0-rc.1" peerDependencies: - graphql: ^15.5.0 || ^16.0.0 + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 react: ^16.8.0 || ^17 || ^18 react-dom: ^16.8.0 || ^17 || ^18 - checksum: 10/c234f24e3bb568f210a45f6a408cc12adb2a82cc7be8ecbdf81cadc7ae398f35474a443fbc20e99d6125acf20268e6a666b05b928725542ea3d133c51798687f + checksum: 10/33a17064594fc883262de07e8cd3012563cf5965e401a8ab3e1aed9c38e69b4222a3cd1cefa58e3c0e5ba9e01dcd403d0452c4991814702da4d6fb37acc9a8ae languageName: node linkType: hard -"graphql-config@npm:^5.0.2": - version: 5.1.5 - resolution: "graphql-config@npm:5.1.5" +"graphql-config@npm:^5.1.6": + version: 5.1.6 + resolution: "graphql-config@npm:5.1.6" dependencies: "@graphql-tools/graphql-file-loader": "npm:^8.0.0" "@graphql-tools/json-file-loader": "npm:^8.0.0" "@graphql-tools/load": "npm:^8.1.0" "@graphql-tools/merge": "npm:^9.0.0" - "@graphql-tools/url-loader": "npm:^8.0.0" - "@graphql-tools/utils": "npm:^10.0.0" + "@graphql-tools/url-loader": "npm:^9.0.0" + "@graphql-tools/utils": "npm:^11.0.0" cosmiconfig: "npm:^8.1.0" jiti: "npm:^2.0.0" - minimatch: "npm:^9.0.5" + minimatch: "npm:^10.0.0" string-env-interpolation: "npm:^1.0.1" tslib: "npm:^2.4.0" peerDependencies: @@ -33490,7 +33924,7 @@ __metadata: peerDependenciesMeta: cosmiconfig-toml-loader: optional: true - checksum: 10/f33a4e73265f84790888d05d7dbf50a0b3454adf6e184ac018165e679ecab7fca746fbf4069dea601261f193dcb5a015a1679403ea1fd4eab109e79d8fed306d + checksum: 10/941b3166d395d22ae6f4048c564b09364d2234c88f8ad596188599d4650c28a35a2484b549e0ef239ae461a23874efc69e0234f7f8977a8a6f39640867086003 languageName: node linkType: hard @@ -33503,18 +33937,18 @@ __metadata: languageName: node linkType: hard -"graphql-language-service@npm:5.2.2, graphql-language-service@npm:^5.2.0, graphql-language-service@npm:^5.2.2": - version: 5.2.2 - resolution: "graphql-language-service@npm:5.2.2" +"graphql-language-service@npm:5.5.0, graphql-language-service@npm:^5.3.1": + version: 5.5.0 + resolution: "graphql-language-service@npm:5.5.0" dependencies: debounce-promise: "npm:^3.1.2" nullthrows: "npm:^1.0.0" vscode-languageserver-types: "npm:^3.17.1" peerDependencies: - graphql: ^15.5.0 || ^16.0.0 + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 bin: graphql: dist/temp-bin.js - checksum: 10/3148b0a49fb2784eaf363a2d7e4fe0800f69c11d24528c2daaff1bf3d07be24db61e1d84b6bec94c68068f7ad1fc6d459edfbda964418ad3735bc353a636a690 + checksum: 10/7f398395cab50faaad717d107413df5424ac102bf33ac4a5e4ef34bf7c2e13a27d51fd93456601f560874801bf93bec281589164d901bab3aef56f4518782f60 languageName: node linkType: hard @@ -33540,7 +33974,7 @@ __metadata: languageName: node linkType: hard -"graphql-ws@npm:^5.14.0, graphql-ws@npm:^5.4.1": +"graphql-ws@npm:^5.4.1": version: 5.16.2 resolution: "graphql-ws@npm:5.16.2" peerDependencies: @@ -33549,6 +33983,25 @@ __metadata: languageName: node linkType: hard +"graphql-ws@npm:^6.0.6": + version: 6.0.7 + resolution: "graphql-ws@npm:6.0.7" + peerDependencies: + "@fastify/websocket": ^10 || ^11 + crossws: ~0.3 + graphql: ^15.10.1 || ^16 + ws: ^8 + peerDependenciesMeta: + "@fastify/websocket": + optional: true + crossws: + optional: true + ws: + optional: true + checksum: 10/633b142a7a8683f900f1f3590a30ff696076d94d17cc8c0a42d069288cd8ca77b4967e87a9f7ac884c026106be42055b9e2bda5e7de28579bd2fc8e65d0b0424 + languageName: node + linkType: hard + "graphql@npm:^14.0.2 || ^15.5": version: 15.10.1 resolution: "graphql@npm:15.10.1" @@ -33557,9 +34010,9 @@ __metadata: linkType: hard "graphql@npm:^16.0.0, graphql@npm:^16.8.1": - version: 16.12.0 - resolution: "graphql@npm:16.12.0" - checksum: 10/e299bc97cca178e549c8c1ed4cb164f631f07be987d3657f76cdf18c0250040cc0d456d4b6d41c87b855cac97b15a62ed345557527efcb0546492895a893bb87 + version: 16.13.1 + resolution: "graphql@npm:16.13.1" + checksum: 10/a42f857f60351e1f4665aa5bc5524796d3f45bf81793e6db932f902aff769b0488dafaa1d9c07bddda7122a1f6d0b0105fab12d38992f6b6fb81fc3d7ced1afc languageName: node linkType: hard @@ -33994,9 +34447,9 @@ __metadata: linkType: hard "hono@npm:^4.11.4": - version: 4.12.0 - resolution: "hono@npm:4.12.0" - checksum: 10/8a4de1ac4394816cbdbc87ef813ce3fb767953dff4cd88fa519bec66df8d7d801dcfddea815677440c5b0fbed636ee7fbdab834f589154550d83ea4de713e769 + version: 4.12.5 + resolution: "hono@npm:4.12.5" + checksum: 10/3d03cf2885f7c75325869a178bc94291a17a656002b930dc91456cb177366f6f344c7c90c09707c1fcb4c6e40b1f33fac98f1622628061628cabadcf8cf6d3fd languageName: node linkType: hard @@ -34564,9 +35017,9 @@ __metadata: linkType: hard "immutable@npm:^3.x.x": - version: 3.8.2 - resolution: "immutable@npm:3.8.2" - checksum: 10/8a94647c769e97c9685be1b89d5e1b3171e8c1361fb9061fbcf78f630f70bf60e4de0bfca8bdd24a54b1fb814a945a76a30b11b7ee08967f9802a138a54498a2 + version: 3.8.3 + resolution: "immutable@npm:3.8.3" + checksum: 10/29db366d4dff83b0b296150c351b08db24837005909a71c123d860c1d2a40605f19a3ecd56d1e96be9799e947ddf7906897a28fa2e81f0b8c63c652b6bfe5550 languageName: node linkType: hard @@ -34610,6 +35063,18 @@ __metadata: languageName: node linkType: hard +"import-in-the-middle@npm:^3.0.0": + version: 3.0.0 + resolution: "import-in-the-middle@npm:3.0.0" + dependencies: + acorn: "npm:^8.15.0" + acorn-import-attributes: "npm:^1.9.5" + cjs-module-lexer: "npm:^2.2.0" + module-details-from-path: "npm:^1.0.4" + checksum: 10/0bf1f22d00a080e7f651db8c5d136aa4aca6829397769f22fb544a67d9117b1c78590f180a690eb19eb0cfb4a558beac66b6508d346cccc91e1e5f75c934e9de + languageName: node + linkType: hard + "import-lazy@npm:^2.1.0": version: 2.1.0 resolution: "import-lazy@npm:2.1.0" @@ -34774,7 +35239,7 @@ __metadata: languageName: node linkType: hard -"inquirer@npm:8.2.7, inquirer@npm:^8.0.0, inquirer@npm:^8.2.0": +"inquirer@npm:^8.0.0, inquirer@npm:^8.2.0": version: 8.2.7 resolution: "inquirer@npm:8.2.7" dependencies: @@ -34844,7 +35309,7 @@ __metadata: languageName: node linkType: hard -"invariant@npm:^2.2.2, invariant@npm:^2.2.4": +"invariant@npm:^2.2.2": version: 2.2.4 resolution: "invariant@npm:2.2.4" dependencies: @@ -34870,10 +35335,10 @@ __metadata: languageName: node linkType: hard -"ip-address@npm:10.0.1": - version: 10.0.1 - resolution: "ip-address@npm:10.0.1" - checksum: 10/09731acda32cd8e14c46830c137e7e5940f47b36d63ffb87c737331270287d631cf25aa95570907a67d3f919fdb25f4470c404eda21e62f22e0a55927f4dd0fb +"ip-address@npm:10.1.0": + version: 10.1.0 + resolution: "ip-address@npm:10.1.0" + checksum: 10/a6979629d1ad9c1fb424bc25182203fad739b40225aebc55ec6243bbff5035faf7b9ed6efab3a097de6e713acbbfde944baacfa73e11852bb43989c45a68d79e languageName: node linkType: hard @@ -35753,8 +36218,8 @@ __metadata: linkType: hard "isomorphic-git@npm:^1.23.0": - version: 1.37.1 - resolution: "isomorphic-git@npm:1.37.1" + version: 1.37.2 + resolution: "isomorphic-git@npm:1.37.2" dependencies: async-lock: "npm:^1.4.1" clean-git-ref: "npm:^2.0.1" @@ -35769,7 +36234,7 @@ __metadata: simple-get: "npm:^4.0.1" bin: isogit: cli.cjs - checksum: 10/f10bffddf0f6ac34792fefba48ffcd7c020470f8c99412e0a948cd7a433046131b61384da1df57633a2113950ded027ac5b19b9e18581f96e5e5d3709a3b1eec + checksum: 10/23c225821588b7e85bc5f7eb975ad2e909a0dc954241ee019cfd18c826d3d1119804d719e2105f41212284f81b74bd907696f59e47b68ddad2fbde83859ab3a2 languageName: node linkType: hard @@ -35807,6 +36272,15 @@ __metadata: languageName: node linkType: hard +"isows@npm:^1.0.7": + version: 1.0.7 + resolution: "isows@npm:1.0.7" + peerDependencies: + ws: "*" + checksum: 10/044b949b369872882af07b60b613b5801ae01b01a23b5b72b78af80c8103bbeed38352c3e8ceff13a7834bc91fd2eb41cf91ec01d59a041d8705680e6b0ec546 + languageName: node + linkType: hard + "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.0 resolution: "istanbul-lib-coverage@npm:3.2.0" @@ -36927,9 +37401,9 @@ __metadata: linkType: hard "jsonc-parser@npm:^3.2.0": - version: 3.2.0 - resolution: "jsonc-parser@npm:3.2.0" - checksum: 10/bd68b902e5f9394f01da97921f49c5084b2dc03a0c5b4fdb2a429f8d6f292686c1bf87badaeb0a8148d024192a88f5ad2e57b2918ba43fe25cf15f3371db64d4 + version: 3.3.1 + resolution: "jsonc-parser@npm:3.3.1" + checksum: 10/9b0dc391f20b47378f843ef1e877e73ec652a5bdc3c5fa1f36af0f119a55091d147a86c1ee86a232296f55c929bba174538c2bf0312610e0817a22de131cc3f4 languageName: node linkType: hard @@ -37222,6 +37696,17 @@ __metadata: languageName: node linkType: hard +"keytar@npm:^7.9.0": + version: 7.9.0 + resolution: "keytar@npm:7.9.0" + dependencies: + node-addon-api: "npm:^4.3.0" + node-gyp: "npm:latest" + prebuild-install: "npm:^7.0.1" + checksum: 10/904795bc304f8ad89b80f915c869a941a383312b58584212a199473d18647035cfda92a9c53e6c53bf13ea0fed23037c9597eb418a5c71ee9454f140f026fac9 + languageName: node + linkType: hard + "keyv@npm:*, keyv@npm:^5.2.1": version: 5.6.0 resolution: "keyv@npm:5.6.0" @@ -37320,8 +37805,8 @@ __metadata: linkType: hard "knip@npm:^5.42.0": - version: 5.83.1 - resolution: "knip@npm:5.83.1" + version: 5.85.0 + resolution: "knip@npm:5.85.0" dependencies: "@nodelib/fs.walk": "npm:^1.2.3" fast-glob: "npm:^3.3.3" @@ -37341,7 +37826,7 @@ __metadata: bin: knip: bin/knip.js knip-bun: bin/knip-bun.js - checksum: 10/d4783b22eacbcb726790211cb6e7cfb966cbb42c4737ae71fbe766523add2e9b8c7eb389a14f9418742d7722f26b107b036c6f0e1af4b56d86a7097f62172a54 + checksum: 10/1b80127b24bad3822c2a128e08c30e33db2c716ccb3c1ee19a082d5f04c7d24f049213e15a6369d9449bf60564a86be16e8f986902ba7251bee17a4530c6c954 languageName: node linkType: hard @@ -37442,11 +37927,11 @@ __metadata: linkType: hard "ldapts@npm:^8.0.6": - version: 8.1.6 - resolution: "ldapts@npm:8.1.6" + version: 8.1.7 + resolution: "ldapts@npm:8.1.7" dependencies: strict-event-emitter-types: "npm:2.0.0" - checksum: 10/3ebcdf335e1477d920cdea562fdf1f91218cb4bed6de7077a837a33ec93f3a8e1659e346c28335bd0be0144505315f9445c87acae857b8af385939f0ac78a60e + checksum: 10/6dc0002df51a62de5391066df981fc3dab5471dc733fdedd698b58dea4c802ca20d17498518757555d979abc93a320b3e88bbcf3b380c1635930e48aebf3847c languageName: node linkType: hard @@ -37529,15 +38014,6 @@ __metadata: languageName: node linkType: hard -"linkify-it@npm:^3.0.1": - version: 3.0.2 - resolution: "linkify-it@npm:3.0.2" - dependencies: - uc.micro: "npm:^1.0.1" - checksum: 10/c1fcabd105c2bb97ce993dd9c6474b2f29ab759176f4fddb82fa01f497a0cea268c67276c7bd43a5cf390a6af54e25ead77ea70f75a648fc9736caff6aa6f2f4 - languageName: node - linkType: hard - "linkify-it@npm:^5.0.0": version: 5.0.0 resolution: "linkify-it@npm:5.0.0" @@ -38448,21 +38924,6 @@ __metadata: languageName: node linkType: hard -"markdown-it@npm:^12.2.0": - version: 12.3.2 - resolution: "markdown-it@npm:12.3.2" - dependencies: - argparse: "npm:^2.0.1" - entities: "npm:~2.1.0" - linkify-it: "npm:^3.0.1" - mdurl: "npm:^1.0.1" - uc.micro: "npm:^1.0.5" - bin: - markdown-it: bin/markdown-it.js - checksum: 10/d83d794bfb9f5e05750b25db401d9c1f9b97c6bbabb6cfd78988bb98652c62c24417435487238e2b91fd4e495547ae8c9429fb4c69e9f5bf49bd0dd292d53f24 - languageName: node - linkType: hard - "markdown-it@npm:^14.1.0": version: 14.1.0 resolution: "markdown-it@npm:14.1.0" @@ -38732,7 +39193,7 @@ __metadata: languageName: node linkType: hard -"mdurl@npm:^1.0.0, mdurl@npm:^1.0.1": +"mdurl@npm:^1.0.0": version: 1.0.1 resolution: "mdurl@npm:1.0.1" checksum: 10/ada367d01c9e81d07328101f187d5bd8641b71f33eab075df4caed935a24fa679e625f07108801d8250a5e4a99e5cd4be7679957a11424a3aa3e740d2bb2d5cb @@ -38814,17 +39275,27 @@ __metadata: languageName: node linkType: hard -"memfs@npm:^4.51.1, memfs@npm:^4.6.0": - version: 4.51.1 - resolution: "memfs@npm:4.51.1" +"memfs@npm:^4.56.10, memfs@npm:^4.6.0": + version: 4.56.11 + resolution: "memfs@npm:4.56.11" dependencies: + "@jsonjoy.com/fs-core": "npm:4.56.11" + "@jsonjoy.com/fs-fsa": "npm:4.56.11" + "@jsonjoy.com/fs-node": "npm:4.56.11" + "@jsonjoy.com/fs-node-builtins": "npm:4.56.11" + "@jsonjoy.com/fs-node-to-fsa": "npm:4.56.11" + "@jsonjoy.com/fs-node-utils": "npm:4.56.11" + "@jsonjoy.com/fs-print": "npm:4.56.11" + "@jsonjoy.com/fs-snapshot": "npm:4.56.11" "@jsonjoy.com/json-pack": "npm:^1.11.0" "@jsonjoy.com/util": "npm:^1.9.0" glob-to-regex.js: "npm:^1.0.1" thingies: "npm:^2.5.0" tree-dump: "npm:^1.0.3" tslib: "npm:^2.0.0" - checksum: 10/a2f70cd1b366f910a39bb1a398c03d6f3f0db936376697eca3e176609e9f6acc0ed40fb44d6293dd15eb3f730c3fce536e5024395e3dc92f54374133c96ba1b7 + peerDependencies: + tslib: 2 + checksum: 10/2d99bce59dee0f5c96a039851892e39c09c1eee11d8978cf37dbb93462d706d842d6a5b0b3d5081d47f19883ed4b6d973c8086301a9155b098b80d66c3e79cb7 languageName: node linkType: hard @@ -38886,15 +39357,15 @@ __metadata: languageName: node linkType: hard -"meros@npm:^1.1.4, meros@npm:^1.2.1": - version: 1.3.0 - resolution: "meros@npm:1.3.0" +"meros@npm:^1.1.4, meros@npm:^1.3.2": + version: 1.3.2 + resolution: "meros@npm:1.3.2" peerDependencies: "@types/node": ">=13" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/1893d226866058a32161ab069294a1a16975c765416a2b05165dfafba07cd958ca12503e35c621ffe736c62d935ccb1ce60cb723e2a9e0b85e02bb3236722ef6 + checksum: 10/9269b243f91b714b75169f63231af81bcd4c049c1308f6e78fc08214af89323ce2e36a7c1603cde6f32cf4ba79075365335c0d6b549e997b53124568f213f0a5 languageName: node linkType: hard @@ -39447,7 +39918,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:3.1.2, minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": +"minimatch@npm:3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -39456,6 +39927,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:3.1.5, minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": + version: 3.1.5 + resolution: "minimatch@npm:3.1.5" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10/b11a7ee5773cd34c1a0c8436cdbe910901018fb4b6cb47aa508a18d567f6efd2148507959e35fba798389b161b8604a2d704ccef751ea36bd4582f9852b7d63f + languageName: node + linkType: hard + "minimatch@npm:9.0.3": version: 9.0.3 resolution: "minimatch@npm:9.0.3" @@ -39465,12 +39945,12 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^10.1.1, minimatch@npm:^10.2.0, minimatch@npm:^10.2.1": - version: 10.2.3 - resolution: "minimatch@npm:10.2.3" +"minimatch@npm:^10.0.0, minimatch@npm:^10.1.1, minimatch@npm:^10.2.1, minimatch@npm:^10.2.2": + version: 10.2.4 + resolution: "minimatch@npm:10.2.4" dependencies: brace-expansion: "npm:^5.0.2" - checksum: 10/186c6a6ce9f7a79ae7776efc799c32d1a6670ebbcc2a8756e6cb6ec4aab7439a6ca6e592e1a6aac5f21674eefae5b19821b8fa95072a4f4567da1ae40eb6075d + checksum: 10/aea4874e521c55bb60744685bbffe3d152e5460f84efac3ea936e6bbe2ceba7deb93345fec3f9bb17f7b6946776073a64d40ae32bf5f298ad690308121068a1f languageName: node linkType: hard @@ -39646,10 +40126,10 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": - version: 7.1.2 - resolution: "minipass@npm:7.1.2" - checksum: 10/c25f0ee8196d8e6036661104bacd743785b2599a21de5c516b32b3fa2b83113ac89a2358465bc04956baab37ffb956ae43be679b2262bf7be15fce467ccd7950 +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2, minipass@npm:^7.1.3": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 10/175e4d5e20980c3cd316ae82d2c031c42f6c746467d8b1905b51060a0ba4461441a0c25bb67c025fd9617f9a3873e152c7b543c6b5ac83a1846be8ade80dffd6 languageName: node linkType: hard @@ -39690,7 +40170,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.5, mkdirp@npm:^0.5.6": +"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.5": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" dependencies: @@ -39839,10 +40319,10 @@ __metadata: languageName: node linkType: hard -"module-details-from-path@npm:^1.0.3": - version: 1.0.3 - resolution: "module-details-from-path@npm:1.0.3" - checksum: 10/f93226e9154fc8cb91f4609b639167ec7ad9155b30be4924d9717656648a3ae5f181d4e2338434d4c5afc7b5f4c10dd3b64109e5b89a4be70b20a25ba3573d54 +"module-details-from-path@npm:^1.0.3, module-details-from-path@npm:^1.0.4": + version: 1.0.4 + resolution: "module-details-from-path@npm:1.0.4" + checksum: 10/2ebfada5358492f6ab496b70f70a1042f2ee7a4c79d29467f59ed6704f741fb4461d7cecb5082144ed39a05fec4d19e9ff38b731c76228151be97227240a05b2 languageName: node linkType: hard @@ -39971,17 +40451,14 @@ __metadata: linkType: hard "multer@npm:^2.0.2": - version: 2.0.2 - resolution: "multer@npm:2.0.2" + version: 2.1.1 + resolution: "multer@npm:2.1.1" dependencies: append-field: "npm:^1.0.0" busboy: "npm:^1.6.0" concat-stream: "npm:^2.0.0" - mkdirp: "npm:^0.5.6" - object-assign: "npm:^4.1.1" type-is: "npm:^1.6.18" - xtend: "npm:^4.0.2" - checksum: 10/4bdcb07138cf72f93adc08a0dc27c058faab9f6721067a58f394fa546d73d11b7f100cdd66e733d649d184f9d1b402065f6888b31ec427409f056ee92c4367a6 + checksum: 10/fb22868caaed37d725715c14c60b740b81665265da3a026bb61954414f65b99f76b360128413b8a2a7cc1a95ecae28a42bf831fe172bb79682d19ec105b556bd languageName: node linkType: hard @@ -40017,6 +40494,13 @@ __metadata: languageName: node linkType: hard +"mute-stream@npm:^1.0.0": + version: 1.0.0 + resolution: "mute-stream@npm:1.0.0" + checksum: 10/36fc968b0e9c9c63029d4f9dc63911950a3bdf55c9a87f58d3a266289b67180201cade911e7699f8b2fa596b34c9db43dad37649e3f7fdd13c3bb9edb0017ee7 + languageName: node + linkType: hard + "mute-stream@npm:^2.0.0": version: 2.0.0 resolution: "mute-stream@npm:2.0.0" @@ -40025,8 +40509,8 @@ __metadata: linkType: hard "mysql2@npm:^3.0.0": - version: 3.17.3 - resolution: "mysql2@npm:3.17.3" + version: 3.19.0 + resolution: "mysql2@npm:3.19.0" dependencies: aws-ssl-profiles: "npm:^1.1.2" denque: "npm:^2.1.0" @@ -40036,7 +40520,9 @@ __metadata: lru.min: "npm:^1.1.4" named-placeholders: "npm:^1.1.6" sql-escaper: "npm:^1.3.3" - checksum: 10/8fdb862f4bb30124f72d88650e110be7a4261d2ff4989c1ec6d42a3cb6981992d8192b6c1069e183464a1cf6f6963f487d70c67733fd09e1f7d347d5e4a6ed45 + peerDependencies: + "@types/node": ">= 8" + checksum: 10/62d0ac029ed152647d48bebfeceb009acc493544e6358e9c3a0595bce03de764ca0902e3c94b4222d7d02ab40761c12856ff6e2a29c4f1ddc8865e8fbd49652c languageName: node linkType: hard @@ -40273,6 +40759,15 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^4.3.0": + version: 4.3.0 + resolution: "node-addon-api@npm:4.3.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10/d3b38d16cb9ad0714d965331d0e38cef1c27750c2c3343cd3464a9ed8158501a2910ccbf2fd9fdc476e806a19dbc9e0524ff9d66a7c779d42a9752a63ba30b80 + languageName: node + linkType: hard + "node-addon-api@npm:^8.0.0, node-addon-api@npm:^8.2.2, node-addon-api@npm:^8.3.0, node-addon-api@npm:^8.3.1": version: 8.5.0 resolution: "node-addon-api@npm:8.5.0" @@ -42132,13 +42627,13 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^2.0.0": - version: 2.0.0 - resolution: "path-scurry@npm:2.0.0" +"path-scurry@npm:^2.0.0, path-scurry@npm:^2.0.2": + version: 2.0.2 + resolution: "path-scurry@npm:2.0.2" dependencies: lru-cache: "npm:^11.0.0" minipass: "npm:^7.1.2" - checksum: 10/285ae0c2d6c34ae91dc1d5378ede21981c9a2f6de1ea9ca5a88b5a270ce9763b83dbadc7a324d512211d8d36b0c540427d3d0817030849d97a60fa840a2c59ec + checksum: 10/2b4257422bcb870a4c2d205b3acdbb213a72f5e2250f61c80f79c9d014d010f82bdf8584441612c8e1fa4eb098678f5704a66fa8377d72646bad4be38e57a2c3 languageName: node linkType: hard @@ -42270,10 +42765,10 @@ __metadata: languageName: node linkType: hard -"pg-connection-string@npm:^2.11.0, pg-connection-string@npm:^2.3.0, pg-connection-string@npm:^2.5.0": - version: 2.11.0 - resolution: "pg-connection-string@npm:2.11.0" - checksum: 10/0333bb1b7ddeac6fa5262920f82a983222c600d21ef14fdc5254b0d3cbb1763030d20c1e8e3c20fa767a6eda8f4b4773550954c06f3e072e5288b6fa9e9cae13 +"pg-connection-string@npm:^2.12.0, pg-connection-string@npm:^2.3.0, pg-connection-string@npm:^2.5.0": + version: 2.12.0 + resolution: "pg-connection-string@npm:2.12.0" + checksum: 10/03e5462c1f4da57166344a9eae105f95af6887a501dfe4ca2feb85ca8d351162afe9fc581cb27d44ecdaecc59a8bd55a1ec35b66714b01a9db148f0a91c0b36c languageName: node linkType: hard @@ -42291,19 +42786,19 @@ __metadata: languageName: node linkType: hard -"pg-pool@npm:^3.11.0": - version: 3.11.0 - resolution: "pg-pool@npm:3.11.0" +"pg-pool@npm:^3.13.0": + version: 3.13.0 + resolution: "pg-pool@npm:3.13.0" peerDependencies: pg: ">=8.0" - checksum: 10/51c77d99f17cf791333467352df8326e0f70f9c517eada65a5e7819b2422f6e655e52319f5406eb578504442ae5f399b6e1d023e41d0c199aaf82879a890db6d + checksum: 10/0addd11b3f3f49fb1d16bf181583411e8a9809730dbfc5edb9bbf5110bc02beb4ac346c52a121f446ff76bcd420a76a3409952ea743aa5f6a04705ea953bd8aa languageName: node linkType: hard -"pg-protocol@npm:*, pg-protocol@npm:^1.11.0": - version: 1.11.0 - resolution: "pg-protocol@npm:1.11.0" - checksum: 10/a70b1b4a3fc5b1be80dfdd65c829a149b8bd9df7488f9c47e0b51c9413aec5eb6da0a9ae9812891d74cd9f2ee90c0e391984a41b64603e7375fcbb9e07070b08 +"pg-protocol@npm:*, pg-protocol@npm:^1.13.0": + version: 1.13.0 + resolution: "pg-protocol@npm:1.13.0" + checksum: 10/302cd3920df00f178519693557c34949d64c8b3af7e2c12772b14f61547b947e4c761b4ca2319dbba5b0906207bb1b535cbfc7006d40d47fd823e277c2690a71 languageName: node linkType: hard @@ -42321,13 +42816,13 @@ __metadata: linkType: hard "pg@npm:^8.11.3, pg@npm:^8.9.0": - version: 8.18.0 - resolution: "pg@npm:8.18.0" + version: 8.20.0 + resolution: "pg@npm:8.20.0" dependencies: pg-cloudflare: "npm:^1.3.0" - pg-connection-string: "npm:^2.11.0" - pg-pool: "npm:^3.11.0" - pg-protocol: "npm:^1.11.0" + pg-connection-string: "npm:^2.12.0" + pg-pool: "npm:^3.13.0" + pg-protocol: "npm:^1.13.0" pg-types: "npm:2.2.0" pgpass: "npm:1.0.5" peerDependencies: @@ -42338,7 +42833,7 @@ __metadata: peerDependenciesMeta: pg-native: optional: true - checksum: 10/91c622f179f60df08ab7aa9b05a890567ea47f2d7984377b64e88e1eba1c42787324b7fc5ff00e109a757f3329dc4b57c73502603ae2765d1827b2082abbdcfa + checksum: 10/a30b99c799eddbd4f7f98bef906fe25e17c17d7d8918bc7545108947388df0d9c490858b04dabd37ce0063f40b1e58987b0079f99d5a661fb66f40c17f172bfc languageName: node linkType: hard @@ -43152,7 +43647,7 @@ __metadata: languageName: node linkType: hard -"prebuild-install@npm:^7.1.1, prebuild-install@npm:^7.1.3": +"prebuild-install@npm:^7.0.1, prebuild-install@npm:^7.1.1, prebuild-install@npm:^7.1.3": version: 7.1.3 resolution: "prebuild-install@npm:7.1.3" dependencies: @@ -43551,9 +44046,9 @@ __metadata: languageName: node linkType: hard -"protobufjs@npm:^7.0.0, protobufjs@npm:^7.2.5, protobufjs@npm:^7.2.6, protobufjs@npm:^7.3.0, protobufjs@npm:^7.3.2, protobufjs@npm:^7.4.0": - version: 7.5.3 - resolution: "protobufjs@npm:7.5.3" +"protobufjs@npm:^7.0.0, protobufjs@npm:^7.2.5, protobufjs@npm:^7.2.6, protobufjs@npm:^7.3.2, protobufjs@npm:^7.4.0, protobufjs@npm:^7.5.3": + version: 7.5.4 + resolution: "protobufjs@npm:7.5.4" dependencies: "@protobufjs/aspromise": "npm:^1.1.2" "@protobufjs/base64": "npm:^1.1.2" @@ -43567,7 +44062,7 @@ __metadata: "@protobufjs/utf8": "npm:^1.1.0" "@types/node": "npm:>=13.7.0" long: "npm:^5.0.0" - checksum: 10/3e412d2e2f799875dcdac1b43508f465a499dd3ffd9d24073669702589ef016529905617a12a967b1a8cfbe803a638b494cc3ed8db035605c7570d1a7fc094c9 + checksum: 10/88d677bb6f11a2ecec63fdd053dfe6d31120844d04e865efa9c8fbe0674cd077d6624ecfdf014018a20dcb114ae2a59c1b21966dd8073e920650c71370966439 languageName: node linkType: hard @@ -43649,7 +44144,7 @@ __metadata: languageName: node linkType: hard -"punycode@npm:^1.3.2, punycode@npm:^1.4.1": +"punycode@npm:^1.4.1": version: 1.4.1 resolution: "punycode@npm:1.4.1" checksum: 10/af2700dde1a116791ff8301348ff344c47d6c224e875057237d1b5112035655fb07a6175cfdb8bf0e3a8cdfd2dc82b3a622e0aefd605566c0e949a6d0d1256a4 @@ -43981,95 +44476,95 @@ __metadata: linkType: hard "react-aria-components@npm:^1.14.0": - version: 1.14.0 - resolution: "react-aria-components@npm:1.14.0" + version: 1.16.0 + resolution: "react-aria-components@npm:1.16.0" dependencies: - "@internationalized/date": "npm:^3.10.1" + "@internationalized/date": "npm:^3.12.0" "@internationalized/string": "npm:^3.2.7" - "@react-aria/autocomplete": "npm:3.0.0-rc.4" - "@react-aria/collections": "npm:^3.0.1" - "@react-aria/dnd": "npm:^3.11.4" - "@react-aria/focus": "npm:^3.21.3" - "@react-aria/interactions": "npm:^3.26.0" + "@react-aria/autocomplete": "npm:3.0.0-rc.6" + "@react-aria/collections": "npm:^3.0.3" + "@react-aria/dnd": "npm:^3.11.6" + "@react-aria/focus": "npm:^3.21.5" + "@react-aria/interactions": "npm:^3.27.1" "@react-aria/live-announcer": "npm:^3.4.4" - "@react-aria/overlays": "npm:^3.31.0" + "@react-aria/overlays": "npm:^3.31.2" "@react-aria/ssr": "npm:^3.9.10" - "@react-aria/textfield": "npm:^3.18.3" - "@react-aria/toolbar": "npm:3.0.0-beta.22" - "@react-aria/utils": "npm:^3.32.0" - "@react-aria/virtualizer": "npm:^4.1.11" + "@react-aria/textfield": "npm:^3.18.5" + "@react-aria/toolbar": "npm:3.0.0-beta.24" + "@react-aria/utils": "npm:^3.33.1" + "@react-aria/virtualizer": "npm:^4.1.13" "@react-stately/autocomplete": "npm:3.0.0-beta.4" - "@react-stately/layout": "npm:^4.5.2" - "@react-stately/selection": "npm:^3.20.7" - "@react-stately/table": "npm:^3.15.2" + "@react-stately/layout": "npm:^4.6.0" + "@react-stately/selection": "npm:^3.20.9" + "@react-stately/table": "npm:^3.15.4" "@react-stately/utils": "npm:^3.11.0" - "@react-stately/virtualizer": "npm:^4.4.4" - "@react-types/form": "npm:^3.7.16" - "@react-types/grid": "npm:^3.3.6" - "@react-types/shared": "npm:^3.32.1" - "@react-types/table": "npm:^3.13.4" + "@react-stately/virtualizer": "npm:^4.4.6" + "@react-types/form": "npm:^3.7.18" + "@react-types/grid": "npm:^3.3.8" + "@react-types/shared": "npm:^3.33.1" + "@react-types/table": "npm:^3.13.6" "@swc/helpers": "npm:^0.5.0" client-only: "npm:^0.0.1" - react-aria: "npm:^3.45.0" - react-stately: "npm:^3.43.0" - use-sync-external-store: "npm:^1.4.0" + react-aria: "npm:^3.47.0" + react-stately: "npm:^3.45.0" + use-sync-external-store: "npm:^1.6.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/cc842f03a6cb10a11ac6c95fbdcc97329a2bce7cf9203bd3fc9f4f4ea248bd308bc31b39f6df3c6b427be5adc370813e04b7a4cf41e8a0193195dde048462d9b + checksum: 10/091ff728a31b703e2a0c9218fab9486c797773054676380080b050c5af4afb614c9822c3cf08b7f932acec8eed4cd0b04fad7ac78cb782827e8352851dc50220 languageName: node linkType: hard -"react-aria@npm:^3.45.0": - version: 3.45.0 - resolution: "react-aria@npm:3.45.0" +"react-aria@npm:^3.47.0": + version: 3.47.0 + resolution: "react-aria@npm:3.47.0" dependencies: "@internationalized/string": "npm:^3.2.7" - "@react-aria/breadcrumbs": "npm:^3.5.30" - "@react-aria/button": "npm:^3.14.3" - "@react-aria/calendar": "npm:^3.9.3" - "@react-aria/checkbox": "npm:^3.16.3" - "@react-aria/color": "npm:^3.1.3" - "@react-aria/combobox": "npm:^3.14.1" - "@react-aria/datepicker": "npm:^3.15.3" - "@react-aria/dialog": "npm:^3.5.32" - "@react-aria/disclosure": "npm:^3.1.1" - "@react-aria/dnd": "npm:^3.11.4" - "@react-aria/focus": "npm:^3.21.3" - "@react-aria/gridlist": "npm:^3.14.2" - "@react-aria/i18n": "npm:^3.12.14" - "@react-aria/interactions": "npm:^3.26.0" - "@react-aria/label": "npm:^3.7.23" - "@react-aria/landmark": "npm:^3.0.8" - "@react-aria/link": "npm:^3.8.7" - "@react-aria/listbox": "npm:^3.15.1" - "@react-aria/menu": "npm:^3.19.4" - "@react-aria/meter": "npm:^3.4.28" - "@react-aria/numberfield": "npm:^3.12.3" - "@react-aria/overlays": "npm:^3.31.0" - "@react-aria/progress": "npm:^3.4.28" - "@react-aria/radio": "npm:^3.12.3" - "@react-aria/searchfield": "npm:^3.8.10" - "@react-aria/select": "npm:^3.17.1" - "@react-aria/selection": "npm:^3.27.0" - "@react-aria/separator": "npm:^3.4.14" - "@react-aria/slider": "npm:^3.8.3" + "@react-aria/breadcrumbs": "npm:^3.5.32" + "@react-aria/button": "npm:^3.14.5" + "@react-aria/calendar": "npm:^3.9.5" + "@react-aria/checkbox": "npm:^3.16.5" + "@react-aria/color": "npm:^3.1.5" + "@react-aria/combobox": "npm:^3.15.0" + "@react-aria/datepicker": "npm:^3.16.1" + "@react-aria/dialog": "npm:^3.5.34" + "@react-aria/disclosure": "npm:^3.1.3" + "@react-aria/dnd": "npm:^3.11.6" + "@react-aria/focus": "npm:^3.21.5" + "@react-aria/gridlist": "npm:^3.14.4" + "@react-aria/i18n": "npm:^3.12.16" + "@react-aria/interactions": "npm:^3.27.1" + "@react-aria/label": "npm:^3.7.25" + "@react-aria/landmark": "npm:^3.0.10" + "@react-aria/link": "npm:^3.8.9" + "@react-aria/listbox": "npm:^3.15.3" + "@react-aria/menu": "npm:^3.21.0" + "@react-aria/meter": "npm:^3.4.30" + "@react-aria/numberfield": "npm:^3.12.5" + "@react-aria/overlays": "npm:^3.31.2" + "@react-aria/progress": "npm:^3.4.30" + "@react-aria/radio": "npm:^3.12.5" + "@react-aria/searchfield": "npm:^3.8.12" + "@react-aria/select": "npm:^3.17.3" + "@react-aria/selection": "npm:^3.27.2" + "@react-aria/separator": "npm:^3.4.16" + "@react-aria/slider": "npm:^3.8.5" "@react-aria/ssr": "npm:^3.9.10" - "@react-aria/switch": "npm:^3.7.9" - "@react-aria/table": "npm:^3.17.9" - "@react-aria/tabs": "npm:^3.10.9" - "@react-aria/tag": "npm:^3.7.3" - "@react-aria/textfield": "npm:^3.18.3" - "@react-aria/toast": "npm:^3.0.9" - "@react-aria/tooltip": "npm:^3.9.0" - "@react-aria/tree": "npm:^3.1.5" - "@react-aria/utils": "npm:^3.32.0" - "@react-aria/visually-hidden": "npm:^3.8.29" - "@react-types/shared": "npm:^3.32.1" + "@react-aria/switch": "npm:^3.7.11" + "@react-aria/table": "npm:^3.17.11" + "@react-aria/tabs": "npm:^3.11.1" + "@react-aria/tag": "npm:^3.8.1" + "@react-aria/textfield": "npm:^3.18.5" + "@react-aria/toast": "npm:^3.0.11" + "@react-aria/tooltip": "npm:^3.9.2" + "@react-aria/tree": "npm:^3.1.7" + "@react-aria/utils": "npm:^3.33.1" + "@react-aria/visually-hidden": "npm:^3.8.31" + "@react-types/shared": "npm:^3.33.1" 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/9c674ccc8c07ab66333276edb64471e94cd80fefe8bc41b48be79034f1bba3d180037a8bd89847a60c90076168ab12870ca381bc61b1ca1f76167e54a985559c + checksum: 10/9afb9079939553d222c09630fd8e59949c586a716500a79298fff0f5c830a6f1c83dc017a74811c42f5606ddae4d1f4f1e724c803d70c073cd5598b5eb07140e languageName: node linkType: hard @@ -44103,6 +44598,15 @@ __metadata: languageName: node linkType: hard +"react-compiler-runtime@npm:19.1.0-rc.1": + version: 19.1.0-rc.1 + resolution: "react-compiler-runtime@npm:19.1.0-rc.1" + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 || ^0.0.0-experimental + checksum: 10/f64a63f8a6eacaebf87b2065dea9b2adc90d061c94361b31233284cbacc3fcf5f7dce6b5353ac8862ca5aa0a0d92240e3d6d695afa73d6bc5ce6d347b4c66870 + languageName: node + linkType: hard + "react-copy-to-clipboard@npm:5.1.0, react-copy-to-clipboard@npm:^5.0.4": version: 5.1.0 resolution: "react-copy-to-clipboard@npm:5.1.0" @@ -44333,11 +44837,11 @@ __metadata: linkType: hard "react-hook-form@npm:^7.12.2": - version: 7.71.1 - resolution: "react-hook-form@npm:7.71.1" + version: 7.71.2 + resolution: "react-hook-form@npm:7.71.2" peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - checksum: 10/54b56aa52a6ac17fb5aa68ce072b08f50b75c2705bf878069f817c346e464c1ebfbf67be372d94cf8e620fa07ca8e64ffca71bb69071dd0290a1ef8f43eefb9d + checksum: 10/4cc90868016f8463463ea5d1812f9405832e02fdb12f18ff0262c6437e7a9cdfe6443a7f58decf903cb8f20bfd68c0ed419283b0d6be886f3e84dc4d14b0efa6 languageName: node linkType: hard @@ -44527,38 +45031,38 @@ __metadata: languageName: node linkType: hard -"react-remove-scroll-bar@npm:^2.3.3": - version: 2.3.4 - resolution: "react-remove-scroll-bar@npm:2.3.4" +"react-remove-scroll-bar@npm:^2.3.7": + version: 2.3.8 + resolution: "react-remove-scroll-bar@npm:2.3.8" dependencies: - react-style-singleton: "npm:^2.2.1" + react-style-singleton: "npm:^2.2.2" tslib: "npm:^2.0.0" peerDependencies: - "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10/ac028b3ed12e66972cab8656747736729b219dff5a600178d1650300a2a750ace37f7ec82146147d37b092b19874f45cf7a45edceff68ac1f59607a828ca089f + checksum: 10/6c0f8cff98b9f49a4ee2263f1eedf12926dced5ce220fbe83bd93544460e2a7ec8ec39b35d1b2a75d2fced0b2d64afeb8e66f830431ca896e05a20585f9fc350 languageName: node linkType: hard -"react-remove-scroll@npm:2.5.5": - version: 2.5.5 - resolution: "react-remove-scroll@npm:2.5.5" +"react-remove-scroll@npm:^2.6.3": + version: 2.7.2 + resolution: "react-remove-scroll@npm:2.7.2" dependencies: - react-remove-scroll-bar: "npm:^2.3.3" - react-style-singleton: "npm:^2.2.1" + react-remove-scroll-bar: "npm:^2.3.7" + react-style-singleton: "npm:^2.2.3" tslib: "npm:^2.1.0" - use-callback-ref: "npm:^1.3.0" - use-sidecar: "npm:^1.1.2" + use-callback-ref: "npm:^1.3.3" + use-sidecar: "npm:^1.1.3" peerDependencies: - "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/f0646ac384ce3852d1f41e30a9f9e251b11cf3b430d1d114c937c8fa7f90a895c06378d0d6b6ff0b2d00cbccf15e845921944fd6074ae67a0fb347a718106d88 + checksum: 10/6a7858f9a3eb57128abb64479ced78283b0cbee0b43c2b87e42711e75c564c27d1d3dd2b81f2ea95c80cb9e6b6965d1c9e2332f6a7e7b753cd8aeb02b9b97704 languageName: node linkType: hard @@ -44680,56 +45184,55 @@ __metadata: languageName: node linkType: hard -"react-stately@npm:^3.43.0": - version: 3.43.0 - resolution: "react-stately@npm:3.43.0" +"react-stately@npm:^3.45.0": + version: 3.45.0 + resolution: "react-stately@npm:3.45.0" dependencies: - "@react-stately/calendar": "npm:^3.9.1" - "@react-stately/checkbox": "npm:^3.7.3" - "@react-stately/collections": "npm:^3.12.8" - "@react-stately/color": "npm:^3.9.3" - "@react-stately/combobox": "npm:^3.12.1" - "@react-stately/data": "npm:^3.15.0" - "@react-stately/datepicker": "npm:^3.15.3" - "@react-stately/disclosure": "npm:^3.0.9" - "@react-stately/dnd": "npm:^3.7.2" - "@react-stately/form": "npm:^3.2.2" - "@react-stately/list": "npm:^3.13.2" - "@react-stately/menu": "npm:^3.9.9" - "@react-stately/numberfield": "npm:^3.10.3" - "@react-stately/overlays": "npm:^3.6.21" - "@react-stately/radio": "npm:^3.11.3" - "@react-stately/searchfield": "npm:^3.5.17" - "@react-stately/select": "npm:^3.9.0" - "@react-stately/selection": "npm:^3.20.7" - "@react-stately/slider": "npm:^3.7.3" - "@react-stately/table": "npm:^3.15.2" - "@react-stately/tabs": "npm:^3.8.7" - "@react-stately/toast": "npm:^3.1.2" - "@react-stately/toggle": "npm:^3.9.3" - "@react-stately/tooltip": "npm:^3.5.9" - "@react-stately/tree": "npm:^3.9.4" - "@react-types/shared": "npm:^3.32.1" + "@react-stately/calendar": "npm:^3.9.3" + "@react-stately/checkbox": "npm:^3.7.5" + "@react-stately/collections": "npm:^3.12.10" + "@react-stately/color": "npm:^3.9.5" + "@react-stately/combobox": "npm:^3.13.0" + "@react-stately/data": "npm:^3.15.2" + "@react-stately/datepicker": "npm:^3.16.1" + "@react-stately/disclosure": "npm:^3.0.11" + "@react-stately/dnd": "npm:^3.7.4" + "@react-stately/form": "npm:^3.2.4" + "@react-stately/list": "npm:^3.13.4" + "@react-stately/menu": "npm:^3.9.11" + "@react-stately/numberfield": "npm:^3.11.0" + "@react-stately/overlays": "npm:^3.6.23" + "@react-stately/radio": "npm:^3.11.5" + "@react-stately/searchfield": "npm:^3.5.19" + "@react-stately/select": "npm:^3.9.2" + "@react-stately/selection": "npm:^3.20.9" + "@react-stately/slider": "npm:^3.7.5" + "@react-stately/table": "npm:^3.15.4" + "@react-stately/tabs": "npm:^3.8.9" + "@react-stately/toast": "npm:^3.1.3" + "@react-stately/toggle": "npm:^3.9.5" + "@react-stately/tooltip": "npm:^3.5.11" + "@react-stately/tree": "npm:^3.9.6" + "@react-types/shared": "npm:^3.33.1" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/a82d9ba6fdb9a922cb0820de2678be548fa08fc011a9b0be35f3a0022049219810ce1696b7b9104db907b3103233143801649b584d8a06fec582dfb78c81dcf9 + checksum: 10/5a34d8832b3867e06582bba57dbfd5dfbacd2ee7489ab8fa21ff25d6f0716e9fbf018d2390d5c5af62b99d53f3100c703b372234b0421934a3910e3160ec8d4a languageName: node linkType: hard -"react-style-singleton@npm:^2.2.1": - version: 2.2.1 - resolution: "react-style-singleton@npm:2.2.1" +"react-style-singleton@npm:^2.2.2, react-style-singleton@npm:^2.2.3": + version: 2.2.3 + resolution: "react-style-singleton@npm:2.2.3" dependencies: get-nonce: "npm:^1.0.0" - invariant: "npm:^2.2.4" tslib: "npm:^2.0.0" peerDependencies: - "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/80c58fd6aac3594e351e2e7b048d8a5b09508adb21031a38b3c40911fe58295572eddc640d4b20a7be364842c8ed1120fe30097e22ea055316b375b88d4ff02a + checksum: 10/62498094ff3877a37f351b29e6cad9e38b2eb1ac3c0cb27ebf80aee96554f80b35e17bdb552bcd7ac8b7cb9904fea93ea5668f2057c73d38f90b5d46bb9b27ab languageName: node linkType: hard @@ -45936,7 +46439,7 @@ __metadata: "@storybook/addon-a11y": "npm:^10.3.0-alpha.1" "@storybook/addon-docs": "npm:^10.3.0-alpha.1" "@storybook/addon-links": "npm:^10.3.0-alpha.1" - "@storybook/addon-mcp": "npm:^0.2.2" + "@storybook/addon-mcp": "npm:^0.3.0" "@storybook/addon-themes": "npm:^10.3.0-alpha.1" "@storybook/addon-vitest": "npm:^10.3.0-alpha.1" "@storybook/react-vite": "npm:^10.3.0-alpha.1" @@ -46034,6 +46537,13 @@ __metadata: languageName: node linkType: hard +"run-async@npm:^3.0.0": + version: 3.0.0 + resolution: "run-async@npm:3.0.0" + checksum: 10/97fb8747f7765b77ebcd311d3a33548099336f04c6434e0763039b98c1de0f1b4421000695aff8751f309c0b995d8dfd620c1f1e4c35572da38c101488165305 + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -46174,6 +46684,13 @@ __metadata: languageName: node linkType: hard +"sax@npm:^1.5.0": + version: 1.5.0 + resolution: "sax@npm:1.5.0" + checksum: 10/9012ff37dda7a7ac5da45db2143b04036103e8bef8d586c3023afd5df6caf0ebd7f38017eee344ad2e2247eded7d38e9c42cf291d8dd91781352900ac0fd2d9f + languageName: node + linkType: hard + "saxes@npm:^6.0.0": version: 6.0.0 resolution: "saxes@npm:6.0.0" @@ -46466,27 +46983,18 @@ __metadata: languageName: node linkType: hard -"serialize-javascript@npm:^6.0.2": - version: 6.0.2 - resolution: "serialize-javascript@npm:6.0.2" - dependencies: - randombytes: "npm:^2.1.0" - checksum: 10/445a420a6fa2eaee4b70cbd884d538e259ab278200a2ededd73253ada17d5d48e91fb1f4cd224a236ab62ea7ba0a70c6af29fc93b4f3d3078bf7da1c031fde58 - languageName: node - linkType: hard - "serve-handler@npm:^6.1.3": - version: 6.1.6 - resolution: "serve-handler@npm:6.1.6" + version: 6.1.7 + resolution: "serve-handler@npm:6.1.7" dependencies: bytes: "npm:3.0.0" content-disposition: "npm:0.5.2" mime-types: "npm:2.1.18" - minimatch: "npm:3.1.2" + minimatch: "npm:3.1.5" path-is-inside: "npm:1.0.2" path-to-regexp: "npm:3.3.0" range-parser: "npm:1.2.0" - checksum: 10/7e7d93eb7e69fcd9f9c5afc2ef2b46cb0072b4af13cbabef9bca725afb350ddae6857d8c8be2c256f7ce1f7677c20347801399c11caa5805c0090339f894e8f2 + checksum: 10/2366e53cc8e8376d58abb289293b930111fa5da6d14bb31eafac5b1162f332c45c6f394c7d78fdcf6b5736e12caf9370b02d05c7e8a75291d2fc6a55b52b14ea languageName: node linkType: hard @@ -47442,12 +47950,12 @@ __metadata: linkType: hard "storybook@npm:^10.3.0-alpha.1": - version: 10.3.0-alpha.1 - resolution: "storybook@npm:10.3.0-alpha.1" + version: 10.3.0-alpha.14 + resolution: "storybook@npm:10.3.0-alpha.14" dependencies: "@storybook/global": "npm:^5.0.0" "@storybook/icons": "npm:^2.0.1" - "@testing-library/jest-dom": "npm:^6.6.3" + "@testing-library/jest-dom": "npm:^6.9.1" "@testing-library/user-event": "npm:^14.6.1" "@vitest/expect": "npm:3.2.4" "@vitest/spy": "npm:3.2.4" @@ -47464,7 +47972,7 @@ __metadata: optional: true bin: storybook: ./dist/bin/dispatcher.js - checksum: 10/9b76b96109cd9b2b9ff9e50d05dfc211ccf076c1bf1293e47a3f3b65fdc543a37b2ae4d3088edb9e10af5b7a320e9cadde7dc8945fe0ad5f9cd61b779b3e19b0 + checksum: 10/f19d870da20440976de997117839178aa6d169b441dbf27d600f0cdfb1f1bb3730fe0f0a4f665fae59662664b53dd6fef0bed5e6e35c10de89c903b45e0ac43f languageName: node linkType: hard @@ -47800,11 +48308,11 @@ __metadata: linkType: hard "strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0": - version: 7.1.2 - resolution: "strip-ansi@npm:7.1.2" + version: 7.2.0 + resolution: "strip-ansi@npm:7.2.0" dependencies: - ansi-regex: "npm:^6.0.1" - checksum: 10/db0e3f9654e519c8a33c50fc9304d07df5649388e7da06d3aabf66d29e5ad65d5e6315d8519d409c15b32fa82c1df7e11ed6f8cd50b0e4404463f0c9d77c8d0b + ansi-regex: "npm:^6.2.2" + checksum: 10/96da3bc6d73cfba1218625a3d66cf7d37a69bf0920d8735b28f9eeaafcdb6c1fe8440e1ae9eb1ba0ca355dbe8702da872e105e2e939fa93e7851b3cb5dd7d316 languageName: node linkType: hard @@ -48141,33 +48649,34 @@ __metadata: linkType: hard "svgo@npm:^2.7.0": - version: 2.8.0 - resolution: "svgo@npm:2.8.0" + version: 2.8.2 + resolution: "svgo@npm:2.8.2" dependencies: - "@trysound/sax": "npm:0.2.0" commander: "npm:^7.2.0" css-select: "npm:^4.1.3" css-tree: "npm:^1.1.3" csso: "npm:^4.2.0" picocolors: "npm:^1.0.0" + sax: "npm:^1.5.0" stable: "npm:^0.1.8" bin: - svgo: bin/svgo - checksum: 10/2b74544da1a9521852fe2784252d6083b336e32528d0e424ee54d1613f17312edc7020c29fa399086560e96cba42ede4a2205328a08edeefa26de84cd769a64a + svgo: ./bin/svgo + checksum: 10/a0922a2cbbbc51c0162ea7a7d6c5b660fb4fb65e0f05e226ba571cfe8b651fd870072aed2722ef96715fb77829562b351eb72f2b7bf09038ffc88969acaffd0f languageName: node linkType: hard -"swagger-client@npm:^3.36.1": - version: 3.36.1 - resolution: "swagger-client@npm:3.36.1" +"swagger-client@npm:^3.37.0": + version: 3.37.0 + resolution: "swagger-client@npm:3.37.0" dependencies: "@babel/runtime-corejs3": "npm:^7.22.15" "@scarf/scarf": "npm:=1.4.0" - "@swagger-api/apidom-core": "npm:^1.3.0" - "@swagger-api/apidom-error": "npm:^1.3.0" - "@swagger-api/apidom-json-pointer": "npm:^1.3.0" - "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.3.0" - "@swagger-api/apidom-reference": "npm:^1.3.0" + "@swagger-api/apidom-core": "npm:^1.6.0" + "@swagger-api/apidom-error": "npm:^1.6.0" + "@swagger-api/apidom-json-pointer": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.6.0" + "@swagger-api/apidom-ns-openapi-3-2": "npm:^1.6.0" + "@swagger-api/apidom-reference": "npm:^1.6.0" "@swaggerexpert/cookie": "npm:^2.0.2" deepmerge: "npm:~4.3.0" fast-json-patch: "npm:^3.0.0-1" @@ -48179,13 +48688,13 @@ __metadata: openapi-server-url-templating: "npm:^1.3.0" ramda: "npm:^0.30.1" ramda-adjunct: "npm:^5.1.0" - checksum: 10/849cb6c2248c4f0a378701896ae70703997c1915888b76dd5e6cf2c2d43b51e929fa7d9273c635a46a3b0cd2a9eb265f4e130dd4538b4a2cf3ddaebe0650a4ca + checksum: 10/f85d56c07191e26f3ca928d610efa813e5f9fa447d4732bc737fcab1c1b744047bf8baf83d094ad044d54872f99e2f6eceb94955fcecdecabc3844df35822227 languageName: node linkType: hard "swagger-ui-react@npm:^5.27.1": - version: 5.31.1 - resolution: "swagger-ui-react@npm:5.31.1" + version: 5.32.0 + resolution: "swagger-ui-react@npm:5.32.0" dependencies: "@babel/runtime-corejs3": "npm:^7.27.1" "@scarf/scarf": "npm:=1.4.0" @@ -48216,7 +48725,7 @@ __metadata: reselect: "npm:^5.1.1" serialize-error: "npm:^8.1.0" sha.js: "npm:^2.4.12" - swagger-client: "npm:^3.36.1" + swagger-client: "npm:^3.37.0" url-parse: "npm:^1.5.10" xml: "npm:=1.0.1" xml-but-prettier: "npm:^1.0.1" @@ -48224,31 +48733,31 @@ __metadata: peerDependencies: react: ">=16.8.0 <20" react-dom: ">=16.8.0 <20" - checksum: 10/cfe093e664d0d888374d004e13e803009208cd3ee48ff5d8f42ae6535728b42f39bbd28dac11934491aa4ca85426c0d20847dcd93a172c63ead026cd6682a57c + checksum: 10/7ac4481d452a54f1b656719be5cea4717afdee45b7367c6086eb278742d8cc331ad393db5668921c02934c9b95ff3d8ba6bb5f86101f5f9f593f84d798bc838b languageName: node linkType: hard "swc-loader@npm:^0.2.3": - version: 0.2.6 - resolution: "swc-loader@npm:0.2.6" + version: 0.2.7 + resolution: "swc-loader@npm:0.2.7" dependencies: "@swc/counter": "npm:^0.1.3" peerDependencies: "@swc/core": ^1.2.147 webpack: ">=2" - checksum: 10/fe90948c02a51bb8ffcff1ce3590e01dc12860b0bb7c9e22052b14fa846ed437781ae265614a5e14344bea22001108780f00a6e350e28c0b3499bc4cd11335fb + checksum: 10/15cbc3769209f7e2f927e49c19a5ef30fe03663bd34688289c003d966c7dfe782eec39de77c454aa7465ce84138d8bc31257b736b32ff3be10191f1de88cbbf5 languageName: node linkType: hard "swr@npm:^2.0.0, swr@npm:^2.2.5": - version: 2.4.0 - resolution: "swr@npm:2.4.0" + version: 2.4.1 + resolution: "swr@npm:2.4.1" dependencies: dequal: "npm:^2.0.3" use-sync-external-store: "npm:^1.6.0" peerDependencies: react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10/3536db25748b49659d87450ed40e391b0a8c2cfe387e41224ee505a18ae739477bd60caba1d8ff7bd961db923cf3d6771e4aee13de822855436a7bca9e343ccc + checksum: 10/611c0a39d42abb7d7eae37eb36e6a30a8a2c387bab6026d8db13ae41b9f8f3c2a187486008781de3f083ef5b39e6d90af1f275a73ade882a567e338166266356 languageName: node linkType: hard @@ -48266,6 +48775,17 @@ __metadata: languageName: node linkType: hard +"sync-fetch@npm:0.6.0": + version: 0.6.0 + resolution: "sync-fetch@npm:0.6.0" + dependencies: + node-fetch: "npm:^3.3.2" + timeout-signal: "npm:^2.0.0" + whatwg-mimetype: "npm:^4.0.0" + checksum: 10/524f4d45e903d208351d9bc43802002394cbf5e12699fb57471594f433cef5dfaf0c41e7f3484255865683f64125aeb6c279d264be123fc93bb9c6ae3f064bb4 + languageName: node + linkType: hard + "synckit@npm:^0.11.8": version: 0.11.11 resolution: "synckit@npm:0.11.11" @@ -48473,14 +48993,13 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:*, terser-webpack-plugin@npm:^5.1.3, terser-webpack-plugin@npm:^5.3.16": - version: 5.3.16 - resolution: "terser-webpack-plugin@npm:5.3.16" +"terser-webpack-plugin@npm:*, terser-webpack-plugin@npm:^5.1.3, terser-webpack-plugin@npm:^5.3.17": + version: 5.3.17 + resolution: "terser-webpack-plugin@npm:5.3.17" dependencies: "@jridgewell/trace-mapping": "npm:^0.3.25" jest-worker: "npm:^27.4.5" schema-utils: "npm:^4.3.0" - serialize-javascript: "npm:^6.0.2" terser: "npm:^5.31.1" peerDependencies: webpack: ^5.1.0 @@ -48491,7 +49010,7 @@ __metadata: optional: true uglify-js: optional: true - checksum: 10/09dfbff602acfa114cdd174254b69a04adbc47856021ab351e37982202fd1ec85e0b62ffd5864c98beb8e96aef2f43da490b3448b4541db539c2cff6607394a6 + checksum: 10/e51b00fe5e54beff82e8c2bea72b5104a2608e375d612ee56887e521210f840f2297fda2e0778c26bbd726b3c77ba2ec3234b2bdaa20b2f9174733745a457b33 languageName: node linkType: hard @@ -48644,6 +49163,13 @@ __metadata: languageName: node linkType: hard +"timeout-signal@npm:^2.0.0": + version: 2.0.0 + resolution: "timeout-signal@npm:2.0.0" + checksum: 10/5f022c225bac6542716478edf7bb5fc871d985cfa398b4f2eaa3d13fa6fda1225ce77cc65c5a92ae23b58882e2c14b83532a7c6f2f7710d06c9605b48ece4fe2 + languageName: node + linkType: hard + "timers-browserify@npm:^2.0.4": version: 2.0.11 resolution: "timers-browserify@npm:2.0.11" @@ -49028,15 +49554,12 @@ __metadata: linkType: hard "ts-checker-rspack-plugin@npm:^1.1.5": - version: 1.2.6 - resolution: "ts-checker-rspack-plugin@npm:1.2.6" + version: 1.3.0 + resolution: "ts-checker-rspack-plugin@npm:1.3.0" dependencies: - "@babel/code-frame": "npm:^7.27.1" "@rspack/lite-tapable": "npm:^1.1.0" chokidar: "npm:^3.6.0" - is-glob: "npm:^4.0.3" - memfs: "npm:^4.51.1" - minimatch: "npm:^9.0.5" + memfs: "npm:^4.56.10" picocolors: "npm:^1.1.1" peerDependencies: "@rspack/core": ^1.0.0 || ^2.0.0-0 @@ -49044,7 +49567,7 @@ __metadata: peerDependenciesMeta: "@rspack/core": optional: true - checksum: 10/fc7af29af284caed8adedb766bbd289168f8ec0f45bbc8add9ba15900d143b8149d9ca197d7747bc54a79a052b7a24cb2c447141c8eab16ec4a80bc475d5e75a + checksum: 10/dfd19f2c210b8c6ee2255a6a6c7db30bcff08cb4664982f3c3edfc21ee78017b25ad1ce074e1a5ee9f7551291d4302debc53856d24dfcef1ca26209a9d997c18 languageName: node linkType: hard @@ -49332,10 +49855,10 @@ __metadata: languageName: node linkType: hard -"type-flag@npm:^4.0.3": - version: 4.0.3 - resolution: "type-flag@npm:4.0.3" - checksum: 10/ffa9e56e50ed73ed802aa45b8d47eeba7677d2d66785e9153bfbc758c2613b6ba78b621d7766b713a287dbbe6758a61000f57d2ce8afc94e1876a37de914c73c +"type-flag@npm:^4.1.0": + version: 4.1.0 + resolution: "type-flag@npm:4.1.0" + checksum: 10/d3af106dab651751426f778095b3963791d15482b2154f92f4f98e5d6b72d593ae9873120d2de24f34191ab42c0d71c0259743162e543aee8d55b528ed1c6e78 languageName: node linkType: hard @@ -49595,13 +50118,6 @@ __metadata: languageName: node linkType: hard -"uc.micro@npm:^1.0.1, uc.micro@npm:^1.0.5": - version: 1.0.6 - resolution: "uc.micro@npm:1.0.6" - checksum: 10/6898bb556319a38e9cf175e3628689347bd26fec15fc6b29fa38e0045af63075ff3fea4cf1fdba9db46c9f0cbf07f2348cd8844889dd31ebd288c29fe0d27e7a - languageName: node - linkType: hard - "uc.micro@npm:^2.0.0, uc.micro@npm:^2.1.0": version: 2.1.0 resolution: "uc.micro@npm:2.1.0" @@ -49677,9 +50193,9 @@ __metadata: linkType: hard "underscore@npm:^1.12.1, underscore@npm:^1.13.3": - version: 1.13.7 - resolution: "underscore@npm:1.13.7" - checksum: 10/1ce3368dbe73d1e99678fa5d341a9682bd27316032ad2de7883901918f0f5d50e80320ccc543f53c1862ab057a818abc560462b5f83578afe2dd8dd7f779766c + version: 1.13.8 + resolution: "underscore@npm:1.13.8" + checksum: 10/b50ac5806d059cc180b1bd9adea6f7ed500021f4dc782dfc75d66a90337f6f0506623c1b37863f4a9bf64ffbeb5769b638a54b7f2f5966816189955815953139 languageName: node linkType: hard @@ -50088,6 +50604,15 @@ __metadata: languageName: node linkType: hard +"upper-case@npm:^2.0.2": + version: 2.0.2 + resolution: "upper-case@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/508723a2b03ab90cf1d6b7e0397513980fab821cbe79c87341d0e96cedefadf0d85f9d71eac24ab23f526a041d585a575cfca120a9f920e44eb4f8a7cf89121c + languageName: node + linkType: hard + "uri-js@npm:^4.2.2, uri-js@npm:^4.4.1": version: 4.4.1 resolution: "uri-js@npm:4.4.1" @@ -50182,6 +50707,13 @@ __metadata: languageName: node linkType: hard +"urlpattern-polyfill@npm:^10.0.0": + version: 10.1.0 + resolution: "urlpattern-polyfill@npm:10.1.0" + checksum: 10/a3a5cb577f40e9d14a2bc0e23e540d088ec8c18e52932337c2499a8118f32aa8fa32578f0cddaa189de671964b0f33ba7e008d0784b99804a09a9ff924f87f5a + languageName: node + linkType: hard + "urlpattern-polyfill@npm:^8.0.0": version: 8.0.2 resolution: "urlpattern-polyfill@npm:8.0.2" @@ -50189,25 +50721,18 @@ __metadata: languageName: node linkType: hard -"urlpattern-polyfill@npm:^9.0.0": - version: 9.0.0 - resolution: "urlpattern-polyfill@npm:9.0.0" - checksum: 10/63d59e08d58189d340e3acb0fb69c11d8f06da5e38c091cdac66cac07e4ca81378ad19cd1a923d5593a899603a0e607fe3ef793ef368fefbc1b2b840b24839b8 - languageName: node - linkType: hard - -"use-callback-ref@npm:^1.3.0": - version: 1.3.0 - resolution: "use-callback-ref@npm:1.3.0" +"use-callback-ref@npm:^1.3.3": + version: 1.3.3 + resolution: "use-callback-ref@npm:1.3.3" dependencies: tslib: "npm:^2.0.0" peerDependencies: - "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/f9f1b217db60419b033228ba17cee5c521123e7c7f35577258a1abdce6d9623e5880f0bed3a0504eff35fdf6c761a2b2e020337a34218fb86229b8641772654a + checksum: 10/adf06a7b6a27d3651c325ac9b66d2b82ccacaed7450b85b211d123e91d9a23cb5a587fcc6db5b4fd07ac7233e5abf024d30cf02ddc2ec46bca712151c0836151 languageName: node linkType: hard @@ -50277,19 +50802,19 @@ __metadata: languageName: node linkType: hard -"use-sidecar@npm:^1.1.2": - version: 1.1.2 - resolution: "use-sidecar@npm:1.1.2" +"use-sidecar@npm:^1.1.3": + version: 1.1.3 + resolution: "use-sidecar@npm:1.1.3" dependencies: detect-node-es: "npm:^1.1.0" tslib: "npm:^2.0.0" peerDependencies: - "@types/react": ^16.9.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: "@types/react": optional: true - checksum: 10/ec99e31aefeb880f6dc4d02cb19a01d123364954f857811470ece32872f70d6c3eadbe4d073770706a9b7db6136f2a9fbf1bb803e07fbb21e936a47479281690 + checksum: 10/2fec05eb851cdfc4a4657b1dfb434e686f346c3265ffc9db8a974bb58f8128bd4a708a3cc00e8f51655fccf81822ed4419ebed42f41610589e3aab0cf2492edb languageName: node linkType: hard @@ -50495,13 +51020,6 @@ __metadata: languageName: node linkType: hard -"value-or-promise@npm:^1.0.11, value-or-promise@npm:^1.0.12": - version: 1.0.12 - resolution: "value-or-promise@npm:1.0.12" - checksum: 10/a4cc31fc9c3826b8a216ef2037b676904324c00c4acd903aaec2fe0c08516a189345261dd3cc822ec108532b2ea36b7c99bbdee1c3ddcb7f4b3d57d7e61b2064 - languageName: node - linkType: hard - "vary@npm:^1, vary@npm:^1.1.2, vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" @@ -50866,10 +51384,10 @@ __metadata: languageName: node linkType: hard -"webpack-sources@npm:^3.3.3": - version: 3.3.3 - resolution: "webpack-sources@npm:3.3.3" - checksum: 10/ec5d72607e8068467370abccbfff855c596c098baedbe9d198a557ccf198e8546a322836a6f74241492576adba06100286592993a62b63196832cdb53c8bae91 +"webpack-sources@npm:^3.3.4": + version: 3.3.4 + resolution: "webpack-sources@npm:3.3.4" + checksum: 10/714427b235b04c2d7cf229f204b9e65145ea3643da3c7b139ebfa8a51056238d1e3a2a47c3cc3fc8eab71ed4300f66405cdc7cff29cd2f7f6b71086252f81cf1 languageName: node linkType: hard @@ -50881,8 +51399,8 @@ __metadata: linkType: hard "webpack@npm:^5, webpack@npm:~5.105.0": - version: 5.105.2 - resolution: "webpack@npm:5.105.2" + version: 5.105.4 + resolution: "webpack@npm:5.105.4" dependencies: "@types/eslint-scope": "npm:^3.7.7" "@types/estree": "npm:^1.0.8" @@ -50890,11 +51408,11 @@ __metadata: "@webassemblyjs/ast": "npm:^1.14.1" "@webassemblyjs/wasm-edit": "npm:^1.14.1" "@webassemblyjs/wasm-parser": "npm:^1.14.1" - acorn: "npm:^8.15.0" + acorn: "npm:^8.16.0" acorn-import-phases: "npm:^1.0.3" browserslist: "npm:^4.28.1" chrome-trace-event: "npm:^1.0.2" - enhanced-resolve: "npm:^5.19.0" + enhanced-resolve: "npm:^5.20.0" es-module-lexer: "npm:^2.0.0" eslint-scope: "npm:5.1.1" events: "npm:^3.2.0" @@ -50906,15 +51424,15 @@ __metadata: neo-async: "npm:^2.6.2" schema-utils: "npm:^4.3.3" tapable: "npm:^2.3.0" - terser-webpack-plugin: "npm:^5.3.16" + terser-webpack-plugin: "npm:^5.3.17" watchpack: "npm:^2.5.1" - webpack-sources: "npm:^3.3.3" + webpack-sources: "npm:^3.3.4" peerDependenciesMeta: webpack-cli: optional: true bin: webpack: bin/webpack.js - checksum: 10/77cf6f13e55cae2fafae015cc4a0b341b096c284877244ef4b46b4de1884dcf4e8d1a166a91f0a7cb6db41e9de2d0facb5caf518cbd4cd7ba963c59c07ff0a5a + checksum: 10/ae8088dd1c995fa17b920009f864138297a9ea5089bc563601f661fa4a31bb24b000cc91ae122168ce9def79c49258b8aa1021c2754c3555205c29a0d6c9cc8d languageName: node linkType: hard @@ -51251,7 +51769,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:*, ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.17.1, ws@npm:^8.18.0, ws@npm:^8.18.2, ws@npm:^8.18.3, ws@npm:^8.8.0": +"ws@npm:*, ws@npm:^8.18.0, ws@npm:^8.18.2, ws@npm:^8.18.3, ws@npm:^8.19.0, ws@npm:^8.8.0": version: 8.19.0 resolution: "ws@npm:8.19.0" peerDependencies: