diff --git a/.changeset/every-ties-wink.md b/.changeset/every-ties-wink.md new file mode 100644 index 0000000000..31eff3fb9a --- /dev/null +++ b/.changeset/every-ties-wink.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fixed an issue causing the `repo lint` command to fail when the `--max-warnings` option was used. diff --git a/.changeset/funny-hotels-cut.md b/.changeset/funny-hotels-cut.md new file mode 100644 index 0000000000..7972b68bbb --- /dev/null +++ b/.changeset/funny-hotels-cut.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-sentry': patch +--- + +Made "sentry:project:create" action idempotent diff --git a/.changeset/giant-donkeys-punch.md b/.changeset/giant-donkeys-punch.md new file mode 100644 index 0000000000..522055832a --- /dev/null +++ b/.changeset/giant-donkeys-punch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-azure': patch +--- + +Made "publish:azure" action idempotent diff --git a/.changeset/honest-ties-worry.md b/.changeset/honest-ties-worry.md new file mode 100644 index 0000000000..74d8c591c1 --- /dev/null +++ b/.changeset/honest-ties-worry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Scaffolding - Template card - button to show template entity detail diff --git a/.changeset/itchy-times-behave.md b/.changeset/itchy-times-behave.md new file mode 100644 index 0000000000..dc883487b0 --- /dev/null +++ b/.changeset/itchy-times-behave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket': patch +--- + +Made "publish:bitbucket" action idempotent diff --git a/.changeset/large-kings-protect.md b/.changeset/large-kings-protect.md new file mode 100644 index 0000000000..363e2b7759 --- /dev/null +++ b/.changeset/large-kings-protect.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +Added `validateLocationsExist` to the config definition where it was missing. diff --git a/.changeset/metal-animals-notice.md b/.changeset/metal-animals-notice.md new file mode 100644 index 0000000000..c1a844ddb0 --- /dev/null +++ b/.changeset/metal-animals-notice.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +Made "github:autolinks:create" action idempotent diff --git a/.changeset/odd-bobcats-hang.md b/.changeset/odd-bobcats-hang.md new file mode 100644 index 0000000000..9b3aeccffc --- /dev/null +++ b/.changeset/odd-bobcats-hang.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-scaffolder-node': patch +--- + +Document the internal built-in filters, and ensure that the types are validated when using `createTemplateFilter` and `createTemplateGlobalFunction` from the `zod` schema. diff --git a/.changeset/sad-pots-try.md b/.changeset/sad-pots-try.md new file mode 100644 index 0000000000..68c442fc6e --- /dev/null +++ b/.changeset/sad-pots-try.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Add `operation` to alpha `defaultEntityContentGroups`. diff --git a/.changeset/silver-rocks-invite.md b/.changeset/silver-rocks-invite.md new file mode 100644 index 0000000000..3a08bc72af --- /dev/null +++ b/.changeset/silver-rocks-invite.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +--- + +Made "publish:bitbucketCloud" action idempotent diff --git a/.changeset/spotty-towns-show.md b/.changeset/spotty-towns-show.md new file mode 100644 index 0000000000..8ec69f3f5e --- /dev/null +++ b/.changeset/spotty-towns-show.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +Made "github:branch-protection:create" action idempotent diff --git a/.changeset/swift-boats-add.md b/.changeset/swift-boats-add.md new file mode 100644 index 0000000000..42682437b0 --- /dev/null +++ b/.changeset/swift-boats-add.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +Made "publish:github" action idempotent diff --git a/.changeset/tame-sloths-brake.md b/.changeset/tame-sloths-brake.md new file mode 100644 index 0000000000..eae1321175 --- /dev/null +++ b/.changeset/tame-sloths-brake.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-notifications': patch +--- + +Made "notification:send" action idempotent diff --git a/.changeset/thin-snails-judge.md b/.changeset/thin-snails-judge.md new file mode 100644 index 0000000000..c2328ed64f --- /dev/null +++ b/.changeset/thin-snails-judge.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Resolved a problem where the `start` command did not correctly handle multiple `--require` flags, ensuring all specified modules are now properly loaded. diff --git a/.changeset/tiny-swans-warn.md b/.changeset/tiny-swans-warn.md new file mode 100644 index 0000000000..45c6435a5d --- /dev/null +++ b/.changeset/tiny-swans-warn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-home-react': patch +--- + +Fixes auto-hiding of content divider when title not specified diff --git a/.changeset/twenty-forks-cheat.md b/.changeset/twenty-forks-cheat.md new file mode 100644 index 0000000000..c0a7f03ee8 --- /dev/null +++ b/.changeset/twenty-forks-cheat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Fix offset pagination to reset when updating filters in `useEntityList` diff --git a/.changeset/twenty-walls-join.md b/.changeset/twenty-walls-join.md new file mode 100644 index 0000000000..2282e28775 --- /dev/null +++ b/.changeset/twenty-walls-join.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +Made "github:actions:dispatch" action idempotent diff --git a/.changeset/wet-penguins-beg.md b/.changeset/wet-penguins-beg.md new file mode 100644 index 0000000000..c4248f86f8 --- /dev/null +++ b/.changeset/wet-penguins-beg.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': minor +--- + +Internal update to move the `clean`, `pre/postpack` and `fix` commands into their own separate module. diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 281507f99b..fb2a443f58 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -46,7 +46,7 @@ jobs: cat ${{ github.event_path }} > event.json - name: Upload Artifacts - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: preview-spec path: | diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index ea9b6c2e06..a700b21d5e 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -73,7 +73,7 @@ jobs: run: yarn build:api-docs - name: upload API reference - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: stable-reference path: docs/reference/ @@ -89,7 +89,7 @@ jobs: run: yarn docusaurus gen-api-docs all - name: upload OpenAPI API docs - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: stable-openapi-docs path: docs/**/*.api.mdx @@ -129,7 +129,7 @@ jobs: run: yarn build:api-docs - name: upload API reference - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: next-reference path: docs/reference/ @@ -146,7 +146,7 @@ jobs: working-directory: storybook - name: storybook upload - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: storybook path: storybook/dist/ @@ -162,7 +162,7 @@ jobs: run: yarn docusaurus gen-api-docs all - name: upload OpenAPI API docs - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: next-openapi-docs path: docs/**/*.api.mdx @@ -211,13 +211,13 @@ jobs: working-directory: microsite - name: download stable reference - uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 + uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 with: name: stable-reference path: docs/reference - name: download stable OpenAPI API docs - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 + uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 with: name: stable-openapi-docs path: docs/**/*.api.mdx @@ -235,7 +235,7 @@ jobs: run: rm -r docs/reference - name: clear OpenAPI reference - run: find . -name '*.api.mdx' -delete + run: find . -name '*.api.mdx' -type f -delete # Next docs - name: checkout master @@ -248,13 +248,13 @@ jobs: working-directory: microsite - name: download next reference - uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 + uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 with: name: next-reference path: docs/reference - name: download next OpenAPI API docs - uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 + uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 with: name: next-openapi-docs path: docs/**/*.api.mdx @@ -263,7 +263,7 @@ jobs: run: yarn build working-directory: microsite - - uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 + - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 with: name: storybook path: microsite/build/storybook diff --git a/canon-docs/yarn.lock b/canon-docs/yarn.lock index 6dbd15358b..b51042674f 100644 --- a/canon-docs/yarn.lock +++ b/canon-docs/yarn.lock @@ -1754,12 +1754,10 @@ __metadata: languageName: node linkType: hard -"detect-libc@npm:^1.0.3": - version: 1.0.3 - resolution: "detect-libc@npm:1.0.3" - bin: - detect-libc: ./bin/detect-libc.js - checksum: daaaed925ffa7889bd91d56e9624e6c8033911bb60f3a50a74a87500680652969dbaab9526d1e200a4c94acf80fc862a22131841145a0a8482d60a99c24f4a3e +"detect-libc@npm:^2.0.3": + version: 2.0.3 + resolution: "detect-libc@npm:2.0.3" + checksum: 2ba6a939ae55f189aea996ac67afceb650413c7a34726ee92c40fb0deb2400d57ef94631a8a3f052055eea7efb0f99a9b5e6ce923415daa3e68221f963cfc27d languageName: node linkType: hard @@ -3424,91 +3422,91 @@ __metadata: languageName: node linkType: hard -"lightningcss-darwin-arm64@npm:1.29.1": - version: 1.29.1 - resolution: "lightningcss-darwin-arm64@npm:1.29.1" +"lightningcss-darwin-arm64@npm:1.29.3": + version: 1.29.3 + resolution: "lightningcss-darwin-arm64@npm:1.29.3" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"lightningcss-darwin-x64@npm:1.29.1": - version: 1.29.1 - resolution: "lightningcss-darwin-x64@npm:1.29.1" +"lightningcss-darwin-x64@npm:1.29.3": + version: 1.29.3 + resolution: "lightningcss-darwin-x64@npm:1.29.3" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"lightningcss-freebsd-x64@npm:1.29.1": - version: 1.29.1 - resolution: "lightningcss-freebsd-x64@npm:1.29.1" +"lightningcss-freebsd-x64@npm:1.29.3": + version: 1.29.3 + resolution: "lightningcss-freebsd-x64@npm:1.29.3" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"lightningcss-linux-arm-gnueabihf@npm:1.29.1": - version: 1.29.1 - resolution: "lightningcss-linux-arm-gnueabihf@npm:1.29.1" +"lightningcss-linux-arm-gnueabihf@npm:1.29.3": + version: 1.29.3 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.29.3" conditions: os=linux & cpu=arm languageName: node linkType: hard -"lightningcss-linux-arm64-gnu@npm:1.29.1": - version: 1.29.1 - resolution: "lightningcss-linux-arm64-gnu@npm:1.29.1" +"lightningcss-linux-arm64-gnu@npm:1.29.3": + version: 1.29.3 + resolution: "lightningcss-linux-arm64-gnu@npm:1.29.3" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"lightningcss-linux-arm64-musl@npm:1.29.1": - version: 1.29.1 - resolution: "lightningcss-linux-arm64-musl@npm:1.29.1" +"lightningcss-linux-arm64-musl@npm:1.29.3": + version: 1.29.3 + resolution: "lightningcss-linux-arm64-musl@npm:1.29.3" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"lightningcss-linux-x64-gnu@npm:1.29.1": - version: 1.29.1 - resolution: "lightningcss-linux-x64-gnu@npm:1.29.1" +"lightningcss-linux-x64-gnu@npm:1.29.3": + version: 1.29.3 + resolution: "lightningcss-linux-x64-gnu@npm:1.29.3" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"lightningcss-linux-x64-musl@npm:1.29.1": - version: 1.29.1 - resolution: "lightningcss-linux-x64-musl@npm:1.29.1" +"lightningcss-linux-x64-musl@npm:1.29.3": + version: 1.29.3 + resolution: "lightningcss-linux-x64-musl@npm:1.29.3" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"lightningcss-win32-arm64-msvc@npm:1.29.1": - version: 1.29.1 - resolution: "lightningcss-win32-arm64-msvc@npm:1.29.1" +"lightningcss-win32-arm64-msvc@npm:1.29.3": + version: 1.29.3 + resolution: "lightningcss-win32-arm64-msvc@npm:1.29.3" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"lightningcss-win32-x64-msvc@npm:1.29.1": - version: 1.29.1 - resolution: "lightningcss-win32-x64-msvc@npm:1.29.1" +"lightningcss-win32-x64-msvc@npm:1.29.3": + version: 1.29.3 + resolution: "lightningcss-win32-x64-msvc@npm:1.29.3" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "lightningcss@npm:^1.28.2": - version: 1.29.1 - resolution: "lightningcss@npm:1.29.1" + version: 1.29.3 + resolution: "lightningcss@npm:1.29.3" dependencies: - detect-libc: ^1.0.3 - lightningcss-darwin-arm64: 1.29.1 - lightningcss-darwin-x64: 1.29.1 - lightningcss-freebsd-x64: 1.29.1 - lightningcss-linux-arm-gnueabihf: 1.29.1 - lightningcss-linux-arm64-gnu: 1.29.1 - lightningcss-linux-arm64-musl: 1.29.1 - lightningcss-linux-x64-gnu: 1.29.1 - lightningcss-linux-x64-musl: 1.29.1 - lightningcss-win32-arm64-msvc: 1.29.1 - lightningcss-win32-x64-msvc: 1.29.1 + detect-libc: ^2.0.3 + lightningcss-darwin-arm64: 1.29.3 + lightningcss-darwin-x64: 1.29.3 + lightningcss-freebsd-x64: 1.29.3 + lightningcss-linux-arm-gnueabihf: 1.29.3 + lightningcss-linux-arm64-gnu: 1.29.3 + lightningcss-linux-arm64-musl: 1.29.3 + lightningcss-linux-x64-gnu: 1.29.3 + lightningcss-linux-x64-musl: 1.29.3 + lightningcss-win32-arm64-msvc: 1.29.3 + lightningcss-win32-x64-msvc: 1.29.3 dependenciesMeta: lightningcss-darwin-arm64: optional: true @@ -3530,7 +3528,7 @@ __metadata: optional: true lightningcss-win32-x64-msvc: optional: true - checksum: d1c4dba66dfe7f6a76532bdb84c35742bee61149550e5eb5b0e84e282f21aecd335f917ca9619bb7ca95fc1eb3092dc7e22f2c16b01e9a0ee472b76452343cce + checksum: 80cad5e1eb1c5eaae22ca6f17b127d65b5c93e859da124d57a90e256d7cb7802afa8dc7a6e1ec1e6aa208d0b23c8b9ec07078e69e61b10748bffdc8cadebed7e languageName: node linkType: hard diff --git a/docs/backend-system/core-services/auditor.md b/docs/backend-system/core-services/auditor.md index d4cbf70325..43e789a70f 100644 --- a/docs/backend-system/core-services/auditor.md +++ b/docs/backend-system/core-services/auditor.md @@ -7,7 +7,7 @@ description: Documentation for the Auditor service ## Overview -This document describes the Auditor Service, a software service designed to record and report on security-relevant events within an application. This service utilizes the `winston` library for logging and provides a flexible way to capture and format audit events. +This document describes the Auditor Service, a core service designed to record and report on security-relevant events within an application. By default, this service utilizes the `rootLogger` core service for logging. ## Key Features @@ -16,15 +16,13 @@ This document describes the Auditor Service, a software service designed to reco - Supports detailed metadata for each event. - Offers success/failure reporting for events. - Integrates with authentication and plugin services for enhanced context. -- Uses `winston` for flexible log formatting and transport. - Provides a service factory for easy integration with Backstage plugins. -- Supports configurable log transports (console, file). ## How it Works -The Auditor Service defines a core class, `Auditor`, which implements the `AuditorService` interface. This class uses `winston` to log audit events with varying levels of severity and associated metadata. It also integrates with authentication and plugin services to capture actor details and plugin context. +The Auditor Service defines a class, `Auditor`, which implements the `AuditorService` interface. This class uses a `logFn` to log audit events with varying levels of severity and associated metadata. It also integrates with authentication and plugin services to capture actor details and plugin context. -The `auditorServiceFactory` creates an `Auditor` instance for the root context and provides a factory function for creating child loggers for individual plugins. This allows each plugin to have its own logger with inherited and additional metadata. +The `auditorServiceFactory` wraps the `rootLogger` core service and provides a factory function for creating child loggers for individual plugins. This allows each plugin to have its own logger with inherited and additional metadata. ## Usage Guidance @@ -78,10 +76,78 @@ In this example, an audit event is created for each request to `/my-endpoint`. T ## Naming Conventions -When defining `eventId` and `subEventId` for your audit events, follow these guidelines: +When defining audit events, follow these guidelines to ensure consistency and clarity: -- Use kebab-case (e.g., `user-login`, `file-download`, `fetch`, `entity-create`, `entity-update`). -- The `eventId` represents a logical group of similar events or operations. For example, "fetch" could be used as an `eventId` encompassing various fetch methods like `by-id` or `by-location`. -- Use `subEventId` to further categorize events within a logical group. For example, if the `eventId` is "fetch", the `subEventId` could be "by-id" or "by-location" to specify the method used for fetching. -- Avoid redundant prefixes related to the plugin ID, as that context is already provided. -- Choose names that clearly and concisely describe the event being audited. +- **Use kebab-case:** Event IDs should be in kebab-case (e.g., `user-session`, `file-download`, `entity-fetch`). +- **`eventId` for Logical Grouping:** The `eventId` represents a broad category or logical group of related operations. For example, `entity-fetch` would group all entity retrieval events. `location-mutate` would group all actions that mutate a location. +- **`meta.queryType` (or related field) for Specific Actions within a Group:** Use a `meta` field (like `queryType`, `actionType` or similar) to specify the particular action or query that occurred within the broader `eventId` group. + - For instance, with `eventId: entity-fetch`, use `meta: { queryType: 'by-id' }` to represent fetching an entity by its ID. Other examples could be: + - `meta: { queryType: 'all' }` for fetching all entities. + - `meta: { queryType: 'by-query' }` for fetching entities by a query. + - `meta: { actionType: 'delete' }` for `eventId: entity-mutate` when an entity was deleted. + - `meta: { actionType: 'create' }` for `eventId: location-mutate` when a location was added. + - Use `meta` fields to add more context to the event being tracked. +- **Avoid Redundant Prefixes:** Do not include redundant prefixes related to the plugin ID in your event names. The plugin context is already provided separately. +- **Clear and Concise:** Choose names that clearly and concisely describe the event being audited. + +## Common Meta Keys and Values + +The following table details common keys found within the `meta` object of audit events and their formats: + +| Key | Description | Format | Example(s) | +| ------------- | ------------------------------------------------------------------------------ | ------------------------------------- | ------------------------------------------------------------------------- | +| `queryType` | Specifies the type of query performed when fetching data. | A kebab-case string | `all`, `by-id`, `by-name`, `by-query`, `by-refs`, `ancestry`, `by-entity` | +| `actionType` | Specifies the type of action performed when modifying data. | A kebab-case string | `create`, `delete`, `refresh` | +| `entityRef` | The full reference of an entity, including kind, namespace, and name. | `[kind]:[namespace]/[name]` | `component:default/my-component`, `group:my-org/team-a` | +| `locationRef` | A specific reference to a location being operated on. | Any string representing the location. | `url:https://example.com/catalog-info.yaml`, `custom:default/my-location` | +| `uid` | The unique identifier of a location or other object involved in the operation. | Any valid unique ID string | `9a4e740b-e557-427f-b9f2-0d4f092b1c1e` | + +By following these conventions, you create a more structured and informative audit trail that is easier to search, filter, and understand. This allows you to better group and understand the events being logged. + +## Audit Event Examples + +To illustrate how these naming conventions and the meta field are used in practice, the following examples demonstrate typical audit events for common operations. + +**Typical Read Operation Example:** + +For an operation that fetches all entities, a typical audit event would look like this: + +```json +{ + "eventId": "entity-fetch", + "meta": { + "queryType": "all" + } + ... +} +``` + +**Typical Write Operation Example:** + +For an operation that deletes an entity, a typical audit event would look like this: + +```json +{ + "eventId": "entity-mutate", + "meta": { + "actionType": "delete", + "uid": "some-entity-uid", + "entityRef": "component:default/petstore" + }, + "severityLevel": "medium" + ... +} +``` + +## Practical Examples for Auditor Implementation + +To clarify how to utilize the Auditor feature effectively, we recommend exploring the Catalog Backend. It offers two valuable resources: + +- **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. + - 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/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 85f29cb359..bc2847a81b 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -561,7 +561,7 @@ catalogFilter: metadata.annotations.github.com/team-slug: { exists: true } ``` -#### Custom validation messages +### Custom validation messages You may specify custom JSON Schema validation messages as supported by the [ajv-errors](https://github.com/ajv-validator/ajv-errors) plugin library to [ajv](https://github.com/ajv-validator/ajv). diff --git a/docs/tutorials/using-backstage-proxy-within-plugin.md b/docs/tutorials/using-backstage-proxy-within-plugin.md index d5ce554239..88052c5f39 100644 --- a/docs/tutorials/using-backstage-proxy-within-plugin.md +++ b/docs/tutorials/using-backstage-proxy-within-plugin.md @@ -131,7 +131,7 @@ export class MyAwesomeApiClient implements MyAwesomeApi { const proxyUri = `${await this.discoveryApi.getBaseUrl('proxy')}/`; const resp = await this.fetchApi.fetch(`${proxyUri}${input}`, init); - if (!resp.ok) throw new Error(resp); + if (!resp.ok) throw new Error(resp.statusText); return await resp.json(); } @@ -151,8 +151,9 @@ export class MyAwesomeApiClient implements MyAwesomeApi { } ``` -> For more information on the DiscoveryApi check out the -> [docs](../reference/core-plugin-api.discoveryapi.md) +> Check out the docs for more information on the +> [DiscoveryApi](../reference/core-plugin-api.discoveryapi.md) or the +> [FetchApi](../reference/core-plugin-api.fetchapi.md) ## Bundling your ApiRef with your plugin @@ -171,6 +172,7 @@ import { createRoutableExtension, createComponentExtension, discoveryApiRef, + fetchApiRef, } from '@backstage/core-plugin-api'; //... @@ -182,8 +184,12 @@ export const myCustomPlugin = createPlugin({ apis: [ createApiFactory({ api: myAwesomeApiRef, - deps: { discoveryApi: discoveryApiRef }, - factory: ({ discoveryApi }) => new MyAwesomeApiClient({ discoveryApi }), + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + }, + factory: ({ discoveryApi, fetchApi }) => + new MyAwesomeApiClient({ discoveryApi, fetchApi }), }), ], }); diff --git a/microsite/data/plugins/amplication.yaml b/microsite/data/plugins/amplication.yaml new file mode 100644 index 0000000000..a1b5f09c0e --- /dev/null +++ b/microsite/data/plugins/amplication.yaml @@ -0,0 +1,10 @@ +--- +title: Amplication Resources +author: Amplication +authorUrl: https://amplication.com +category: Development +description: A plugin that integrates Amplication services into Backstage, enabling seamless development and management of your applications. +documentation: https://docs.amplication.com/day-zero/integrate-with-backstage +iconUrl: /img/amplication_logo.png +npmPackageName: '@backstage-community/plugin-scaffolder-backend-module-amplication' +addedDate: '2025-03-07' diff --git a/microsite/static/img/amplication_logo.png b/microsite/static/img/amplication_logo.png new file mode 100644 index 0000000000..76cffe492b Binary files /dev/null and b/microsite/static/img/amplication_logo.png differ diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 6f9780dba6..43a630236c 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -1637,11 +1637,11 @@ __metadata: linkType: hard "@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.3, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.22.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": - version: 7.26.7 - resolution: "@babel/runtime@npm:7.26.7" + version: 7.26.10 + resolution: "@babel/runtime@npm:7.26.10" dependencies: regenerator-runtime: ^0.14.0 - checksum: a1664a08f3f4854b895b540cca2f5f5c6c1993b5fb788c9615d70fc201e16bb254df8e0550c83eaf2749a14d87775e11a7c9ded6161203e9da7a4a323d546925 + checksum: 22d2e0abb86e90de489ab16bb578db6fe2b63a88696db431198b24963749820c723f1982298cdbbea187f7b2b80fb4d98a514faf114ddb2fdc14a4b96277b955 languageName: node linkType: hard diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index 32918caad2..699161966c 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -200,10 +200,10 @@ Commands: start [options] build [options] test - lint [options] [directories...] clean prepack postpack + lint [options] [directories...] help [command] ``` @@ -272,7 +272,7 @@ Options: --check --inspect [host] --inspect-brk [host] - --require + --require --link -h, --help ``` @@ -452,6 +452,7 @@ Options: --since --successCache --successCacheDir + --max-warnings --fix -h, --help ``` diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 9a76711d08..99f2bc99ef 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -35,6 +35,10 @@ import { registerPackageCommands as registerPackageLintCommands, registerRepoCommands as registerRepoLintCommands, } from '../modules/lint'; +import { + registerPackageCommands as registerMaintenancePackageCommands, + registerRepoCommands as registerMaintenanceRepoCommands, +} from '../modules/maintenance'; export function registerRepoCommand(program: Command) { const command = program @@ -44,30 +48,7 @@ export function registerRepoCommand(program: Command) { registerRepoBuildCommands(command); registerRepoTestCommands(command); registerRepoLintCommands(command); - - command - .command('fix') - .description('Automatically fix packages in the project') - .option( - '--publish', - 'Enable additional fixes that only apply when publishing packages', - ) - .option( - '--check', - 'Fail if any packages would have been changed by the command', - ) - .action(lazy(() => import('./repo/fix'), 'command')); - - command - .command('clean') - .description('Delete cache and output directories') - .action(lazy(() => import('./repo/clean'), 'command')); - - command - .command('list-deprecations') - .description('List deprecations') - .option('--json', 'Output as JSON') - .action(lazy(() => import('./repo/list-deprecations'), 'command')); + registerMaintenanceRepoCommands(command); } export function registerScriptCommand(program: Command) { @@ -86,28 +67,17 @@ export function registerScriptCommand(program: Command) { '--inspect-brk [host]', 'Enable debugger in Node.js environments, breaking before code starts', ) - .option('--require ', 'Add a --require argument to the node process') + .option( + '--require ', + 'Add a --require argument to the node process', + ) .option('--link ', 'Link an external workspace for module resolution') .action(lazy(() => import('./start'), 'command')); registerPackageBuildCommands(command); registerPackageTestCommands(command); - + registerMaintenancePackageCommands(command); registerPackageLintCommands(command); - command - .command('clean') - .description('Delete cache directories') - .action(lazy(() => import('./clean/clean'), 'default')); - - command - .command('prepack') - .description('Prepares a package for packaging before publishing') - .action(lazy(() => import('./pack'), 'pre')); - - command - .command('postpack') - .description('Restores the changes made by the prepack command') - .action(lazy(() => import('./pack'), 'post')); } export function registerCommands(program: Command) { @@ -153,7 +123,6 @@ export function registerCommands(program: Command) { registerMigrateCommand(program); registerBuildCommands(program); registerInfoCommands(program); - program .command('create-github-app ') .description('Create new GitHub App in your organization.') diff --git a/packages/cli/src/lib/runner/runBackend.ts b/packages/cli/src/lib/runner/runBackend.ts index fb8fdd9d55..c085e183f1 100644 --- a/packages/cli/src/lib/runner/runBackend.ts +++ b/packages/cli/src/lib/runner/runBackend.ts @@ -39,7 +39,7 @@ export type RunBackendOptions = { /** Whether to forward the --inspect-brk flag to the node process */ inspectBrkEnabled: boolean; /** Additional module to require via the --require flag to the node process */ - require?: string; + require?: string | string[]; /** An external linked workspace to override module resolution towards */ linkedWorkspace?: string; }; @@ -107,7 +107,10 @@ export async function runBackend(options: RunBackendOptions) { optionArgs.push(inspect); } if (options.require) { - optionArgs.push(`--require=${options.require}`); + const requires = [options.require].flat(); + for (const r of requires) { + optionArgs.push(`--require=${r}`); + } } const userArgs = process.argv diff --git a/packages/cli/src/modules/lint/alpha.ts b/packages/cli/src/modules/lint/alpha.ts index 8703e73b5e..617f71d5c9 100644 --- a/packages/cli/src/modules/lint/alpha.ts +++ b/packages/cli/src/modules/lint/alpha.ts @@ -38,7 +38,7 @@ export default createCliPlugin({ ); command.option( '--max-warnings ', - 'Fail if more than this number of warnings. -1 allows warnings. (default: 0)', + 'Fail if more than this number of warnings. -1 allows warnings. (default: -1)', ); command.description('Lint a package'); command.action( @@ -76,6 +76,10 @@ export default createCliPlugin({ '--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')); diff --git a/packages/cli/src/modules/lint/commands/repo/lint.ts b/packages/cli/src/modules/lint/commands/repo/lint.ts index 15a3584020..ab6597c70a 100644 --- a/packages/cli/src/modules/lint/commands/repo/lint.ts +++ b/packages/cli/src/modules/lint/commands/repo/lint.ts @@ -111,6 +111,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { fix: Boolean(opts.fix), format: opts.format as string | undefined, shouldCache: Boolean(cacheContext), + maxWarnings: opts.maxWarnings ?? -1, successCache: cacheContext?.entries, rootDir: paths.targetRoot, }, @@ -120,6 +121,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { shouldCache, successCache, rootDir, + maxWarnings, }) => { const { ESLint } = require('eslint') as typeof import('eslint'); const crypto = require('crypto') as typeof import('crypto'); @@ -131,7 +133,6 @@ export async function command(opts: OptionValues, cmd: Command): Promise { return async ({ fullDir, relativeDir, - lintOptions, parentHash, }): Promise<{ relativeDir: string; @@ -199,7 +200,6 @@ export async function command(opts: OptionValues, cmd: Command): Promise { await ESLint.outputFixes(results); } - const maxWarnings = lintOptions?.maxWarnings ?? 0; const ignoreWarnings = +maxWarnings === -1; const resultText = formatter.format(results) as string; diff --git a/packages/cli/src/modules/lint/index.ts b/packages/cli/src/modules/lint/index.ts index e2bfcd0779..dfa0916efb 100644 --- a/packages/cli/src/modules/lint/index.ts +++ b/packages/cli/src/modules/lint/index.ts @@ -31,7 +31,7 @@ export function registerPackageCommands(command: Command) { .option('--fix', 'Attempt to automatically fix violations') .option( '--max-warnings ', - 'Fail if more than this number of warnings. -1 allows warnings. (default: 0)', + 'Fail if more than this number of warnings. -1 allows warnings. (default: -1)', ) .description('Lint a package') .action(lazy(() => import('./commands/package/lint'), 'default')); @@ -62,6 +62,10 @@ export function registerRepoCommands(command: Command) { '--successCacheDir ', 'Set the success cache location, (default: node_modules/.cache/backstage-cli)', ) + .option( + '--max-warnings ', + 'Fail if more than this number of warnings. -1 allows warnings. (default: -1)', + ) .option('--fix', 'Attempt to automatically fix violations') .action(lazy(() => import('./commands/repo/lint'), 'command')); } diff --git a/packages/cli/src/modules/maintenance/alpha.ts b/packages/cli/src/modules/maintenance/alpha.ts new file mode 100644 index 0000000000..41192afdc4 --- /dev/null +++ b/packages/cli/src/modules/maintenance/alpha.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 { Command } from 'commander'; +import { createCliPlugin } from '../../wiring/factory'; +import { lazy } from '../../lib/lazy'; + +export default createCliPlugin({ + pluginId: 'maintenance', + init: async reg => { + reg.addCommand({ + path: ['package', 'clean'], + description: 'Delete cache directories', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command.action( + lazy(() => import('./commands/package/clean'), 'default'), + ); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['package', 'prepack'], + description: 'Prepares a package for packaging before publishing', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command.action( + lazy(() => import('./commands/package/pack'), 'pre'), + ); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['package', 'postpack'], + description: 'Restores the changes made by the prepack command', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command.action( + lazy(() => import('./commands/package/pack'), 'post'), + ); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['repo', 'fix'], + description: 'Automatically fix packages in the project', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command + .option( + '--publish', + 'Enable additional fixes that only apply when publishing packages', + ) + .option( + '--check', + 'Fail if any packages would have been changed by the command', + ) + .action(lazy(() => import('./commands/repo/fix'), 'command')); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['repo', 'clean'], + description: 'Delete cache and output directories', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command.action( + lazy(() => import('./commands/repo/clean'), 'command'), + ); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['repo', 'list-deprecations'], + description: 'List deprecations', + execute: async ({ args }) => { + const command = new Command(); + const defaultCommand = command + .option('--json', 'Output as JSON') + .action( + lazy(() => import('./commands/repo/list-deprecations'), 'command'), + ); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + }, +}); diff --git a/packages/cli/src/commands/clean/clean.ts b/packages/cli/src/modules/maintenance/commands/package/clean.ts similarity index 94% rename from packages/cli/src/commands/clean/clean.ts rename to packages/cli/src/modules/maintenance/commands/package/clean.ts index 74a1ea658b..5e2d0d65b3 100644 --- a/packages/cli/src/commands/clean/clean.ts +++ b/packages/cli/src/modules/maintenance/commands/package/clean.ts @@ -15,7 +15,7 @@ */ import fs from 'fs-extra'; -import { paths } from '../../lib/paths'; +import { paths } from '../../../../lib/paths'; export default async function clean() { await fs.remove(paths.resolveTarget('dist')); diff --git a/packages/cli/src/commands/pack.ts b/packages/cli/src/modules/maintenance/commands/package/pack.ts similarity index 80% rename from packages/cli/src/commands/pack.ts rename to packages/cli/src/modules/maintenance/commands/package/pack.ts index 17162b9596..510401c6d1 100644 --- a/packages/cli/src/commands/pack.ts +++ b/packages/cli/src/modules/maintenance/commands/package/pack.ts @@ -17,11 +17,11 @@ import { productionPack, revertProductionPack, -} from '../modules/build/lib/packager/productionPack'; -import { paths } from '../lib/paths'; +} from '../../../../modules/build/lib/packager/productionPack'; +import { paths } from '../../../../lib/paths'; import fs from 'fs-extra'; -import { publishPreflightCheck } from '../lib/publishing'; -import { createTypeDistProject } from '../lib/typeDistProject'; +import { publishPreflightCheck } from '../../../../lib/publishing'; +import { createTypeDistProject } from '../../../../lib/typeDistProject'; export const pre = async () => { publishPreflightCheck({ diff --git a/packages/cli/src/commands/repo/clean.ts b/packages/cli/src/modules/maintenance/commands/repo/clean.ts similarity index 97% rename from packages/cli/src/commands/repo/clean.ts rename to packages/cli/src/modules/maintenance/commands/repo/clean.ts index 13ff061423..51c31129ea 100644 --- a/packages/cli/src/commands/repo/clean.ts +++ b/packages/cli/src/modules/maintenance/commands/repo/clean.ts @@ -19,7 +19,7 @@ import fs from 'fs-extra'; import { resolve as resolvePath } from 'path'; import { promisify } from 'util'; import { PackageGraph } from '@backstage/cli-node'; -import { paths } from '../../lib/paths'; +import { paths } from '../../../../lib/paths'; const execFile = promisify(execFileCb); diff --git a/packages/cli/src/commands/repo/fix.ts b/packages/cli/src/modules/maintenance/commands/repo/fix.ts similarity index 99% rename from packages/cli/src/commands/repo/fix.ts rename to packages/cli/src/modules/maintenance/commands/repo/fix.ts index a55440c53f..83f4da6114 100644 --- a/packages/cli/src/commands/repo/fix.ts +++ b/packages/cli/src/modules/maintenance/commands/repo/fix.ts @@ -24,8 +24,8 @@ import { import { OptionValues } from 'commander'; import fs from 'fs-extra'; import { resolve as resolvePath, posix, relative as relativePath } from 'path'; -import { paths } from '../../lib/paths'; -import { publishPreflightCheck } from '../../lib/publishing'; +import { paths } from '../../../../lib/paths'; +import { publishPreflightCheck } from '../../../../lib/publishing'; /** * A mutable object representing a package.json file with potential fixes. diff --git a/packages/cli/src/commands/repo/list-deprecations.ts b/packages/cli/src/modules/maintenance/commands/repo/list-deprecations.ts similarity index 98% rename from packages/cli/src/commands/repo/list-deprecations.ts rename to packages/cli/src/modules/maintenance/commands/repo/list-deprecations.ts index 772038025a..de83f7a4ac 100644 --- a/packages/cli/src/commands/repo/list-deprecations.ts +++ b/packages/cli/src/modules/maintenance/commands/repo/list-deprecations.ts @@ -19,7 +19,7 @@ import { ESLint } from 'eslint'; import { OptionValues } from 'commander'; import { relative as relativePath } from 'path'; import { PackageGraph } from '@backstage/cli-node'; -import { paths } from '../../lib/paths'; +import { paths } from '../../../../lib/paths'; export async function command(opts: OptionValues) { const packages = await PackageGraph.listTargetPackages(); diff --git a/packages/cli/src/modules/maintenance/index.ts b/packages/cli/src/modules/maintenance/index.ts new file mode 100644 index 0000000000..48669e9c34 --- /dev/null +++ b/packages/cli/src/modules/maintenance/index.ts @@ -0,0 +1,61 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Command } from 'commander'; +import { lazy } from '../../lib/lazy'; + +export function registerPackageCommands(command: Command) { + command + .command('clean') + .description('Delete cache directories') + .action(lazy(() => import('./commands/package/clean'), 'default')); + + command + .command('prepack') + .description('Prepares a package for packaging before publishing') + .action(lazy(() => import('./commands/package/pack'), 'pre')); + + command + .command('postpack') + .description('Restores the changes made by the prepack command') + .action(lazy(() => import('./commands/package/pack'), 'post')); +} + +export function registerRepoCommands(command: Command) { + command + .command('fix') + .description('Automatically fix packages in the project') + .option( + '--publish', + 'Enable additional fixes that only apply when publishing packages', + ) + .option( + '--check', + 'Fail if any packages would have been changed by the command', + ) + .action(lazy(() => import('./commands/repo/fix'), 'command')); + + command + .command('clean') + .description('Delete cache and output directories') + .action(lazy(() => import('./commands/repo/clean'), 'command')); + + command + .command('list-deprecations') + .description('List deprecations') + .option('--json', 'Output as JSON') + .action(lazy(() => import('./commands/repo/list-deprecations'), 'command')); +} diff --git a/packages/cli/src/modules/migrate/commands/packageExports.ts b/packages/cli/src/modules/migrate/commands/packageExports.ts index c54422ceb9..4eb741ce4d 100644 --- a/packages/cli/src/modules/migrate/commands/packageExports.ts +++ b/packages/cli/src/modules/migrate/commands/packageExports.ts @@ -18,7 +18,7 @@ import { fixPackageExports, readFixablePackages, writeFixedPackages, -} from '../../../commands/repo/fix'; +} from '../../maintenance/commands/repo/fix'; export async function command() { console.log( diff --git a/plugins/catalog-backend-module-github/config.d.ts b/plugins/catalog-backend-module-github/config.d.ts index cc197f3ec7..c94593b985 100644 --- a/plugins/catalog-backend-module-github/config.d.ts +++ b/plugins/catalog-backend-module-github/config.d.ts @@ -71,6 +71,11 @@ export interface Config { * Default: `/catalog-info.yaml`. */ catalogPath?: string; + /** + * (Optional) Whether to validate locations that exist before emitting them. + * Default: `false`. + */ + validateLocationsExist?: boolean; /** * (Optional) Filter configuration. */ diff --git a/plugins/catalog-react/report-alpha.api.md b/plugins/catalog-react/report-alpha.api.md index 4a154356d1..2df3371353 100644 --- a/plugins/catalog-react/report-alpha.api.md +++ b/plugins/catalog-react/report-alpha.api.md @@ -99,6 +99,7 @@ export const defaultEntityContentGroups: { documentation: string; development: string; deployment: string; + operation: string; observability: string; }; diff --git a/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx b/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx index c085912123..b5092d99b7 100644 --- a/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx @@ -41,6 +41,7 @@ export const defaultEntityContentGroups = { documentation: 'Documentation', development: 'Development', deployment: 'Deployment', + operation: 'Operation', observability: 'Observability', }; diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index 2a922cf417..35e34b4063 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -23,7 +23,7 @@ import { identityApiRef, storageApiRef, } from '@backstage/core-plugin-api'; -import { TestApiProvider, mockApis } from '@backstage/test-utils'; +import { mockApis, TestApiProvider } from '@backstage/test-utils'; import { act, renderHook, waitFor } from '@testing-library/react'; import qs from 'qs'; import React, { PropsWithChildren } from 'react'; @@ -593,6 +593,7 @@ describe(``, () => { expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith({ filter: { kind: 'component' }, limit, + offset: 0, orderFields, fullTextFilter: { term: '2', @@ -620,6 +621,7 @@ describe(``, () => { expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith({ filter: { kind: 'component' }, limit, + offset: 0, orderFields, }); }); @@ -746,6 +748,7 @@ describe(``, () => { expect(mockCatalogApi.queryEntities).toHaveBeenNthCalledWith(2, { filter: { kind: 'api', 'spec.type': ['service'] }, limit, + offset: 0, orderFields, }); }); diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index fc2dfe6a73..e3ca95cebb 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -379,14 +379,19 @@ export const EntityListProvider = ( // TODO(vinzscam): this is currently causing issues at page reload // where the state is not kept. Unfortunately we need to rethink // the way filters work in order to fix this. - setCursor(undefined); + if (paginationMode === 'cursor') { + setCursor(undefined); + } else if (paginationMode === 'offset') { + // Same thing with offset + setOffset(0); + } setRequestedFilters(prevFilters => { const newFilters = typeof update === 'function' ? update(prevFilters) : update; return { ...prevFilters, ...newFilters }; }); }, - [], + [paginationMode], ); const pageInfo = useMemo(() => { diff --git a/plugins/home-react/src/extensions.tsx b/plugins/home-react/src/extensions.tsx index 71b17a81a4..dd4b1e9df7 100644 --- a/plugins/home-react/src/extensions.tsx +++ b/plugins/home-react/src/extensions.tsx @@ -152,7 +152,8 @@ function CardExtension(props: CardExtensionComponentProps) { } const cardProps = { - ...(title && { title, divider: !!title }), + divider: !!title, + ...(title && { title }), ...(Settings && !isCustomizable ? { action: ( diff --git a/plugins/scaffolder-backend-module-azure/src/actions/azure.ts b/plugins/scaffolder-backend-module-azure/src/actions/azure.ts index acb4754520..8c1f5afec8 100644 --- a/plugins/scaffolder-backend-module-azure/src/actions/azure.ts +++ b/plugins/scaffolder-backend-module-azure/src/actions/azure.ts @@ -174,37 +174,49 @@ export function createPublishAzureAction(options: { const webApi = new WebApi(url, authHandler); const client = await webApi.getGitApi(); const createOptions: GitRepositoryCreateOptions = { name: repo }; - const returnedRepo = await client.createRepository( - createOptions, - project, - ); - if (!returnedRepo) { - throw new InputError( - `Unable to create the repository with Organization ${organization}, Project ${project} and Repo ${repo}. + const { remoteUrl, repositoryId, repoContentsUrl } = await ctx.checkpoint( + { + key: `create.repo.${repo}`, + fn: async () => { + const returnedRepo = await client.createRepository( + createOptions, + project, + ); + + if (!returnedRepo) { + throw new InputError( + `Unable to create the repository with Organization ${organization}, Project ${project} and Repo ${repo}. Please make sure that both the Org and Project are typed corrected and exist.`, - ); - } - const remoteUrl = returnedRepo.remoteUrl; + ); + } - if (!remoteUrl) { - throw new InputError( - 'No remote URL returned from create repository for Azure', - ); - } - const repositoryId = returnedRepo.id; + if (!returnedRepo.remoteUrl) { + throw new InputError( + 'No remote URL returned from create repository for Azure', + ); + } - if (!repositoryId) { - throw new InputError('No Id returned from create repository for Azure'); - } + if (!returnedRepo.id) { + throw new InputError( + 'No Id returned from create repository for Azure', + ); + } - const repoContentsUrl = returnedRepo.webUrl; + if (!returnedRepo.webUrl) { + throw new InputError( + 'No web URL returned from create repository for Azure', + ); + } - if (!repoContentsUrl) { - throw new InputError( - 'No web URL returned from create repository for Azure', - ); - } + return { + remoteUrl: returnedRepo.remoteUrl, + repositoryId: returnedRepo.id, + repoContentsUrl: returnedRepo.webUrl, + }; + }, + }, + ); const gitAuthorInfo = { name: gitAuthorName @@ -229,20 +241,30 @@ export function createPublishAzureAction(options: { ); } - const commitResult = await initRepoAndPush({ - dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), - remoteUrl, - defaultBranch, - auth: auth, - logger: ctx.logger, - commitMessage: gitCommitMessage - ? gitCommitMessage - : config.getOptionalString('scaffolder.defaultCommitMessage'), - gitAuthorInfo, - signingKey: signCommit ? signingKey : undefined, + const commitHash = await ctx.checkpoint({ + key: `init.repo.and.push.${remoteUrl}`, + fn: async () => { + const commitResult = await initRepoAndPush({ + dir: getRepoSourceDirectory( + ctx.workspacePath, + ctx.input.sourcePath, + ), + remoteUrl, + defaultBranch, + auth: auth, + logger: ctx.logger, + commitMessage: gitCommitMessage + ? gitCommitMessage + : config.getOptionalString('scaffolder.defaultCommitMessage'), + gitAuthorInfo, + signingKey: signCommit ? signingKey : undefined, + }); + + return commitResult?.commitHash; + }, }); - ctx.output('commitHash', commitResult?.commitHash); + ctx.output('commitHash', commitHash); ctx.output('remoteUrl', remoteUrl); ctx.output('repoContentsUrl', repoContentsUrl); ctx.output('repositoryId', repositoryId); diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts index 945aab1216..ff61213e1a 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts @@ -224,15 +224,19 @@ export function createPublishBitbucketCloudAction(options: { const apiBaseUrl = integrationConfig.config.apiBaseUrl; - const { remoteUrl, repoContentsUrl } = await createRepository({ - authorization, - workspace: workspace || '', - project, - repo, - repoVisibility, - mainBranch: defaultBranch, - description, - apiBaseUrl, + const { remoteUrl, repoContentsUrl } = await ctx.checkpoint({ + key: `create.repo.${host}.${repo}`, + fn: async () => + await createRepository({ + authorization, + workspace: workspace || '', + project, + repo, + repoVisibility, + mainBranch: defaultBranch, + description, + apiBaseUrl, + }), }); const gitAuthorInfo = { @@ -272,20 +276,29 @@ export function createPublishBitbucketCloudAction(options: { ); } - const commitResult = await initRepoAndPush({ - dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), - remoteUrl, - auth, - defaultBranch, - logger: ctx.logger, - commitMessage: - gitCommitMessage || - config.getOptionalString('scaffolder.defaultCommitMessage'), - gitAuthorInfo, - signingKey: signCommit ? signingKey : undefined, + const commitHash = await ctx.checkpoint({ + key: `init.repo.and.push${host}.${repo}`, + fn: async () => { + const commitResult = await initRepoAndPush({ + dir: getRepoSourceDirectory( + ctx.workspacePath, + ctx.input.sourcePath, + ), + remoteUrl, + auth, + defaultBranch, + logger: ctx.logger, + commitMessage: + gitCommitMessage || + config.getOptionalString('scaffolder.defaultCommitMessage'), + gitAuthorInfo, + signingKey: signCommit ? signingKey : undefined, + }); + return commitResult?.commitHash; + }, }); - ctx.output('commitHash', commitResult?.commitHash); + ctx.output('commitHash', commitHash); ctx.output('remoteUrl', remoteUrl); ctx.output('repoContentsUrl', repoContentsUrl); }, diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts index e52eb50976..e2978470bd 100644 --- a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts @@ -368,15 +368,19 @@ export function createPublishBitbucketAction(options: { ? createBitbucketCloudRepository : createBitbucketServerRepository; - const { remoteUrl, repoContentsUrl } = await createMethod({ - authorization, - workspace: workspace || '', - project, - repo, - repoVisibility, - mainBranch: defaultBranch, - description, - apiBaseUrl, + const { remoteUrl, repoContentsUrl } = await ctx.checkpoint({ + key: `create.repo.${host}.${repo}`, + fn: async () => + createMethod({ + authorization, + workspace: workspace || '', + project, + repo, + repoVisibility, + mainBranch: defaultBranch, + description, + apiBaseUrl, + }), }); const gitAuthorInfo = { @@ -414,24 +418,33 @@ export function createPublishBitbucketAction(options: { }; } - const commitResult = await initRepoAndPush({ - dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), - remoteUrl, - auth, - defaultBranch, - logger: ctx.logger, - commitMessage: gitCommitMessage - ? gitCommitMessage - : config.getOptionalString('scaffolder.defaultCommitMessage'), - gitAuthorInfo, - signingKey: signCommit ? signingKey : undefined, + const commitHash = await ctx.checkpoint({ + key: `init.repo.and.push${host}.${repo}`, + fn: async () => { + const commitResult = await initRepoAndPush({ + dir: getRepoSourceDirectory( + ctx.workspacePath, + ctx.input.sourcePath, + ), + remoteUrl, + auth, + defaultBranch, + logger: ctx.logger, + commitMessage: gitCommitMessage + ? gitCommitMessage + : config.getOptionalString('scaffolder.defaultCommitMessage'), + gitAuthorInfo, + signingKey: signCommit ? signingKey : undefined, + }); + return commitResult?.commitHash; + }, }); if (enableLFS && host !== 'bitbucket.org') { await performEnableLFS({ authorization, host, project, repo }); } - ctx.output('commitHash', commitResult?.commitHash); + ctx.output('commitHash', commitHash); ctx.output('remoteUrl', remoteUrl); ctx.output('repoContentsUrl', repoContentsUrl); }, diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.ts b/plugins/scaffolder-backend-module-github/src/actions/github.ts index 6c47a2dade..02b1fbbe32 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.ts @@ -241,36 +241,43 @@ export function createPublishGithubAction(options: { }); const client = new Octokit(octokitOptions); - const newRepo = await createGithubRepoWithCollaboratorsAndTopics( - client, - repo, - owner, - repoVisibility, - description, - homepage, - deleteBranchOnMerge, - allowMergeCommit, - allowSquashMerge, - squashMergeCommitTitle, - squashMergeCommitMessage, - allowRebaseMerge, - allowAutoMerge, - access, - collaborators, - hasProjects, - hasWiki, - hasIssues, - topics, - repoVariables, - secrets, - oidcCustomization, - customProperties, - subscribe, - ctx.logger, - ); + const { remoteUrl, repoContentsUrl } = await ctx.checkpoint({ + key: `create.github.repo.${owner}.${repo}`, + fn: async () => { + const newRepo = await createGithubRepoWithCollaboratorsAndTopics( + client, + repo, + owner, + repoVisibility, + description, + homepage, + deleteBranchOnMerge, + allowMergeCommit, + allowSquashMerge, + squashMergeCommitTitle, + squashMergeCommitMessage, + allowRebaseMerge, + allowAutoMerge, + access, + collaborators, + hasProjects, + hasWiki, + hasIssues, + topics, + repoVariables, + secrets, + oidcCustomization, + customProperties, + subscribe, + ctx.logger, + ); - const remoteUrl = newRepo.clone_url; - const repoContentsUrl = `${newRepo.html_url}/blob/${defaultBranch}`; + return { + remoteUrl: newRepo.clone_url, + repoContentsUrl: `${newRepo.html_url}/blob/${defaultBranch}`, + }; + }, + }); const commitResult = await initRepoPushAndProtect( remoteUrl, diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts index 8e185a9567..4faa53e6b8 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts @@ -115,15 +115,20 @@ export function createGithubActionsDispatchAction(options: { }), ); - await client.rest.actions.createWorkflowDispatch({ - owner, - repo, - workflow_id: workflowId, - ref: branchOrTagName, - inputs: workflowInputs, - }); + await ctx.checkpoint({ + key: `create.workflow.dispatch.${owner}.${repo}.${workflowId}`, + fn: async () => { + await client.rest.actions.createWorkflowDispatch({ + owner, + repo, + workflow_id: workflowId, + ref: branchOrTagName, + inputs: workflowInputs, + }); - ctx.logger.info(`Workflow ${workflowId} dispatched successfully`); + ctx.logger.info(`Workflow ${workflowId} dispatched successfully`); + }, + }); }, }); } diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.ts b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.ts index 85c7f5da6f..396d85708f 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.ts @@ -108,15 +108,20 @@ export function createGithubAutolinksAction(options: { }), ); - await client.rest.repos.createAutolink({ - owner, - repo, - key_prefix: keyPrefix, - url_template: urlTemplate, - is_alphanumeric: isAlphanumeric, - }); + await ctx.checkpoint({ + key: `create.auto.link.${owner}.${repo}`, + fn: async () => { + await client.rest.repos.createAutolink({ + owner, + repo, + key_prefix: keyPrefix, + url_template: urlTemplate, + is_alphanumeric: isAlphanumeric, + }); - ctx.logger.info(`Autolink reference created successfully`); + ctx.logger.info(`Autolink reference created successfully`); + }, + }); }, }); } diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts index 6de447541b..ae8965ef9a 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts @@ -130,29 +130,40 @@ export function createGithubBranchProtectionAction(options: { }); const client = new Octokit(octokitOptions); - const repository = await client.rest.repos.get({ - owner: owner, - repo: repo, + const defaultBranch = await ctx.checkpoint({ + key: `read.default.branch.${owner}.${repo}`, + fn: async () => { + const repository = await client.rest.repos.get({ + owner: owner, + repo: repo, + }); + return repository.data.default_branch; + }, }); - await enableBranchProtectionOnDefaultRepoBranch({ - repoName: repo, - client, - owner, - logger: ctx.logger, - requireCodeOwnerReviews, - bypassPullRequestAllowances, - requiredApprovingReviewCount, - restrictions, - requiredStatusCheckContexts, - requireBranchesToBeUpToDate, - requiredConversationResolution, - requireLastPushApproval, - defaultBranch: branch ?? repository.data.default_branch, - enforceAdmins, - dismissStaleReviews, - requiredCommitSigning, - requiredLinearHistory, + await ctx.checkpoint({ + key: `enable.branch.protection.${owner}.${repo}`, + fn: async () => { + await enableBranchProtectionOnDefaultRepoBranch({ + repoName: repo, + client, + owner, + logger: ctx.logger, + requireCodeOwnerReviews, + bypassPullRequestAllowances, + requiredApprovingReviewCount, + restrictions, + requiredStatusCheckContexts, + requireBranchesToBeUpToDate, + requiredConversationResolution, + requireLastPushApproval, + defaultBranch: branch ?? defaultBranch, + enforceAdmins, + dismissStaleReviews, + requiredCommitSigning, + requiredLinearHistory, + }); + }, }); }, }); diff --git a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts index 9646de0001..d1954dfd8c 100644 --- a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts +++ b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts @@ -133,9 +133,14 @@ export function createSendNotificationAction(options: { }; try { - await notifications.send({ - recipients: notificationRecipients, - payload, + await ctx.checkpoint({ + key: `send.notification.${payload.title}`, + fn: async () => { + await notifications.send({ + recipients: notificationRecipients, + payload, + }); + }, }); } catch (e) { ctx.logger.error(`Failed to send notification: ${e}`); diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts index 259b670f9e..6c173deb59 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts @@ -88,32 +88,41 @@ export function createSentryCreateProjectAction(options: { config: Config }) { throw new InputError(`No valid sentry token given`); } - const response = await fetch( - `https://sentry.io/api/0/teams/${organizationSlug}/${teamSlug}/projects/`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(body), + const { result } = await ctx.checkpoint({ + key: `create.project.${organizationSlug}.${teamSlug}`, + fn: async () => { + const response = await fetch( + `https://sentry.io/api/0/teams/${organizationSlug}/${teamSlug}/projects/`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }, + ); + + const contentType = response.headers.get('content-type'); + + if (contentType !== 'application/json') { + throw new InputError( + `Unexpected Sentry Response Type: ${await response.text()}`, + ); + } + + const res = await response.json(); + + if (response.status !== 201) { + throw new InputError(`Sentry Response was: ${await res.detail}`); + } + + return { + code: response.status, + result: res as { id: string }, + }; }, - ); - - const contentType = response.headers.get('content-type'); - - if (contentType !== 'application/json') { - throw new InputError( - `Unexpected Sentry Response Type: ${await response.text()}`, - ); - } - - const code = response.status; - const result = await response.json(); - - if (code !== 201) { - throw new InputError(`Sentry Response was: ${await result.detail}`); - } + }); ctx.output('id', result.id); ctx.output('result', result); diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index 37afe780a9..c3100d8259 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -529,7 +529,7 @@ export interface RouterOptions { // (undocumented) additionalTemplateFilters?: | Record - | CreatedTemplateFilter[]; + | CreatedTemplateFilter[]; // (undocumented) additionalTemplateGlobals?: | Record diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts index 4882b3cb2e..9fe78c7119 100644 --- a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts +++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts @@ -82,7 +82,7 @@ export const scaffolderPlugin = createBackendPlugin({ }, }); - const additionalTemplateFilters: CreatedTemplateFilter[] = []; + const additionalTemplateFilters: CreatedTemplateFilter[] = []; const additionalTemplateGlobals: CreatedTemplateGlobal[] = []; env.registerExtensionPoint(scaffolderTemplatingExtensionPoint, { diff --git a/plugins/scaffolder-backend/src/lib/templating/filters.ts b/plugins/scaffolder-backend/src/lib/templating/filters.ts deleted file mode 100644 index bc3f95b18c..0000000000 --- a/plugins/scaffolder-backend/src/lib/templating/filters.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { parseEntityRef } from '@backstage/catalog-model'; -import { ScmIntegrations } from '@backstage/integration'; -import type { JsonObject, JsonValue } from '@backstage/types'; -import { - parseRepoUrl, - TemplateFilter, -} from '@backstage/plugin-scaffolder-node'; -import get from 'lodash/get'; - -export default ({ - integrations, -}: { - integrations: ScmIntegrations; -}): Record => { - return { - parseRepoUrl: url => parseRepoUrl(url as string, integrations), - parseEntityRef: (ref: JsonValue, context?: JsonValue) => - parseEntityRef(ref as string, context as JsonObject), - pick: (obj: JsonValue, key: JsonValue) => get(obj, key as string), - projectSlug: repoUrl => { - const { owner, repo } = parseRepoUrl(repoUrl as string, integrations); - return `${owner}/${repo}`; - }, - }; -}; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/createDefaultFilters.ts b/plugins/scaffolder-backend/src/lib/templating/filters/createDefaultFilters.ts new file mode 100644 index 0000000000..5eb4de5ac3 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/createDefaultFilters.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ScmIntegrations } from '@backstage/integration'; +import { createParseRepoUrl } from './parseRepoUrl'; +import { parseEntityRef } from './parseEntityRef'; +import { pick } from './pick'; +import { createProjectSlug } from './projectSlug'; + +export const createDefaultFilters = (options: { + integrations: ScmIntegrations; +}) => [ + createParseRepoUrl(options.integrations), + parseEntityRef, + pick, + createProjectSlug(options.integrations), +]; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/examples.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/examples.ts new file mode 100644 index 0000000000..aef5b451bb --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/examples.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; + +export const examples: TemplateFilterExample[] = [ + { + description: 'Without context', + example: `\ +- id: log + name: Parse Entity Reference + action: debug:log + input: + message: \${{ parameters.owner | parseEntityRef }} +`, + notes: `\ +- **Input**: \`group:techdocs\` +- **Output**: \`{"kind": "group", "namespace": "default", "name": "techdocs"}\` +`, + }, + { + description: 'With context', + example: `\ +- id: log + name: Parse Entity Reference + action: debug:log + input: + message: \${{ parameters.owner | parseEntityRef({ defaultKind:"group", defaultNamespace:"another-namespace" }) }} +`, + notes: `\ +- **Input**: \`techdocs\` +- **Arguments:**: \`[{ "defaultKind": "group", "defaultNamespace": "another-namespace" }]\` +- **Output**: \`{"kind": "group", "namespace": "another-namespace", "name": "techdocs"}\` +`, + }, +]; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/filter.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/filter.ts new file mode 100644 index 0000000000..23ce340370 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/filter.ts @@ -0,0 +1,63 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { parseEntityRef as filter } from '@backstage/catalog-model'; +import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; +import { examples } from './examples'; + +export const parseEntityRef = createTemplateFilter({ + id: 'parseEntityRef', + description: + 'Extracts the parts of an entity reference, such as the kind, namespace, and name.', + schema: z => + z + .function() + .args( + z.union([ + z.string().describe('compact entity reference'), + z + .object({ + kind: z.string().optional(), + namespace: z.string().optional(), + name: z.string(), + }) + .describe('`CompoundEntityRef`'), + ]), + z + .object({ + defaultKind: z + .string() + .describe('The default kind, if none is given in the reference'), + defaultNamespace: z + .string() + .describe( + 'The default namespace, if none is given in the reference', + ), + }) + .partial() + .optional(), + ) + .returns( + z + .object({ + kind: z.string(), + namespace: z.string(), + name: z.string(), + }) + .describe('`CompoundEntityRef`'), + ), + examples, + filter, +}); diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/index.ts new file mode 100644 index 0000000000..b11d925161 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseEntityRef/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { parseEntityRef } from './filter'; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/examples.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/examples.ts new file mode 100644 index 0000000000..4348305737 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/examples.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; + +export const examples: TemplateFilterExample[] = [ + { + example: `\ +- id: log + name: Parse Repo URL + action: debug:log + input: + message: \${{ parameters.repoUrl | parseRepoUrl }}`, + notes: ` - **Input**: \`github.com?repo=backstage&owner=backstage\` + - **Output**: \`{"host":"github.com","owner":"backstage","repo":"backstage"}\` +`, + }, +]; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/filter.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/filter.ts new file mode 100644 index 0000000000..8a7daea3bc --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/filter.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ScmIntegrations } from '@backstage/integration'; +import { parseRepoUrl } from '@backstage/plugin-scaffolder-node'; +import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; +import { examples } from './examples'; + +export const createParseRepoUrl = (integrations: ScmIntegrations) => + createTemplateFilter({ + id: 'parseRepoUrl', + description: + 'Parses a repository URL into its constituent parts: owner, repository name, etc.', + schema: z => + z + .function() + .args( + z.string().describe('repo URL as collected from repository picker'), + ) + .returns( + z + .object({ + repo: z.string(), + host: z.string(), + }) + .merge( + z + .object({ + owner: z.string(), + organization: z.string(), + workspace: z.string(), + project: z.string(), + }) + .partial(), + ) + .describe('`RepoSpec`'), + ), + examples, + filter: url => parseRepoUrl(url, integrations), + }); diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts new file mode 100644 index 0000000000..ed505315b5 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/parseRepoUrl/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { createParseRepoUrl } from './filter'; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/pick/examples.ts b/plugins/scaffolder-backend/src/lib/templating/filters/pick/examples.ts new file mode 100644 index 0000000000..28593847ef --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/pick/examples.ts @@ -0,0 +1,31 @@ +import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; + +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const examples: TemplateFilterExample[] = [ + { + example: `\ +- id: log + name: Pick + action: debug:log + input: + message: \${{ parameters.owner | parseEntityRef | pick('name') }}`, + notes: `\ +- **Input**: \`{ kind: 'Group', namespace: 'default', name: 'techdocs'\` } +- **Output**: \`techdocs\` +`, + }, +]; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/pick/filter.ts b/plugins/scaffolder-backend/src/lib/templating/filters/pick/filter.ts new file mode 100644 index 0000000000..a38855becc --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/pick/filter.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; +import { get as filter } from 'lodash'; +import { examples } from './examples'; + +export const pick = createTemplateFilter({ + id: 'pick', + description: + 'Selects a specific property (e.g. kind, namespace, name) from an object.', + schema: z => + z + .function() + .args(z.any(), z.string().describe('Property')) + .returns(z.any().describe('Selected property')), + examples, + filter, +}); diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/pick/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/pick/index.ts new file mode 100644 index 0000000000..b3ca69bc38 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/pick/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { pick } from './filter'; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/examples.ts b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/examples.ts new file mode 100644 index 0000000000..2d668baee4 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/examples.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TemplateFilterExample } from '@backstage/plugin-scaffolder-node/alpha'; + +export const examples: TemplateFilterExample[] = [ + { + example: `\ +- id: log + name: Project Slug + action: debug:log + input: + message: \${{ parameters.repoUrl | projectSlug }} +`, + notes: `\ +- **Input**: \`github.com?repo=backstage&owner=backstage\` +- **Output**: backstage/backstage +`, + }, +]; diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/filter.ts b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/filter.ts new file mode 100644 index 0000000000..f6d0eaf681 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/filter.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ScmIntegrations } from '@backstage/integration'; +import { parseRepoUrl } from '@backstage/plugin-scaffolder-node'; +import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; +import { examples } from './examples'; + +export const createProjectSlug = (integrations: ScmIntegrations) => + createTemplateFilter({ + id: 'projectSlug', + description: 'Generates a project slug from a repository URL.', + schema: z => + z + .function() + .args( + z.string().describe('repo URL as collected from repository picker'), + ) + .returns(z.string()), + examples, + filter: repoUrl => { + const { owner, repo } = parseRepoUrl(repoUrl, integrations); + return `${owner}/${repo}`; + }, + }); diff --git a/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts new file mode 100644 index 0000000000..71a092232c --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templating/filters/projectSlug/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { createProjectSlug } from './filter'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index 543c25ab3e..8dfa26057b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -29,7 +29,7 @@ import globby from 'globby'; import fs from 'fs-extra'; import { isBinaryFile } from 'isbinaryfile'; import { SecureTemplater } from '../../../../lib/templating/SecureTemplater'; -import createDefaultFilters from '../../../../lib/templating/filters'; +import { createDefaultFilters } from '../../../../lib/templating/filters/createDefaultFilters'; import { examples } from './template.examples'; import { convertFiltersToRecord } from '../../../../util/templating'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts index f3b98d148a..2cd4ce883c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts @@ -25,7 +25,7 @@ import { TemplateGlobal, } from '@backstage/plugin-scaffolder-node'; import { SecureTemplater } from '../../../../lib/templating/SecureTemplater'; -import createDefaultFilters from '../../../../lib/templating/filters'; +import { createDefaultFilters } from '../../../../lib/templating/filters/createDefaultFilters'; import path from 'path'; import fs from 'fs-extra'; import { convertFiltersToRecord } from '../../../../util/templating'; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index b771c5ded1..c352714d9d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -55,7 +55,7 @@ import { TemplateFilter, TemplateGlobal, } from '@backstage/plugin-scaffolder-node'; -import createDefaultFilters from '../../lib/templating/filters'; +import { createDefaultFilters } from '../../lib/templating/filters/createDefaultFilters'; import { scaffolderActionRules } from '../../service/rules'; import { createCounterMetric, createHistogramMetric } from '../../util/metrics'; import { BackstageLoggerTransport, WinstonLogger } from './logger'; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 1186e7d4f6..8258d8c36f 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -179,7 +179,7 @@ export interface RouterOptions { taskBroker?: TaskBroker; additionalTemplateFilters?: | Record - | CreatedTemplateFilter[]; + | CreatedTemplateFilter[]; additionalTemplateGlobals?: | Record | CreatedTemplateGlobal[]; diff --git a/plugins/scaffolder-backend/src/util/templating.test.ts b/plugins/scaffolder-backend/src/util/templating.test.ts index 5ac6db601b..61d8637016 100644 --- a/plugins/scaffolder-backend/src/util/templating.test.ts +++ b/plugins/scaffolder-backend/src/util/templating.test.ts @@ -28,14 +28,14 @@ import { extractGlobalValueMetadata, } from './templating'; import { JsonValue } from '@backstage/types'; -import builtInFilters from '../lib/templating/filters'; +import { createDefaultFilters } from '../lib/templating/filters/createDefaultFilters'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; describe('templating utilities', () => { describe('built-in filters', () => { const integrations = ScmIntegrations.fromConfig(new ConfigReader({})); - const filters = builtInFilters({ integrations }); + const filters = createDefaultFilters({ integrations }); it('generates equivalent filter metadata', () => { const metadata = extractFilterMetadata(filters); expect(metadata).toMatchObject(extractFilterMetadata(filters)); @@ -64,7 +64,7 @@ describe('templating utilities', () => { z.string().describe('separator').optional(), ) .returns(z.string()), - filter: (input: string, times: number, separator?: string) => + filter: (input, times, separator) => Array(times) .fill(input) .join(separator ?? ''), @@ -149,7 +149,7 @@ describe('templating utilities', () => { id: 'respond', schema: z => z.function().args(z.string().describe('prompt')).returns(z.string()), - fn: (prompt: string) => + fn: prompt => prompt === 'knock knock' ? "who's there?" : "nobody's home", }), ]; diff --git a/plugins/scaffolder-backend/src/util/templating.ts b/plugins/scaffolder-backend/src/util/templating.ts index 1f97e98b83..2607d5bef6 100644 --- a/plugins/scaffolder-backend/src/util/templating.ts +++ b/plugins/scaffolder-backend/src/util/templating.ts @@ -22,7 +22,7 @@ import { CreatedTemplateGlobal, CreatedTemplateGlobalFunction, CreatedTemplateGlobalValue, - TemplateGlobalFunctionSchema, + ZodFunctionSchema, } from '@backstage/plugin-scaffolder-node/alpha'; import { JsonValue } from '@backstage/types'; import { Schema } from 'jsonschema'; @@ -63,8 +63,12 @@ type ExportFilterSchema = { * Converts a Zod function schema to JSON schema */ function convertZodFunctionToJsonSchema( - t: ReturnType>, + t: ReturnType>, ): ExportFunctionSchema { + if (!('parameters' in t) || !('returnType' in t)) { + throw new Error('Invalid Zod function schema'); + } + const args = (t.parameters().items as ZodType[]).map( zt => zodToJsonSchema(zt) as Schema, ); @@ -142,7 +146,9 @@ export function extractFilterMetadata( if (filter.schema) { metadata.schema = convertToFilterSchema( - convertZodFunctionToJsonSchema(filter.schema(z)), + convertZodFunctionToJsonSchema( + filter.schema(z) as z.ZodFunction, + ), ); } @@ -167,10 +173,7 @@ export function extractFilterMetadata( */ function isGlobalFunction( global: CreatedTemplateGlobal, -): global is CreatedTemplateGlobalFunction< - TemplateGlobalFunctionSchema | undefined, - any -> { +): global is CreatedTemplateGlobalFunction { return 'fn' in global; } diff --git a/plugins/scaffolder-node/report-alpha.api.md b/plugins/scaffolder-node/report-alpha.api.md index fa9e022bb4..a96f312d2d 100644 --- a/plugins/scaffolder-node/report-alpha.api.md +++ b/plugins/scaffolder-node/report-alpha.api.md @@ -29,56 +29,31 @@ export type AutocompleteHandler = ({ // @alpha (undocumented) export type CreatedTemplateFilter< - TSchema extends - | TemplateFilterSchema - | undefined - | unknown = unknown, - TFilterSchema extends TSchema extends TemplateFilterSchema - ? z.infer> - : TSchema extends unknown - ? unknown - : TemplateFilter = TSchema extends TemplateFilterSchema - ? z.infer> - : TSchema extends unknown - ? unknown - : TemplateFilter, + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, > = { id: string; description?: string; examples?: TemplateFilterExample[]; - schema?: TSchema; - filter: TFilterSchema; + schema?: ZodFunctionSchema; + filter: (...args: z.infer>) => z.infer; }; // @alpha (undocumented) export type CreatedTemplateGlobal = | CreatedTemplateGlobalValue - | CreatedTemplateGlobalFunction; + | CreatedTemplateGlobalFunction; // @alpha (undocumented) export type CreatedTemplateGlobalFunction< - TSchema extends - | TemplateGlobalFunctionSchema - | undefined - | unknown = unknown, - TFilterSchema extends TSchema extends TemplateGlobalFunctionSchema - ? z.infer> - : TSchema extends unknown - ? unknown - : Exclude< - TemplateGlobal, - JsonValue - > = TSchema extends TemplateGlobalFunctionSchema - ? z.infer> - : TSchema extends unknown - ? unknown - : Exclude, + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, > = { id: string; description?: string; examples?: TemplateGlobalFunctionExample[]; - schema?: TSchema; - fn: TFilterSchema; + schema?: ZodFunctionSchema; + fn: (...args: z.infer>) => z.infer; }; // @alpha (undocumented) @@ -90,23 +65,27 @@ export type CreatedTemplateGlobalValue = { // @alpha export const createTemplateFilter: < - TSchema extends TemplateFilterSchema | undefined, - TFunctionSchema extends TSchema extends TemplateFilterSchema - ? z.infer> - : (arg: JsonValue, ...rest: JsonValue[]) => JsonValue | undefined, ->( - filter: CreatedTemplateFilter, -) => CreatedTemplateFilter; + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, +>(options: { + id: string; + description?: string; + examples?: TemplateFilterExample[]; + schema?: ZodFunctionSchema; + filter: (...args: z.infer>) => z.infer; +}) => CreatedTemplateFilter; // @alpha export const createTemplateGlobalFunction: < - TSchema extends TemplateGlobalFunctionSchema | undefined, - TFilterSchema extends TSchema extends TemplateGlobalFunctionSchema - ? z.infer> - : (...args: JsonValue[]) => JsonValue | undefined, ->( - fn: CreatedTemplateGlobalFunction, -) => CreatedTemplateGlobalFunction; + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, +>(options: { + id: string; + description?: string; + examples?: TemplateGlobalFunctionExample[]; + schema?: ZodFunctionSchema; + fn: (...args: z.infer>) => z.infer; +}) => CreatedTemplateGlobalFunction; // @alpha export const createTemplateGlobalValue: ( @@ -156,7 +135,9 @@ export const scaffolderTaskBrokerExtensionPoint: ExtensionPoint | CreatedTemplateFilter[], + filters: + | Record + | CreatedTemplateFilter[], ): void; // (undocumented) addTemplateGlobals( @@ -194,16 +175,6 @@ export type TemplateFilterExample = { notes?: string; }; -// @alpha (undocumented) -export type TemplateFilterSchema< - Args extends z.ZodTuple< - | [z.ZodType] - | [z.ZodType, ...(z.ZodType | z.ZodUnknown)[]], - z.ZodType | z.ZodUnknown | null - >, - Result extends z.ZodType | z.ZodUndefined, -> = (zod: typeof z) => z.ZodFunction; - // @public (undocumented) export type TemplateGlobal = | ((...args: JsonValue[]) => JsonValue | undefined) @@ -216,15 +187,6 @@ export type TemplateGlobalFunctionExample = { notes?: string; }; -// @alpha (undocumented) -export type TemplateGlobalFunctionSchema< - Args extends z.ZodTuple< - [] | [z.ZodType, ...(z.ZodType | z.ZodUnknown)[]], - z.ZodType | z.ZodUnknown | null - >, - Result extends z.ZodType | z.ZodUndefined, -> = (zod: typeof z) => z.ZodFunction; - // @alpha export interface WorkspaceProvider { // (undocumented) @@ -244,5 +206,17 @@ export interface WorkspaceProvider { }): Promise; } +// @alpha (undocumented) +export type ZodFunctionSchema< + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, +> = ( + zod: typeof z, +) => + | z.ZodFunction, TReturnType> + | z.ZodType< + (...args: z.infer>) => z.infer + >; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts b/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts index 1f4bac4ab7..f648658588 100644 --- a/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts +++ b/plugins/scaffolder-node/src/alpha/filters/createTemplateFilter.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { JsonValue } from '@backstage/types'; -import { CreatedTemplateFilter, TemplateFilterSchema } from './types'; +import { ZodFunctionSchema } from '../types'; +import { CreatedTemplateFilter, TemplateFilterExample } from './types'; import { z } from 'zod'; /** @@ -23,10 +23,12 @@ import { z } from 'zod'; * @alpha */ export const createTemplateFilter = < - TSchema extends TemplateFilterSchema | undefined, - TFunctionSchema extends TSchema extends TemplateFilterSchema - ? z.infer> - : (arg: JsonValue, ...rest: JsonValue[]) => JsonValue | undefined, ->( - filter: CreatedTemplateFilter, -): CreatedTemplateFilter => filter; + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, +>(options: { + id: string; + description?: string; + examples?: TemplateFilterExample[]; + schema?: ZodFunctionSchema; + filter: (...args: z.infer>) => z.infer; +}): CreatedTemplateFilter => options; diff --git a/plugins/scaffolder-node/src/alpha/filters/types.ts b/plugins/scaffolder-node/src/alpha/filters/types.ts index 2f65baaefc..1906ba1c9c 100644 --- a/plugins/scaffolder-node/src/alpha/filters/types.ts +++ b/plugins/scaffolder-node/src/alpha/filters/types.ts @@ -14,21 +14,10 @@ * limitations under the License. */ import { z } from 'zod'; -import { TemplateFilter } from '../../types'; -import { JsonValue } from '@backstage/types'; +import { ZodFunctionSchema } from '../types'; export type { TemplateFilter } from '../../types'; -/** @alpha */ -export type TemplateFilterSchema< - Args extends z.ZodTuple< - | [z.ZodType] - | [z.ZodType, ...(z.ZodType | z.ZodUnknown)[]], - z.ZodType | z.ZodUnknown | null - >, - Result extends z.ZodType | z.ZodUndefined, -> = (zod: typeof z) => z.ZodFunction; - /** @alpha */ export type TemplateFilterExample = { description?: string; @@ -38,23 +27,12 @@ export type TemplateFilterExample = { /** @alpha */ export type CreatedTemplateFilter< - TSchema extends - | TemplateFilterSchema - | undefined - | unknown = unknown, - TFilterSchema extends TSchema extends TemplateFilterSchema - ? z.infer> - : TSchema extends unknown - ? unknown - : TemplateFilter = TSchema extends TemplateFilterSchema - ? z.infer> - : TSchema extends unknown - ? unknown - : TemplateFilter, + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, > = { id: string; description?: string; examples?: TemplateFilterExample[]; - schema?: TSchema; - filter: TFilterSchema; + schema?: ZodFunctionSchema; + filter: (...args: z.infer>) => z.infer; }; diff --git a/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts b/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts index 1f9e090d7f..2687b83bb4 100644 --- a/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts +++ b/plugins/scaffolder-node/src/alpha/globals/createTemplateGlobal.ts @@ -18,9 +18,9 @@ import { z } from 'zod'; import { CreatedTemplateGlobalFunction, CreatedTemplateGlobalValue, - TemplateGlobalFunctionSchema, + TemplateGlobalFunctionExample, } from './types'; -import { JsonValue } from '@backstage/types'; +import { ZodFunctionSchema } from '../types'; /** * This function is used to create new template global values in type-safe manner. @@ -39,10 +39,12 @@ export const createTemplateGlobalValue = ( * @alpha */ export const createTemplateGlobalFunction = < - TSchema extends TemplateGlobalFunctionSchema | undefined, - TFilterSchema extends TSchema extends TemplateGlobalFunctionSchema - ? z.infer> - : (...args: JsonValue[]) => JsonValue | undefined, ->( - fn: CreatedTemplateGlobalFunction, -): CreatedTemplateGlobalFunction => fn; + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, +>(options: { + id: string; + description?: string; + examples?: TemplateGlobalFunctionExample[]; + schema?: ZodFunctionSchema; + fn: (...args: z.infer>) => z.infer; +}): CreatedTemplateGlobalFunction => options; diff --git a/plugins/scaffolder-node/src/alpha/globals/types.ts b/plugins/scaffolder-node/src/alpha/globals/types.ts index cbe74f1610..27a3a700e5 100644 --- a/plugins/scaffolder-node/src/alpha/globals/types.ts +++ b/plugins/scaffolder-node/src/alpha/globals/types.ts @@ -15,7 +15,7 @@ */ import { JsonValue } from '@backstage/types'; import { z } from 'zod'; -import { TemplateGlobal } from '../../types'; +import { ZodFunctionSchema } from '../types'; export type { TemplateGlobal } from '../../types'; @@ -26,15 +26,6 @@ export type CreatedTemplateGlobalValue = { description?: string; }; -/** @alpha */ -export type TemplateGlobalFunctionSchema< - Args extends z.ZodTuple< - [] | [z.ZodType, ...(z.ZodType | z.ZodUnknown)[]], - z.ZodType | z.ZodUnknown | null - >, - Result extends z.ZodType | z.ZodUndefined, -> = (zod: typeof z) => z.ZodFunction; - /** @alpha */ export type TemplateGlobalFunctionExample = { description?: string; @@ -44,31 +35,17 @@ export type TemplateGlobalFunctionExample = { /** @alpha */ export type CreatedTemplateGlobalFunction< - TSchema extends - | TemplateGlobalFunctionSchema - | undefined - | unknown = unknown, - TFilterSchema extends TSchema extends TemplateGlobalFunctionSchema - ? z.infer> - : TSchema extends unknown - ? unknown - : Exclude< - TemplateGlobal, - JsonValue - > = TSchema extends TemplateGlobalFunctionSchema - ? z.infer> - : TSchema extends unknown - ? unknown - : Exclude, + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, > = { id: string; description?: string; examples?: TemplateGlobalFunctionExample[]; - schema?: TSchema; - fn: TFilterSchema; + schema?: ZodFunctionSchema; + fn: (...args: z.infer>) => z.infer; }; /** @alpha */ export type CreatedTemplateGlobal = | CreatedTemplateGlobalValue - | CreatedTemplateGlobalFunction; + | CreatedTemplateGlobalFunction; diff --git a/plugins/scaffolder-node/src/alpha/index.ts b/plugins/scaffolder-node/src/alpha/index.ts index d97aa54557..9df968a52d 100644 --- a/plugins/scaffolder-node/src/alpha/index.ts +++ b/plugins/scaffolder-node/src/alpha/index.ts @@ -27,6 +27,7 @@ import { CreatedTemplateGlobal } from './globals'; export * from '../tasks/alpha'; export * from './filters'; export * from './globals'; +export * from './types'; /** * Extension point for managing scaffolder actions. @@ -73,7 +74,7 @@ export const scaffolderTaskBrokerExtensionPoint = */ export interface ScaffolderTemplatingExtensionPoint { addTemplateFilters( - filters: Record | CreatedTemplateFilter[], + filters: Record | CreatedTemplateFilter[], ): void; addTemplateGlobals( diff --git a/plugins/scaffolder-node/src/alpha/types.ts b/plugins/scaffolder-node/src/alpha/types.ts new file mode 100644 index 0000000000..ca52973775 --- /dev/null +++ b/plugins/scaffolder-node/src/alpha/types.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { z } from 'zod'; + +/** + * @alpha + */ +export type ZodFunctionSchema< + TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], + TReturnType extends z.ZodTypeAny, +> = ( + zod: typeof z, +) => + | z.ZodFunction, TReturnType> + | z.ZodType< + (...args: z.infer>) => z.infer + >; diff --git a/plugins/scaffolder-react/report-alpha.api.md b/plugins/scaffolder-react/report-alpha.api.md index b007fa7d49..fbd539f96e 100644 --- a/plugins/scaffolder-react/report-alpha.api.md +++ b/plugins/scaffolder-react/report-alpha.api.md @@ -347,6 +347,7 @@ export const scaffolderReactTranslationRef: TranslationRef< readonly 'templateCategoryPicker.title': 'Categories'; readonly 'templateCard.noDescription': 'No description'; readonly 'templateCard.chooseButtonText': 'Choose'; + readonly 'cardHeader.detailBtnTitle': 'Show template entity details'; readonly 'templateOutputs.title': 'Text Output'; } >; diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.test.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.test.tsx index 9ae2a507dd..3c7eef5f8d 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.test.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.test.tsx @@ -24,12 +24,21 @@ import { renderInTestApp, TestApiProvider, } from '@backstage/test-utils'; -import { starredEntitiesApiRef } from '@backstage/plugin-catalog-react'; +import { + entityRouteRef, + starredEntitiesApiRef, +} from '@backstage/plugin-catalog-react'; import { DefaultStarredEntitiesApi } from '@backstage/plugin-catalog'; import Observable from 'zen-observable'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; +const mountedRoutes = { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, +}; + describe('CardHeader', () => { it('should select the correct theme from the theme provider from the header', async () => { // Can't really test what we want here. @@ -64,6 +73,7 @@ describe('CardHeader', () => { /> , + mountedRoutes, ); expect(mockTheme.getPageTheme).toHaveBeenCalledWith({ themeId: 'service' }); @@ -93,6 +103,7 @@ describe('CardHeader', () => { }} /> , + mountedRoutes, ); expect(getByText('service')).toBeInTheDocument(); @@ -118,6 +129,7 @@ describe('CardHeader', () => { , + mountedRoutes, ); const favorite = getByRole('button', { name: 'Add to favorites' }); @@ -129,6 +141,38 @@ describe('CardHeader', () => { ); }); + it('renders TemplateDetailButton with link to entity page', async () => { + const { getByTitle } = await renderInTestApp( + + + , + mountedRoutes, + ); + + const detailButton = getByTitle('Show template entity details'); + const link = detailButton.querySelector('a'); + expect(link).toBeInTheDocument(); + }); + it('should render the name of the entity', async () => { const { getByText } = await renderInTestApp( { }} /> , + mountedRoutes, ); expect(getByText('bob')).toBeInTheDocument(); @@ -182,6 +227,7 @@ describe('CardHeader', () => { }} /> , + mountedRoutes, ); expect(getByText('Iamtitle')).toBeInTheDocument(); diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.tsx index 467d26ce76..5e5c6fe8bf 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.tsx @@ -15,10 +15,11 @@ */ import React from 'react'; -import { Theme, makeStyles, useTheme } from '@material-ui/core/styles'; +import { makeStyles, Theme, useTheme } from '@material-ui/core/styles'; import { ItemCardHeader } from '@backstage/core-components'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { FavoriteEntity } from '@backstage/plugin-catalog-react'; +import { TemplateDetailButton } from './TemplateDetailButton.tsx'; const useStyles = makeStyles< Theme, @@ -66,7 +67,11 @@ export const CardHeader = (props: CardHeaderProps) => {
{type}
- + +
); diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.test.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.test.tsx index 673f1e4a9e..e25952131b 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.test.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.test.tsx @@ -33,6 +33,12 @@ import { permissionApiRef } from '@backstage/plugin-permission-react'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { SWRConfig } from 'swr'; +const mountedRoutes = { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, +}; + describe('TemplateCard', () => { it('should render the card title', async () => { const mockTemplate: TemplateEntityV1beta3 = { @@ -59,6 +65,7 @@ describe('TemplateCard', () => { > , + mountedRoutes, ); expect(getByText('bob')).toBeInTheDocument(); @@ -89,6 +96,7 @@ describe('TemplateCard', () => { > , + mountedRoutes, ); const description = getByText('hello'); @@ -121,6 +129,7 @@ describe('TemplateCard', () => { > , + mountedRoutes, ); expect(getByText('No description')).toBeInTheDocument(); @@ -151,6 +160,7 @@ describe('TemplateCard', () => { > , + mountedRoutes, ); expect(queryByTestId('template-card-separator')).toBeInTheDocument(); @@ -187,6 +197,7 @@ describe('TemplateCard', () => { > , + mountedRoutes, ); for (const tag of mockTemplate.metadata.tags!) { @@ -227,11 +238,7 @@ describe('TemplateCard', () => { > , - { - mountedRoutes: { - '/catalog/:kind/:namespace/:name': entityRouteRef, - }, - }, + mountedRoutes, ); expect(queryByTestId('template-card-separator')).toBeInTheDocument(); @@ -272,11 +279,7 @@ describe('TemplateCard', () => { > , - { - mountedRoutes: { - '/catalog/:kind/:namespace/:name': entityRouteRef, - }, - }, + mountedRoutes, ); expect(queryByTestId('template-card-separator')).toBeInTheDocument(); @@ -321,11 +324,7 @@ describe('TemplateCard', () => { > , - { - mountedRoutes: { - '/catalog/:kind/:namespace/:name': entityRouteRef, - }, - }, + mountedRoutes, ); expect(queryByTestId('template-card-separator')).not.toBeInTheDocument(); @@ -364,17 +363,13 @@ describe('TemplateCard', () => { > , - { - mountedRoutes: { - '/catalog/:kind/:namespace/:name': entityRouteRef, - }, - }, + mountedRoutes, ); expect(getByRole('link', { name: /.*my-test-user$/ })).toBeInTheDocument(); expect(getByRole('link', { name: /.*my-test-user$/ })).toHaveAttribute( 'href', - '/catalog/group/default/my-test-user', + '/catalog/default/group/my-test-user', ); }); @@ -404,11 +399,7 @@ describe('TemplateCard', () => { > , - { - mountedRoutes: { - '/catalog/:kind/:namespace/:name': entityRouteRef, - }, - }, + mountedRoutes, ); expect(getByRole('button', { name: 'Choose' })).toBeInTheDocument(); @@ -448,11 +439,7 @@ describe('TemplateCard', () => { , - { - mountedRoutes: { - '/catalog/:kind/:namespace/:name': entityRouteRef, - }, - }, + mountedRoutes, ); expect(queryByText('Choose')).toBeNull(); diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateDetailButton.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateDetailButton.tsx new file mode 100644 index 0000000000..6ecfc2a1e9 --- /dev/null +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateDetailButton.tsx @@ -0,0 +1,64 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import Tooltip from '@material-ui/core/Tooltip'; +import IconButton from '@material-ui/core/IconButton'; +import Typography from '@material-ui/core/Typography'; +import DescriptionIcon from '@material-ui/icons/Description'; +import { Link } from '@backstage/core-components'; +import { + entityRouteParams, + entityRouteRef, +} from '@backstage/plugin-catalog-react'; +import { useApp, useRouteRef } from '@backstage/core-plugin-api'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { scaffolderReactTranslationRef } from '../../../translation'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; + +export interface TemplateDetailButtonProps { + template: Entity; +} + +export const TemplateDetailButton = ({ + template, +}: TemplateDetailButtonProps) => { + const catalogEntityRoute = useRouteRef(entityRouteRef); + const { t } = useTranslationRef(scaffolderReactTranslationRef); + const entityRef = stringifyEntityRef(template); + + const app = useApp(); + const TemplateIcon = app.getSystemIcon('kind:template') || DescriptionIcon; + + return ( + + + + + + + + + + ); +}; diff --git a/plugins/scaffolder-react/src/translation.ts b/plugins/scaffolder-react/src/translation.ts index f490b52064..c097d78e3e 100644 --- a/plugins/scaffolder-react/src/translation.ts +++ b/plugins/scaffolder-react/src/translation.ts @@ -44,6 +44,9 @@ export const scaffolderReactTranslationRef = createTranslationRef({ noDescription: 'No description', chooseButtonText: 'Choose', }, + cardHeader: { + detailBtnTitle: 'Show template entity details', + }, templateOutputs: { title: 'Text Output', }, diff --git a/plugins/scaffolder/src/alpha/components/TemplateListPage/TemplateListPage.test.tsx b/plugins/scaffolder/src/alpha/components/TemplateListPage/TemplateListPage.test.tsx index 01274cf6a6..03175786b1 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateListPage/TemplateListPage.test.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateListPage/TemplateListPage.test.tsx @@ -17,6 +17,7 @@ import { DefaultStarredEntitiesApi } from '@backstage/plugin-catalog'; import { catalogApiRef, + entityRouteRef, starredEntitiesApiRef, } from '@backstage/plugin-catalog-react'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; @@ -30,6 +31,13 @@ import React from 'react'; import { rootRouteRef } from '../../../routes'; import { TemplateListPage } from './TemplateListPage'; +const mountedRoutes = { + mountedRoutes: { + '/': rootRouteRef, + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, +}; + describe('TemplateListPage', () => { const mockCatalogApi = catalogApiMock({ entities: [ @@ -60,7 +68,7 @@ describe('TemplateListPage', () => { > , - { mountedRoutes: { '/': rootRouteRef } }, + mountedRoutes, ); expect(getByPlaceholderText('Search')).toBeInTheDocument(); @@ -82,7 +90,7 @@ describe('TemplateListPage', () => { > , - { mountedRoutes: { '/': rootRouteRef } }, + mountedRoutes, ); expect(getByRole('menuitem', { name: /All/ })).toBeInTheDocument(); @@ -105,7 +113,7 @@ describe('TemplateListPage', () => { > , - { mountedRoutes: { '/': rootRouteRef } }, + mountedRoutes, ); expect(getByText('Categories')).toBeInTheDocument(); @@ -127,7 +135,7 @@ describe('TemplateListPage', () => { > , - { mountedRoutes: { '/': rootRouteRef } }, + mountedRoutes, ); expect(getByText('Owner')).toBeInTheDocument(); @@ -150,6 +158,7 @@ describe('TemplateListPage', () => { > , + mountedRoutes, ); expect(getByText('Tags')).toBeInTheDocument(); @@ -172,7 +181,7 @@ describe('TemplateListPage', () => { > , - { mountedRoutes: { '/': rootRouteRef } }, + mountedRoutes, ); expect(queryByTestId('menu-button')).toBeInTheDocument(); }); @@ -199,7 +208,7 @@ describe('TemplateListPage', () => { }} /> , - { mountedRoutes: { '/': rootRouteRef } }, + mountedRoutes, ); expect(queryByTestId('menu-button')).not.toBeInTheDocument(); }); diff --git a/storybook/yarn.lock b/storybook/yarn.lock index 4edee92e89..1a3e6f0a24 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -970,9 +970,9 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-actions@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/addon-actions@npm:8.6.4" +"@storybook/addon-actions@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/addon-actions@npm:8.6.7" dependencies: "@storybook/global": ^5.0.0 "@types/uuid": ^9.0.1 @@ -980,231 +980,231 @@ __metadata: polished: ^4.2.2 uuid: ^9.0.0 peerDependencies: - storybook: ^8.6.4 - checksum: b08f73070ac90f2c0d404b63f3148ebe11d4683f0728fd04459df62b521422d8c17b3619f6c012a90571a2d5520ee34d35e02fd104c0298580177dd57e045312 + storybook: ^8.6.7 + checksum: 28add3c30e397894bb13d1d492b88e331cec2a24b47ad9e865002e54851a17a0916387dca02074b834c6d665748bd2a5ec88904ad08795645d1b8a9345fb1328 languageName: node linkType: hard -"@storybook/addon-backgrounds@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/addon-backgrounds@npm:8.6.4" +"@storybook/addon-backgrounds@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/addon-backgrounds@npm:8.6.7" dependencies: "@storybook/global": ^5.0.0 memoizerific: ^1.11.3 ts-dedent: ^2.0.0 peerDependencies: - storybook: ^8.6.4 - checksum: 53a67d385d6241730d0d992552de58316cfa4c46f56fa5a81df8647c76356778164783b8a872e31b3624692f456186074e06407778cf4442ffc8596cdaded49e + storybook: ^8.6.7 + checksum: 574b634e49225f52800a7ed0ea7b120f15198ae91068c06842b16e2d197a076bf94c1b42dc05fc0daac328169317768f28294260a14451f928cb0641010bec0d languageName: node linkType: hard -"@storybook/addon-controls@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/addon-controls@npm:8.6.4" +"@storybook/addon-controls@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/addon-controls@npm:8.6.7" dependencies: "@storybook/global": ^5.0.0 dequal: ^2.0.2 ts-dedent: ^2.0.0 peerDependencies: - storybook: ^8.6.4 - checksum: a25c7f2ae7c3b4aee89ba865e5a305293d54bc4723f4e8e4f22d3c42a626d09b83a69521e728e2b3b35107a7709702801c1f5a4fe96e16871d77108282b8a8a2 + storybook: ^8.6.7 + checksum: 002184ac5444ea21bb76e2737f596920d1d06fbf2f7eab75d12d8f4b4f176439132e81e7433ad46f968e379bfb5224992a0a351b19f7673a96bbd74212d6d6c6 languageName: node linkType: hard -"@storybook/addon-docs@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/addon-docs@npm:8.6.4" +"@storybook/addon-docs@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/addon-docs@npm:8.6.7" dependencies: "@mdx-js/react": ^3.0.0 - "@storybook/blocks": 8.6.4 - "@storybook/csf-plugin": 8.6.4 - "@storybook/react-dom-shim": 8.6.4 + "@storybook/blocks": 8.6.7 + "@storybook/csf-plugin": 8.6.7 + "@storybook/react-dom-shim": 8.6.7 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 ts-dedent: ^2.0.0 peerDependencies: - storybook: ^8.6.4 - checksum: 28705136eb3117fcc46b4038a6fb8bf24207fa39b2b5043ab3dd124a3a186d02d36afdb03a456638ec652e1944679de0721361bca27caa5920b234e92c4e9149 + storybook: ^8.6.7 + checksum: 764c3fddbbb92a2c4c7df4d96d943f9511a96a40e87f94b084dbaed789bec4de8d87a83e3877cb7648aafb1fa61940be8d52c2910c037ade89c6ef02beb3d745 languageName: node linkType: hard "@storybook/addon-essentials@npm:^8.3.5": - version: 8.6.4 - resolution: "@storybook/addon-essentials@npm:8.6.4" + version: 8.6.7 + resolution: "@storybook/addon-essentials@npm:8.6.7" dependencies: - "@storybook/addon-actions": 8.6.4 - "@storybook/addon-backgrounds": 8.6.4 - "@storybook/addon-controls": 8.6.4 - "@storybook/addon-docs": 8.6.4 - "@storybook/addon-highlight": 8.6.4 - "@storybook/addon-measure": 8.6.4 - "@storybook/addon-outline": 8.6.4 - "@storybook/addon-toolbars": 8.6.4 - "@storybook/addon-viewport": 8.6.4 + "@storybook/addon-actions": 8.6.7 + "@storybook/addon-backgrounds": 8.6.7 + "@storybook/addon-controls": 8.6.7 + "@storybook/addon-docs": 8.6.7 + "@storybook/addon-highlight": 8.6.7 + "@storybook/addon-measure": 8.6.7 + "@storybook/addon-outline": 8.6.7 + "@storybook/addon-toolbars": 8.6.7 + "@storybook/addon-viewport": 8.6.7 ts-dedent: ^2.0.0 peerDependencies: - storybook: ^8.6.4 - checksum: 564cc7e6f4559572f12c7f015e40a3995c55c681704e4082ef80d9db0c7b06433a67473e810ea9d67a46121f3a4aca8d7851953bc7f109f54e96c069b05230e0 + storybook: ^8.6.7 + checksum: 774b50e98888ece9536ec2a7f491c8486055d3c7515ac9a7f0264916f684c6c4608e95da7db14bceb65dfdf05f3fc93011a0b74a690eec2d44b6c969511b9313 languageName: node linkType: hard -"@storybook/addon-highlight@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/addon-highlight@npm:8.6.4" +"@storybook/addon-highlight@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/addon-highlight@npm:8.6.7" dependencies: "@storybook/global": ^5.0.0 peerDependencies: - storybook: ^8.6.4 - checksum: e080b543bd3fbe0e8d6e916f2abd562b7136371dff64e96d196532dca4958016b7890c6dcd48545f11c2ac31aa0d3a713b98d6fd2cde2a1f94b1ebaf7031095d + storybook: ^8.6.7 + checksum: 7baae25d40e717e5138d10b4ea7ef424b55500ab7496ef38738ea0fb99f910bdf3a56651efbfa85d3e4218382498b045894653b47cdd066c6b16ed0076537940 languageName: node linkType: hard "@storybook/addon-interactions@npm:^8.3.5": - version: 8.6.4 - resolution: "@storybook/addon-interactions@npm:8.6.4" + version: 8.6.7 + resolution: "@storybook/addon-interactions@npm:8.6.7" dependencies: "@storybook/global": ^5.0.0 - "@storybook/instrumenter": 8.6.4 - "@storybook/test": 8.6.4 + "@storybook/instrumenter": 8.6.7 + "@storybook/test": 8.6.7 polished: ^4.2.2 ts-dedent: ^2.2.0 peerDependencies: - storybook: ^8.6.4 - checksum: 6abfd78b17aef00708eb6221d3fdfc45fb738e1fd206a5c08c3909476abe6f042a507b6226ff99708538196294f37aea1d743ca7c33ea270ca0456c0f332ab55 + storybook: ^8.6.7 + checksum: f7a66307aa4e83b1a230d0103123eef15ce7d27aa25c47270624b9ddb6653293e9d0dfe9bb93d3c348604ec05c774716bd513f4d3987ae0b9818a0f865fa0d69 languageName: node linkType: hard "@storybook/addon-links@npm:^8.3.5": - version: 8.6.4 - resolution: "@storybook/addon-links@npm:8.6.4" + version: 8.6.7 + resolution: "@storybook/addon-links@npm:8.6.7" dependencies: "@storybook/global": ^5.0.0 ts-dedent: ^2.0.0 peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.4 + storybook: ^8.6.7 peerDependenciesMeta: react: optional: true - checksum: 92ffa01adda87133dd97623e3beb69b1095929a8ec79c00191b816b4dcdf35d4dcad33b2dfa16791647f85357b8fa8f662cdc382772a6c9376953c8f49564dde + checksum: 3770196105742335eb862eea9e096a7be4cb056067b9bd72a1616ca8b1033d2549199494e017f252342959e51ebb4205b1562c0db17b800aceb4656c5bbe2fa3 languageName: node linkType: hard -"@storybook/addon-measure@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/addon-measure@npm:8.6.4" +"@storybook/addon-measure@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/addon-measure@npm:8.6.7" dependencies: "@storybook/global": ^5.0.0 tiny-invariant: ^1.3.1 peerDependencies: - storybook: ^8.6.4 - checksum: b020d052c87aab591b4b7e31dd60764af9f70140ad4d529d468cdf0c8eb45d02ad7ec2bc01a83d88a1547cf567da8db5e4ba0b4c7294e968df2bace3f07f4fd7 + storybook: ^8.6.7 + checksum: 38c51463366405216aa21f5e99a5c66acf8a626084427a439bbbdf76f41119cf66a5fd25e1b27f7859e180101b4a536ee058a3978b03cbe6a48f2e0807927c83 languageName: node linkType: hard -"@storybook/addon-outline@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/addon-outline@npm:8.6.4" +"@storybook/addon-outline@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/addon-outline@npm:8.6.7" dependencies: "@storybook/global": ^5.0.0 ts-dedent: ^2.0.0 peerDependencies: - storybook: ^8.6.4 - checksum: d705c2dda8fc4b9af2ecb8217a0463f0f8213f836150627f67e4702d449071e5982580617ff75ae8c48960ca27e957fe3700c17cd854e05d326ceeaf622202c2 + storybook: ^8.6.7 + checksum: 8919842d53079cb6ae8efcecc5673d1fcd3ec8f9cf2dbf2f44de0fa2648ef684bd626c9be187ef570e7ff2b21eb92bfe1759b8e72bd41f9f0cb82e2e57073cc7 languageName: node linkType: hard "@storybook/addon-storysource@npm:^8.3.6": - version: 8.6.4 - resolution: "@storybook/addon-storysource@npm:8.6.4" + version: 8.6.7 + resolution: "@storybook/addon-storysource@npm:8.6.7" dependencies: - "@storybook/source-loader": 8.6.4 + "@storybook/source-loader": 8.6.7 estraverse: ^5.2.0 tiny-invariant: ^1.3.1 peerDependencies: - storybook: ^8.6.4 - checksum: 6172f5ea802a5081a6e777597ca68c2e3bddb58be2bcc7b52f7646bdd2216902a2bb426b3fd8e2a6a8710741d9e72d569a279fb6e0aadadae47fbb881e72308f + storybook: ^8.6.7 + checksum: 3aecdc32867b7ecf0a38efffa80caae6f0249391b8e21ea7a7434ba6dd8f054cd918b4669e799b40fe2f654084c114ab20d411d233e540d46b8c208f9d6f5c2a languageName: node linkType: hard "@storybook/addon-themes@npm:^8.3.6": - version: 8.6.4 - resolution: "@storybook/addon-themes@npm:8.6.4" + version: 8.6.7 + resolution: "@storybook/addon-themes@npm:8.6.7" dependencies: ts-dedent: ^2.0.0 peerDependencies: - storybook: ^8.6.4 - checksum: 11ae08e570df4d94ed452c0be6b15773d51c0a8003c6a238156262fafbb8e094fb5c3d20c11bceaae68461ba12fe305732e4d3ea53bf16c8e115ec2ef6fd1f00 + storybook: ^8.6.7 + checksum: 209793b93774e3e1696e33fcf3a62dd63a4ca60920e45b465a86a2504545d00463ebc683f4ff65606b502175d575a969698efe96538daebc5254adfe9b767929 languageName: node linkType: hard -"@storybook/addon-toolbars@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/addon-toolbars@npm:8.6.4" +"@storybook/addon-toolbars@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/addon-toolbars@npm:8.6.7" peerDependencies: - storybook: ^8.6.4 - checksum: c15ef11c50ac067e5b226dba30db956405d94b5ff56d67279697bcdcc63367ef0ab523f091b8755f8b8ab5c1ac6e55c3ded9cef6f5b917261ea9a232bcf9a8dc + storybook: ^8.6.7 + checksum: 72de8200fcbd95714b79deae7194451725abfd75dac56862b76d9f19150aa844829d7e2c99597fc3be650c6cd952c3fe4789866512314fb26f5d112620ae8145 languageName: node linkType: hard -"@storybook/addon-viewport@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/addon-viewport@npm:8.6.4" +"@storybook/addon-viewport@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/addon-viewport@npm:8.6.7" dependencies: memoizerific: ^1.11.3 peerDependencies: - storybook: ^8.6.4 - checksum: 746cd771f371b825da4c5b974602a1c14287212c19ddff9f593e77e9867a3efe76ccc9ee3e503efdcf91949bf3a03979c8f37aa2fe62eb888a3eb11dd208def2 + storybook: ^8.6.7 + checksum: 2a83a8683ca8306e13578266aeffbc24968f9bf11b1b71c19a5aba0239e0365f19d335950ec261f11c4907577a9600ab6182ccc0ad8e60c13f9a9ea02b6b0adc languageName: node linkType: hard -"@storybook/blocks@npm:8.6.4, @storybook/blocks@npm:^8.3.5": - version: 8.6.4 - resolution: "@storybook/blocks@npm:8.6.4" +"@storybook/blocks@npm:8.6.7, @storybook/blocks@npm:^8.3.5": + version: 8.6.7 + resolution: "@storybook/blocks@npm:8.6.7" dependencies: "@storybook/icons": ^1.2.12 ts-dedent: ^2.0.0 peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^8.6.4 + storybook: ^8.6.7 peerDependenciesMeta: react: optional: true react-dom: optional: true - checksum: fb891491f99fa63c02c90e00d166c9299cf93512d2d6036a932a1c243cd568f7ca45af961b89a19ba1c1c51c4ad76d0de8d687249f1dcb8b3411fbec2b7cf238 + checksum: 89b600680772712500318d82195d2a78885d84127a05ab3e2e6474170f871239ab5de72669d0bfdeede99186f8487c48e197ed33fc3e7fc17d0aea228f915bff languageName: node linkType: hard -"@storybook/builder-vite@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/builder-vite@npm:8.6.4" +"@storybook/builder-vite@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/builder-vite@npm:8.6.7" dependencies: - "@storybook/csf-plugin": 8.6.4 + "@storybook/csf-plugin": 8.6.7 browser-assert: ^1.2.1 ts-dedent: ^2.0.0 peerDependencies: - storybook: ^8.6.4 + storybook: ^8.6.7 vite: ^4.0.0 || ^5.0.0 || ^6.0.0 - checksum: e3e6c1674af25a51b87c084476a2f6e07245a4206493ca7758f116588efc3986e1200b45a9e626406e31a37f001f9c8abf0c77e83f02a14a713c5ac407196775 + checksum: 8ffc8b2f60c4f1f88d99becd35f75cf34e6b7506b2a25ba6ec054a98758f3fada2a054a475c0f8fd74a7baba84fa1d799b9b843352df4cafab7e3d7a96d74348 languageName: node linkType: hard -"@storybook/components@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/components@npm:8.6.4" +"@storybook/components@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/components@npm:8.6.7" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: b50923e759eea99320fdf8946056fb33614b9d993ca567207cd0d714e5fae83da485b64b0d74a18b84355396b33c28b5eea050484c40967873c540f83d314b9a + checksum: cb20f8baa76deef1af1739d6c1b1a9f57eb22d158fa046d9919a91840e7a00f1b48e96b7ffe7994365b60808f864c963a3476ee98b4c8b63968a3a877068da3a languageName: node linkType: hard -"@storybook/core@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/core@npm:8.6.4" +"@storybook/core@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/core@npm:8.6.7" dependencies: - "@storybook/theming": 8.6.4 + "@storybook/theming": 8.6.7 better-opn: ^3.0.2 browser-assert: ^1.2.1 esbuild: ^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 @@ -1220,18 +1220,18 @@ __metadata: peerDependenciesMeta: prettier: optional: true - checksum: 8352b0f65bd7c0462471409a7f086125f4e1a93e1777fa20268beb78109f9784cc1d75536a4f9136368db87ba3c5c11987c95835937ba4c2776f968365795d8f + checksum: b150b7472766fccbbc1dec74cba3c16e009a9f24c13f01198923f553d12e2a2070ee2a35f5514f9af350b79e34577946ea65a4a495fd1fa18a27655f897c013a languageName: node linkType: hard -"@storybook/csf-plugin@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/csf-plugin@npm:8.6.4" +"@storybook/csf-plugin@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/csf-plugin@npm:8.6.7" dependencies: unplugin: ^1.3.1 peerDependencies: - storybook: ^8.6.4 - checksum: f1750183ad0c83438803da83d705a0e73819ad7e0cfd010182cc504d8a37ab2f500df4c6ea61ec82b464b13e992a2a4cf6756aabb2a66e12e8c2ee3f22553929 + storybook: ^8.6.7 + checksum: 75c799fd222552f447e24c852b696bc00f1399a631c6a29e4da1b0be97600aa6085f0ebb56beb8c22731b6099fdddaf52edf01c0d0803ba8e22dd04f79bf483a languageName: node linkType: hard @@ -1261,143 +1261,143 @@ __metadata: languageName: node linkType: hard -"@storybook/instrumenter@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/instrumenter@npm:8.6.4" +"@storybook/instrumenter@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/instrumenter@npm:8.6.7" dependencies: "@storybook/global": ^5.0.0 "@vitest/utils": ^2.1.1 peerDependencies: - storybook: ^8.6.4 - checksum: acfc58fd5c29ec78a00498441c97d4528e8f915956c67600c75e7a30d240fb86551e9ce0dad8c0f83c78a828417a64d8aff38db754b87b854c70507d8d3638d0 + storybook: ^8.6.7 + checksum: 8b5bbdd73637233ddebf6d3f14d3904c6f122f29f2efe4a0172a1f03e066a88a3453ae3eef140b227eb626ee959c0c3522f62944407f4530e24e6a6ac804598b languageName: node linkType: hard -"@storybook/manager-api@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/manager-api@npm:8.6.4" +"@storybook/manager-api@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/manager-api@npm:8.6.7" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 50447d3961dfab2b2f36afa76aaeb779a9b51ca66a912bc763103f18f2f71514191388b101ec83093ac9dee6ab509b955da41325f15d88cfe1c480e7d7d1f9eb + checksum: 6bb0860c3795945189832613da5866e5159b2adb376478a2b596c113928c64f5962efc325f927a6bcb7a48d7257f42cf6489b0c44397876129626ac68c44aa28 languageName: node linkType: hard -"@storybook/preview-api@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/preview-api@npm:8.6.4" +"@storybook/preview-api@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/preview-api@npm:8.6.7" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: d8f642de8053686192e28f7ec748944c99c9e4d0cef7f13d96e840fd159c8bea2e3139b88a84eb16009fbda3152a61d9e075777211c10773e4d01fd103bf0e28 + checksum: 0d24d62444b7b153bde89d3949ab6539a163e15bc80c5bfcc73b7d49bbb34fb02eb74a99434793b8a6446aeec491e07f006c0f4e404f98486c6d1d614d01defa languageName: node linkType: hard -"@storybook/react-dom-shim@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/react-dom-shim@npm:8.6.4" +"@storybook/react-dom-shim@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/react-dom-shim@npm:8.6.7" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.4 - checksum: 0062191f312c0bf32e622904b9da7176c961cec99258c6b230c3fb6758c1cca95ac4142d4701628dc8eb922601d74c350549b78e146b5af1747edf8cb3def756 + storybook: ^8.6.7 + checksum: 51c20259ac4485d9a996266341a4d9898c949049e2a9af9a08bec58c2ace3a40be1a534005c52fc2db2c81bb010dbd36030bb70581b36c05cf4b34d58f140d3a languageName: node linkType: hard "@storybook/react-vite@npm:^8.3.5": - version: 8.6.4 - resolution: "@storybook/react-vite@npm:8.6.4" + version: 8.6.7 + resolution: "@storybook/react-vite@npm:8.6.7" dependencies: "@joshwooding/vite-plugin-react-docgen-typescript": 0.5.0 "@rollup/pluginutils": ^5.0.2 - "@storybook/builder-vite": 8.6.4 - "@storybook/react": 8.6.4 + "@storybook/builder-vite": 8.6.7 + "@storybook/react": 8.6.7 find-up: ^5.0.0 magic-string: ^0.30.0 react-docgen: ^7.0.0 resolve: ^1.22.8 tsconfig-paths: ^4.2.0 peerDependencies: - "@storybook/test": 8.6.4 + "@storybook/test": 8.6.7 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.4 + storybook: ^8.6.7 vite: ^4.0.0 || ^5.0.0 || ^6.0.0 peerDependenciesMeta: "@storybook/test": optional: true - checksum: 877ea89891e18a860b598b48df086db58d161bb840093dc4563d470eab61a5042730de356a306d7a2b9de96afe522caa8ebceb0a4210e9e1b924a2548c451d3c + checksum: 75ed76ac6293520aaab02365a12ba93f360cb2f97b632e1c213de535e1d01e93c202f4a3795863ad273246b2bf1096e5b696ea531902d490534db470a712349f languageName: node linkType: hard -"@storybook/react@npm:8.6.4, @storybook/react@npm:^8.3.5": - version: 8.6.4 - resolution: "@storybook/react@npm:8.6.4" +"@storybook/react@npm:8.6.7, @storybook/react@npm:^8.3.5": + version: 8.6.7 + resolution: "@storybook/react@npm:8.6.7" dependencies: - "@storybook/components": 8.6.4 + "@storybook/components": 8.6.7 "@storybook/global": ^5.0.0 - "@storybook/manager-api": 8.6.4 - "@storybook/preview-api": 8.6.4 - "@storybook/react-dom-shim": 8.6.4 - "@storybook/theming": 8.6.4 + "@storybook/manager-api": 8.6.7 + "@storybook/preview-api": 8.6.7 + "@storybook/react-dom-shim": 8.6.7 + "@storybook/theming": 8.6.7 peerDependencies: - "@storybook/test": 8.6.4 + "@storybook/test": 8.6.7 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.4 + storybook: ^8.6.7 typescript: ">= 4.2.x" peerDependenciesMeta: "@storybook/test": optional: true typescript: optional: true - checksum: 4e9860c8e177d0dd496e7d6c53927daa8429b82f9c02d5b004de61244573901160a936ba37ce69810fb13e3602391e18723237a901c57e1bd19cf9f58241c30c + checksum: c98438ddfd764300e1a805623fab976f9ef94a2d939984d6caf98ce92ed29d8a7307389b98e2df2d1302b302dc549c7c95050464731d9c588347cd154c360834 languageName: node linkType: hard -"@storybook/source-loader@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/source-loader@npm:8.6.4" +"@storybook/source-loader@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/source-loader@npm:8.6.7" dependencies: es-toolkit: ^1.22.0 estraverse: ^5.2.0 prettier: ^3.1.1 peerDependencies: - storybook: ^8.6.4 - checksum: 3336f11b99014bb55a7ad88eabc8149ed0ffa4e4cc3d64e22cfc7d7e7f0b36987eda0ca55b6b20d805e89305f277104db6c60c2100eea439bc385808f6193bc2 + storybook: ^8.6.7 + checksum: 74d5dd20384c0e17720ba3492458362bfd2f527614f37cffef438b202d12f4f45c411d2568308975286c34eea05d7bcb1baeaf6605337b7906725f7257b542c6 languageName: node linkType: hard -"@storybook/test@npm:8.6.4, @storybook/test@npm:^8.3.5": - version: 8.6.4 - resolution: "@storybook/test@npm:8.6.4" +"@storybook/test@npm:8.6.7, @storybook/test@npm:^8.3.5": + version: 8.6.7 + resolution: "@storybook/test@npm:8.6.7" dependencies: "@storybook/global": ^5.0.0 - "@storybook/instrumenter": 8.6.4 + "@storybook/instrumenter": 8.6.7 "@testing-library/dom": 10.4.0 "@testing-library/jest-dom": 6.5.0 "@testing-library/user-event": 14.5.2 "@vitest/expect": 2.0.5 "@vitest/spy": 2.0.5 peerDependencies: - storybook: ^8.6.4 - checksum: 2c16f1cc8e0f6e57f5972a664839885467c7a8b01cf807b32ea7cdd3508354cd408603f05b3f6d1c118e4560f4224102df6dfc0242deb0d2ea670895e8cdcc81 + storybook: ^8.6.7 + checksum: d269423580cbcc61de74a3940504f00bb580b389b04432aa6a0a7e8fed54536782ef254e8edcf6bac3ce98d35e439fdfd86316cf53f3435396f12cbc2e060541 languageName: node linkType: hard -"@storybook/theming@npm:8.6.4": - version: 8.6.4 - resolution: "@storybook/theming@npm:8.6.4" +"@storybook/theming@npm:8.6.7": + version: 8.6.7 + resolution: "@storybook/theming@npm:8.6.7" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: b7e13026c381e18e7ad2064e1528876eb01b7563ad16aababcac531ce76f5e59b985a72603f57d75465a611ea564bd46b9b1760634211faf4307f9265f06e044 + checksum: 747a2377df65d5f0962dc8606d06247ad89fb7ecabab0bd9611055d599b5bd0fa15769e4c76e948ae34ae232ba756bf6795ccb3467b1adf35ff961db7350f4de languageName: node linkType: hard "@storybook/types@npm:^8.3.5": - version: 8.6.4 - resolution: "@storybook/types@npm:8.6.4" + version: 8.6.7 + resolution: "@storybook/types@npm:8.6.7" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 99c59e960b9b4500c1bfcdcaedf0070fc7eb500c1d3226e39bc17c7ab3671fca89645abe30baf4dcfd65764d4955db6b2c416049aef78ab81535a27a4c671eb5 + checksum: bae7a202bda4f83f8f8d9a176ff79a033f2e67f5db4d9ee05cd468c3921d8bcc2a44d93a11c8401ad7b3a08f6a54510013791ce932117f10fc6b289642be18aa languageName: node linkType: hard @@ -4451,10 +4451,10 @@ __metadata: linkType: hard "storybook@npm:^8.3.5": - version: 8.6.4 - resolution: "storybook@npm:8.6.4" + version: 8.6.7 + resolution: "storybook@npm:8.6.7" dependencies: - "@storybook/core": 8.6.4 + "@storybook/core": 8.6.7 peerDependencies: prettier: ^2 || ^3 peerDependenciesMeta: @@ -4464,7 +4464,7 @@ __metadata: getstorybook: ./bin/index.cjs sb: ./bin/index.cjs storybook: ./bin/index.cjs - checksum: 96c59b5c6ac27dfb9fb85d110b0257a77264015454dd0ca7893ae9df04dbf31e523ff351ba45283f33a58ecb28fd985349aa9c1fb83ab51d0c781a4d5809fa15 + checksum: fe21ce4d9cd17069a5b409ed518603f97ec6ec421b7f41cff3e43b409820fcbc2c8dbcb3aed99dd7d0a99a0c6138c7576ddf72f78186b6f86085d503b3c8b32f languageName: node linkType: hard diff --git a/yarn.lock b/yarn.lock index ea0ddbfe96..ae8f7ed17d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14589,13 +14589,13 @@ __metadata: linkType: hard "@playwright/test@npm:^1.32.3": - version: 1.51.0 - resolution: "@playwright/test@npm:1.51.0" + version: 1.51.1 + resolution: "@playwright/test@npm:1.51.1" dependencies: - playwright: 1.51.0 + playwright: 1.51.1 bin: playwright: cli.js - checksum: 14959f930db55882ff45ba660dfd37e066bea2607619549f85b6c9c0591a895f4dcbc5419e2fc4069b03b02fef6d00628bc9382b65216c99735d6d93eb578374 + checksum: c3c14f37451cc0323234e37fa559a075e5aee9f5e6ca06fa3b4a132ec6c809909b22877daca1fa48c95a1112d859346c10111e7003f44582a05100dd5385bbef languageName: node linkType: hard @@ -20229,12 +20229,12 @@ __metadata: linkType: hard "@types/react@npm:^18.0.0": - version: 18.3.18 - resolution: "@types/react@npm:18.3.18" + version: 18.3.19 + resolution: "@types/react@npm:18.3.19" dependencies: "@types/prop-types": "*" csstype: ^3.0.2 - checksum: 5933597bc9f53e282f0438f0bb76d0f0fab60faabe760ea806e05ffe6f5c61b9b4d363e1a03a8fea47c510d493c6cf926cdeeba9f7074fa97b61940c350245e7 + checksum: 41305614686cfc618ef154f06d3f80e7b2df4673ebbc474880c524ecbbaa48f14620b3b1c6cff9f225176932d362c62fe617020027069157cb040752902c35cb languageName: node linkType: hard @@ -39310,27 +39310,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.51.0": - version: 1.51.0 - resolution: "playwright-core@npm:1.51.0" +"playwright-core@npm:1.51.1": + version: 1.51.1 + resolution: "playwright-core@npm:1.51.1" bin: playwright-core: cli.js - checksum: e14798a7323539e1d6f076a6aa0492ea7c5fa89f4db85a53b27001b0f84bde2a5f8b14e25e05c5396594c89173a48398728522932bb7bd2006dd4bb48c8b0979 + checksum: 1eb37e22e97435a5ed6389b4caa666fbe618348861cae97e67586e20c8fed9ac3d3dc899ff3b9237d0ddfcf087d5b552b80be247e246fc45b75282f96be714bb languageName: node linkType: hard -"playwright@npm:1.51.0": - version: 1.51.0 - resolution: "playwright@npm:1.51.0" +"playwright@npm:1.51.1": + version: 1.51.1 + resolution: "playwright@npm:1.51.1" dependencies: fsevents: 2.3.2 - playwright-core: 1.51.0 + playwright-core: 1.51.1 dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 02ec1077e2997872415fd40c9a5df6fd796d9cc15dade24bff9a88a16a451217744d850bdfc09efd4812d05c2fc856f92bfe106aae036fe21defa62c6d5b8ea5 + checksum: 2d78f53eed438d26df9743f98227d2e33bb5a378c8d0144a4ed75b4010c9c517d5a6ee6c1c9245ad61dc615054c5e19d2ded5a6e285e35d2bca39b05bc9c4a35 languageName: node linkType: hard